Skip to content
Closed
46 changes: 30 additions & 16 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1098,22 +1098,36 @@ 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);

// Perfect match: prefer imports that match the exact type-only requirement
if (
fix.addAsTypeOnly === AddAsTypeOnly.Required && isTypeOnly ||
fix.addAsTypeOnly === AddAsTypeOnly.NotAllowed && !isTypeOnly
) {
return fix;
}

// Don't use incompatible imports even as fallback
if (fix.addAsTypeOnly === AddAsTypeOnly.NotAllowed && isTypeOnly) {
// Value imports should not go to type-only imports
continue;
}

// For "Allowed" imports, prefer value imports over type-only imports
// This handles the case where isValidTypeOnlyUseSite is false (value context)
// but addAsTypeOnly is Allowed instead of NotAllowed
if (fix.addAsTypeOnly === AddAsTypeOnly.Allowed && isTypeOnly && !isValidTypeOnlyUseSite) {
continue;
}

best ??= fix;
}
return best;

function getAddToExistingImportFix({ declaration, importKind, symbol, targetFlags }: FixAddToExistingImportInfo): FixAddToExistingImport | undefined {
Expand Down
34 changes: 34 additions & 0 deletions tests/cases/fourslash/completionsSymbolImportAsValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// <reference path="fourslash.ts" />

// 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() {
//// /**/;
////}

verify.completions({
marker: "",
includes: [
{ name: "VALUE", source: "/exports", hasAction: true, sortText: "16" },
],
preferences: {
includeCompletionsForModuleExports: true,
},
});

verify.applyCodeActionFromCompletion("", {
name: "VALUE",
source: "/exports",
description: `Update import from "./exports"`,
newFileContent:
`import { VALUE, type SomeType } from "./exports";
function main() {
VALUE;
}`
});