diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index d910128f6db86..89b673097b70c 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -663,13 +663,15 @@ namespace ts.codefix { } const { allowsImportingSpecifier } = createPackageJsonImportFilter(sourceFile, preferences, host); return fixes.reduce((best, fix) => + // Takes true branch of conditional if `fix` is better than `best` compareModuleSpecifiers(fix, best, sourceFile, program, allowsImportingSpecifier) === Comparison.LessThan ? fix : best ); } + /** @returns `Comparison.LessThan` if `a` is better than `b`. */ function compareModuleSpecifiers(a: ImportFix, b: ImportFix, importingFile: SourceFile, program: Program, allowsImportingSpecifier: (specifier: string) => boolean): Comparison { if (a.kind !== ImportFixKind.UseNamespace && b.kind !== ImportFixKind.UseNamespace) { - return compareBooleans(allowsImportingSpecifier(a.moduleSpecifier), allowsImportingSpecifier(b.moduleSpecifier)) + return compareBooleans(allowsImportingSpecifier(b.moduleSpecifier), allowsImportingSpecifier(a.moduleSpecifier)) || compareNodeCoreModuleSpecifiers(a.moduleSpecifier, b.moduleSpecifier, importingFile, program) || compareNumberOfDirectorySeparators(a.moduleSpecifier, b.moduleSpecifier); } diff --git a/tests/cases/fourslash/completionsImport_46332.ts b/tests/cases/fourslash/completionsImport_46332.ts new file mode 100644 index 0000000000000..836faa5974784 --- /dev/null +++ b/tests/cases/fourslash/completionsImport_46332.ts @@ -0,0 +1,93 @@ +/// + +// @module: esnext +// @moduleResolution: node + +// @Filename: /node_modules/vue/package.json +//// { +//// "name": "vue", +//// "types": "dist/vue.d.ts" +//// } + +// @Filename: /node_modules/vue/dist/vue.d.ts +//// export * from "@vue/runtime-dom" + +// @Filename: /node_modules/@vue/runtime-dom/package.json +//// { +//// "name": "@vue/runtime-dom", +//// "types": "dist/runtime-dom.d.ts" +//// } + +// @Filename: /node_modules/@vue/runtime-dom/dist/runtime-dom.d.ts +//// export * from "@vue/runtime-core"; +//// export {} +//// declare module '@vue/reactivity' { +//// export interface RefUnwrapBailTypes { +//// runtimeDOMBailTypes: any +//// } +//// } + +// @Filename: /node_modules/@vue/runtime-core/package.json +//// { +//// "name": "@vue/runtime-core", +//// "types": "dist/runtime-core.d.ts" +//// } + +// @Filename: /node_modules/@vue/runtime-core/dist/runtime-core.d.ts +//// import { ref } from '@vue/reactivity'; +//// export { ref }; +//// declare module '@vue/reactivity' { +//// export interface RefUnwrapBailTypes { +//// runtimeCoreBailTypes: any +//// } +//// } + +// @Filename: /node_modules/@vue/reactivity/package.json +//// { +//// "name": "@vue/reactivity", +//// "types": "dist/reactivity.d.ts" +//// } + +// @Filename: /node_modules/@vue/reactivity/dist/reactivity.d.ts +//// export declare function ref(): T; + +// @Filename: /package.json +//// { +//// "dependencies": { +//// "vue": "*" +//// } +//// } + +// @Filename: /index.ts +//// import {} from "vue"; +//// ref/**/ + +verify.completions({ + marker: "", + includes: [{ + name: "ref", + source: "vue", + sourceDisplay: "vue", + hasAction: true, + sortText: completion.SortText.AutoImportSuggestions, + }], + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + }, +}); + +verify.applyCodeActionFromCompletion("", { + name: "ref", + source: "vue", + description: `Add 'ref' to existing import declaration from "vue"`, + data: { + exportName: "ref", + fileName: "/node_modules/vue/dist/vue.d.ts", + }, + preferences: { + includeCompletionsForModuleExports: true, + allowIncompleteCompletions: true, + }, + newFileContent: `import { ref } from "vue";\nref` +});