diff --git a/src/code_fixes/import_fixes.ts b/src/code_fixes/import_fixes.ts new file mode 100644 index 0000000..35b8804 --- /dev/null +++ b/src/code_fixes/import_fixes.ts @@ -0,0 +1,58 @@ +import { CodeFixAction } from "typescript/lib/tsserverlibrary"; + +import { registerCodeFix } from "../codefix_provider"; +import { HashMeta } from "../module_resolver/hash_meta"; + +export const importFixName = "import"; +// const importFixId = "fixMissingImport"; + +const errorCodes: readonly number[] = [ + 2304, // Diagnostics.Cannot_find_name_0.code, + 2552, // Diagnostics.Cannot_find_name_0_Did_you_mean_1.code, + 2663, // Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code, + 2662, // Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code, + 2503, // Diagnostics.Cannot_find_namespace_0.code, + 2686, // Diagnostics._0_refers_to_a_UMD_global_but_the_current_file_is_a_module_Consider_adding_an_import_instead.code, + 2693, // Diagnostics._0_only_refers_to_a_type_but_is_being_used_as_a_value_here.code, +]; + +function replaceCodeActions(codeFixActions: readonly CodeFixAction[]): void { + for (const codeAction of codeFixActions) { + if (codeAction.fixName !== importFixName) { + continue; + } + + const matchs = codeAction.description.match( + /\.\..+deno\/deps\/https?\/.+\/\w{64}/, + ); + if (matchs == null || matchs.length === 0) { + continue; + } + + const originImport = matchs[0]; + const meta = HashMeta.create(`${originImport}.metadata.json`); + if (meta == null) { + continue; + } + + const newImport = meta.url.href; + codeAction.description = codeAction.description.replace( + originImport, + newImport, + ); + + for (const change of codeAction.changes) { + for (const textChange of change.textChanges) { + textChange.newText = textChange.newText.replace( + originImport, + newImport, + ); + } + } + } +} + +registerCodeFix({ + errorCodes, + replaceCodeActions, +}); diff --git a/src/code_fixes/index.ts b/src/code_fixes/index.ts new file mode 100644 index 0000000..ef78180 --- /dev/null +++ b/src/code_fixes/index.ts @@ -0,0 +1 @@ +import "./import_fixes" \ No newline at end of file diff --git a/src/codefix_provider.ts b/src/codefix_provider.ts new file mode 100644 index 0000000..d6ce3de --- /dev/null +++ b/src/codefix_provider.ts @@ -0,0 +1,22 @@ +import { CodeFixAction } from "typescript/lib/tsserverlibrary"; + +export interface CodeFixRegistration { + errorCodes: readonly number[]; + // fixIds: readonly string[], + replaceCodeActions: (codeFixActions: readonly CodeFixAction[]) => void; +} + +export const errorCodeToFixes: Map< + number, + Pick[] +> = new Map(); + +export function registerCodeFix(reg: CodeFixRegistration) { + for (const error of reg.errorCodes) { + if (errorCodeToFixes.has(error)) { + errorCodeToFixes.get(error)!.push(reg); + } else { + errorCodeToFixes.set(error, [reg]); + } + } +} diff --git a/src/index.ts b/src/index.ts index 3cffbd3..3cd9c09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,9 @@ import merge from "merge-deep"; import ts_module, { ResolvedModuleFull, CompilerOptions, + UserPreferences, + FormatCodeSettings, + CodeFixAction, } from "typescript/lib/tsserverlibrary"; import { parseFromString, resolve, ImportMaps } from "import-maps"; @@ -21,6 +24,8 @@ import { import { universalModuleResolver } from "./module_resolver/universal_module_resolver"; import { HashMeta } from "./module_resolver/hash_meta"; +import { errorCodeToFixes } from "./codefix_provider"; +import './code_fixes' let logger: Logger; let pluginInfo: ts_module.server.PluginCreateInfo; @@ -404,12 +409,41 @@ module.exports = function init( }); } + // TODO(justjavac): maybe also `getCombinedCodeFix` + function getCodeFixesAtPosition( + fileName: string, + start: number, + end: number, + errorCodes: readonly number[], + formatOptions: FormatCodeSettings, + preferences: UserPreferences, + ): readonly CodeFixAction[] { + const codeFixActions = tsLs.getCodeFixesAtPosition( + fileName, + start, + end, + errorCodes, + formatOptions, + preferences, + ); + + for (const errorCode of errorCodes) { + const fixes = errorCodeToFixes.get(errorCode)! + for (const fix of fixes) { + fix.replaceCodeActions(codeFixActions); + } + } + + return codeFixActions; + } + const proxy: ts_module.LanguageService = Object.assign( Object.create(null), tsLs, { getCompletionEntryDetails, getSemanticDiagnostics, + getCodeFixesAtPosition, }, ); @@ -478,13 +512,13 @@ function parseModuleName( if (meta && meta.url) { scriptURL = meta.url; } else { - scriptURL = new URL("file:///" + path.dirname(containingFile) + "/") + scriptURL = new URL("file:///" + path.dirname(containingFile) + "/"); } } else { - scriptURL = new URL("file:///" + path.dirname(containingFile) + "/") + scriptURL = new URL("file:///" + path.dirname(containingFile) + "/"); } - logger && logger.info(`baseUrl: ${scriptURL}`) + logger && logger.info(`baseUrl: ${scriptURL}`); const moduleUrl = resolve( moduleName, @@ -493,11 +527,11 @@ function parseModuleName( ); if (moduleUrl.protocol === "file:") { - return fileURLToPath(moduleUrl.href) + return fileURLToPath(moduleUrl.href); } if (moduleUrl.protocol === "http:" || moduleUrl.protocol === "https:") { - return moduleUrl.href + return moduleUrl.href; } // just support protocol: file, http, https