diff --git a/package-lock.json b/package-lock.json index d4d2c055ccfff..bf279bf5481fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,7 +42,7 @@ "fast-xml-parser": "^4.5.2", "glob": "^10.4.5", "globals": "^15.15.0", - "hereby": "^1.10.0", + "hereby": "^1.11.0", "jsonc-parser": "^3.3.1", "knip": "^5.44.4", "minimist": "^1.2.8", @@ -2878,15 +2878,16 @@ } }, "node_modules/hereby": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.10.0.tgz", - "integrity": "sha512-TyOJ58DFqzCi6PU4/7bDeb2kNIVCPLfJ+RLGdDZxAGIbPRLQ61nupPUtX9QudYHt+dnRpfIHAwclq7SlDc1cDw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.11.0.tgz", + "integrity": "sha512-Tdp03ueQL3w5eZCrQVW4POBDxg9ldjNyFrZYWt6ndMha5agyRUO8b+irOEG3B1RSS9/2LxSjjyG6DSxITouJzA==", "dev": true, + "license": "MIT", "dependencies": { "command-line-usage": "^6.1.3", "fastest-levenshtein": "^1.0.16", "minimist": "^1.2.8", - "picocolors": "^1.0.1", + "picocolors": "^1.1.0", "pretty-ms": "^8.0.0" }, "bin": { @@ -6842,15 +6843,15 @@ "dev": true }, "hereby": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.10.0.tgz", - "integrity": "sha512-TyOJ58DFqzCi6PU4/7bDeb2kNIVCPLfJ+RLGdDZxAGIbPRLQ61nupPUtX9QudYHt+dnRpfIHAwclq7SlDc1cDw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/hereby/-/hereby-1.11.0.tgz", + "integrity": "sha512-Tdp03ueQL3w5eZCrQVW4POBDxg9ldjNyFrZYWt6ndMha5agyRUO8b+irOEG3B1RSS9/2LxSjjyG6DSxITouJzA==", "dev": true, "requires": { "command-line-usage": "^6.1.3", "fastest-levenshtein": "^1.0.16", "minimist": "^1.2.8", - "picocolors": "^1.0.1", + "picocolors": "^1.1.0", "pretty-ms": "^8.0.0" } }, diff --git a/package.json b/package.json index ef2c80f8b91c2..e7b4c7797d0f8 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "fast-xml-parser": "^4.5.2", "glob": "^10.4.5", "globals": "^15.15.0", - "hereby": "^1.10.0", + "hereby": "^1.11.0", "jsonc-parser": "^3.3.1", "knip": "^5.44.4", "minimist": "^1.2.8", diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index 6034c9dfc3de1..d6c71415b6dbb 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -1098,22 +1098,30 @@ function getAddAsTypeOnly( return AddAsTypeOnly.Allowed; } -function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], isValidTypeOnlyUseSite: boolean, checker: TypeChecker, compilerOptions: CompilerOptions): FixAddToExistingImport | undefined { - let best: FixAddToExistingImport | undefined; - for (const existingImport of existingImports) { - const fix = getAddToExistingImportFix(existingImport); - if (!fix) continue; - const isTypeOnly = isTypeOnlyImportDeclaration(fix.importClauseOrBindingPattern); - if ( - fix.addAsTypeOnly !== AddAsTypeOnly.NotAllowed && isTypeOnly || - fix.addAsTypeOnly === AddAsTypeOnly.NotAllowed && !isTypeOnly - ) { - // Give preference to putting types in existing type-only imports and avoiding conversions - // of import statements to/from type-only. - return fix; - } - best ??= fix; - } +function tryAddToExistingImport(existingImports: readonly FixAddToExistingImportInfo[], isValidTypeOnlyUseSite: boolean, checker: TypeChecker, compilerOptions: CompilerOptions): FixAddToExistingImport | undefined { + let best: FixAddToExistingImport | undefined; + for (const existingImport of existingImports) { + const fix = getAddToExistingImportFix(existingImport); + if (!fix) continue; + const isTypeOnly = isTypeOnlyImportDeclaration(fix.importClauseOrBindingPattern); + + // Skip incompatible combinations: value imports going to type-only imports + if (fix.addAsTypeOnly === AddAsTypeOnly.NotAllowed && isTypeOnly) { + continue; + } + + // Perfect matches: return immediately + if ( + fix.addAsTypeOnly !== AddAsTypeOnly.NotAllowed && isTypeOnly || + fix.addAsTypeOnly === AddAsTypeOnly.NotAllowed && !isTypeOnly + ) { + // Give preference to putting types in existing type-only imports and avoiding conversions + // of import statements to/from type-only. + return fix; + } + + best ??= fix; + } return best; function getAddToExistingImportFix({ declaration, importKind, symbol, targetFlags }: FixAddToExistingImportInfo): FixAddToExistingImport | undefined { @@ -1902,8 +1910,8 @@ function doAddExistingFix( return; } - // promoteFromTypeOnly = true if we need to promote the entire original clause from type only - const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed); + // promoteFromTypeOnly = true if we need to promote the entire original clause from type only + const promoteFromTypeOnly = clause.isTypeOnly && some([defaultImport, ...namedImports], i => i?.addAsTypeOnly === AddAsTypeOnly.NotAllowed); const existingSpecifiers = clause.namedBindings && tryCast(clause.namedBindings, isNamedImports)?.elements; if (defaultImport) { diff --git a/tests/cases/fourslash/completionsSymbolImportAsValue.ts b/tests/cases/fourslash/completionsSymbolImportAsValue.ts new file mode 100644 index 0000000000000..8721d13e7055e --- /dev/null +++ b/tests/cases/fourslash/completionsSymbolImportAsValue.ts @@ -0,0 +1,34 @@ +/// + +// Test the simplest case possible + +// @Filename: /exports.ts +////export const VALUE = 42; +////export interface SomeType { } + +// @Filename: /imports.ts +////import type { SomeType } from "./exports"; +////function main() { +//// /*completion*/; +////} + +verify.completions({ + marker: "completion", + includes: [ + { name: "VALUE", source: "/exports", hasAction: true, sortText: "16" }, + ], + preferences: { + includeCompletionsForModuleExports: true, + }, +}); + +verify.applyCodeActionFromCompletion("completion", { + name: "VALUE", + source: "/exports", + description: `Update import from "./exports"`, + newFileContent: +`import { VALUE, type SomeType } from "./exports"; +function main() { + VALUE; +}` +}); \ No newline at end of file