Skip to content

Commit 544d175

Browse files
Implement fix for duplicate imports in isolatedDeclarations quick fixes
Co-authored-by: RyanCavanaugh <[email protected]>
1 parent 991852a commit 544d175

2 files changed

Lines changed: 124 additions & 43 deletions

File tree

src/services/codefixes/fixMissingTypeAnnotationOnExports.ts

Lines changed: 102 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import {
2-
createCodeFixAction,
3-
createCombinedCodeActions,
4-
createImportAdder,
5-
eachDiagnostic,
6-
registerCodeFix,
7-
typeNodeToAutoImportableTypeNode,
8-
typePredicateToAutoImportableTypeNode,
9-
typeToMinimizedReferenceType,
1+
import {
2+
createCodeFixAction,
3+
createCombinedCodeActions,
4+
createImportAdder,
5+
eachDiagnostic,
6+
ImportAdder,
7+
importSymbols,
8+
registerCodeFix,
9+
tryGetAutoImportableReferenceFromTypeNode,
10+
typePredicateToAutoImportableTypeNode,
11+
typeToMinimizedReferenceType,
1012
} from "../_namespaces/ts.codefix.js";
1113
import {
1214
ArrayBindingPattern,
@@ -38,15 +40,17 @@ import {
3840
findAncestor,
3941
FunctionDeclaration,
4042
GeneratedIdentifierFlags,
41-
getEmitScriptTarget,
42-
getSourceFileOfNode,
43-
getSynthesizedDeepClone,
44-
getTokenAtPosition,
43+
getEmitScriptTarget,
44+
getNameForExportedSymbol,
45+
getSourceFileOfNode,
46+
getSynthesizedDeepClone,
47+
getTokenAtPosition,
4548
getTrailingCommentRanges,
4649
hasInitializer,
4750
hasSyntacticModifier,
48-
Identifier,
49-
InternalNodeBuilderFlags,
51+
Identifier,
52+
ImportDeclaration,
53+
InternalNodeBuilderFlags,
5054
isArrayBindingPattern,
5155
isArrayLiteralExpression,
5256
isAssertionExpression,
@@ -80,16 +84,19 @@ import {
8084
isValueSignatureDeclaration,
8185
isVariableDeclaration,
8286
ModifierFlags,
83-
ModifierLike,
84-
Node,
87+
ModifierLike,
88+
NamedImports,
89+
NamespaceImport,
90+
Node,
8591
NodeBuilderFlags,
8692
NodeFlags,
8793
ObjectBindingPattern,
8894
ObjectLiteralExpression,
8995
ParameterDeclaration,
9096
PropertyAccessExpression,
91-
PropertyDeclaration,
92-
setEmitFlags,
97+
PropertyDeclaration,
98+
ScriptTarget,
99+
setEmitFlags,
93100
SignatureDeclaration,
94101
some,
95102
SourceFile,
@@ -1097,22 +1104,22 @@ function withContext<T>(
10971104
return emptyInferenceResult;
10981105
}
10991106

1100-
function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode | undefined {
1101-
let isTruncated = false;
1102-
const minimizedTypeNode = typeToMinimizedReferenceType(typeChecker, type, enclosingDeclaration, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, {
1103-
moduleResolverHost: program,
1104-
trackSymbol() {
1105-
return true;
1106-
},
1107-
reportTruncationError() {
1108-
isTruncated = true;
1109-
},
1110-
});
1111-
if (!minimizedTypeNode) {
1112-
return undefined;
1113-
}
1114-
const result = typeNodeToAutoImportableTypeNode(minimizedTypeNode, importAdder, scriptTarget);
1115-
return isTruncated ? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) : result;
1107+
function typeToTypeNode(type: Type, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode | undefined {
1108+
let isTruncated = false;
1109+
const minimizedTypeNode = typeToMinimizedReferenceType(typeChecker, type, enclosingDeclaration, declarationEmitNodeBuilderFlags | flags, declarationEmitInternalNodeBuilderFlags, {
1110+
moduleResolverHost: program,
1111+
trackSymbol() {
1112+
return true;
1113+
},
1114+
reportTruncationError() {
1115+
isTruncated = true;
1116+
},
1117+
});
1118+
if (!minimizedTypeNode) {
1119+
return undefined;
1120+
}
1121+
const result = typeNodeToAutoImportableTypeNodeWithExistingImportCheck(minimizedTypeNode, importAdder, scriptTarget, sourceFile, typeChecker);
1122+
return isTruncated ? factory.createKeywordTypeNode(SyntaxKind.AnyKeyword) : result;
11161123
}
11171124

11181125
function typePredicateToTypeNode(typePredicate: TypePredicate, enclosingDeclaration: Node, flags = NodeBuilderFlags.None): TypeNode | undefined {
@@ -1142,14 +1149,66 @@ function withContext<T>(
11421149
}
11431150
}
11441151

1145-
function typeToStringForDiag(node: Node) {
1146-
setEmitFlags(node, EmitFlags.SingleLine);
1147-
const result = typePrinter.printNode(EmitHint.Unspecified, node, sourceFile);
1148-
if (result.length > defaultMaximumTruncationLength) {
1149-
return result.substring(0, defaultMaximumTruncationLength - "...".length) + "...";
1150-
}
1151-
setEmitFlags(node, EmitFlags.None);
1152-
return result;
1152+
function typeToStringForDiag(node: Node) {
1153+
setEmitFlags(node, EmitFlags.SingleLine);
1154+
const result = typePrinter.printNode(EmitHint.Unspecified, node, sourceFile);
1155+
if (result.length > defaultMaximumTruncationLength) {
1156+
return result.substring(0, defaultMaximumTruncationLength - "...".length) + "...";
1157+
}
1158+
setEmitFlags(node, EmitFlags.None);
1159+
return result;
1160+
}
1161+
1162+
function typeNodeToAutoImportableTypeNodeWithExistingImportCheck(typeNode: TypeNode, importAdder: ImportAdder, scriptTarget: ScriptTarget, sourceFile: SourceFile, typeChecker: TypeChecker): TypeNode | undefined {
1163+
const importableReference = tryGetAutoImportableReferenceFromTypeNode(typeNode, scriptTarget);
1164+
if (importableReference) {
1165+
// Check if symbols are already available before importing them
1166+
const symbolsToImport = importableReference.symbols.filter(symbol => {
1167+
const symbolName = getNameForExportedSymbol(symbol, scriptTarget);
1168+
return !isSymbolAlreadyAvailable(symbolName, sourceFile, typeChecker);
1169+
});
1170+
1171+
if (symbolsToImport.length > 0) {
1172+
importSymbols(importAdder, symbolsToImport);
1173+
}
1174+
typeNode = importableReference.typeNode;
1175+
}
1176+
1177+
// Ensure nodes are fresh so they can have different positions when going through formatting.
1178+
return getSynthesizedDeepClone(typeNode);
1179+
}
1180+
1181+
function isSymbolAlreadyAvailable(symbolName: string, sourceFile: SourceFile, _typeChecker: TypeChecker): boolean {
1182+
// Check if the symbol name is already imported in the current file
1183+
for (const statement of sourceFile.statements) {
1184+
if (statement.kind === SyntaxKind.ImportDeclaration) {
1185+
const importDecl = statement as ImportDeclaration;
1186+
if (importDecl.importClause) {
1187+
// Check default import
1188+
if (importDecl.importClause.name && importDecl.importClause.name.text === symbolName) {
1189+
return true;
1190+
}
1191+
// Check named imports
1192+
if (importDecl.importClause.namedBindings && importDecl.importClause.namedBindings.kind === SyntaxKind.NamedImports) {
1193+
const namedImports = importDecl.importClause.namedBindings;
1194+
for (const element of namedImports.elements) {
1195+
const name = element.name.text;
1196+
if (name === symbolName) {
1197+
return true;
1198+
}
1199+
}
1200+
}
1201+
// Check namespace import
1202+
if (importDecl.importClause.namedBindings && importDecl.importClause.namedBindings.kind === SyntaxKind.NamespaceImport) {
1203+
const namespaceImport = importDecl.importClause.namedBindings;
1204+
if (namespaceImport.name.text === symbolName) {
1205+
return true;
1206+
}
1207+
}
1208+
}
1209+
}
1210+
}
1211+
return false;
11531212
}
11541213

11551214
// Some --isolatedDeclarations errors are not present on the node that directly needs type annotation, so look in the
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/// <reference path='fourslash.ts'/>
2+
3+
// @isolatedDeclarations: true
4+
// @declaration: true
5+
6+
// @fileName: mymodule.d.ts
7+
////declare class VolumeClass {
8+
//// constructor();
9+
////}
10+
////export const Volume: typeof VolumeClass;
11+
12+
// @fileName: test.ts
13+
////import { Volume } from './mymodule';
14+
////export const foo = new Volume();
15+
16+
verify.codeFixAll({
17+
fixId: "fixMissingTypeAnnotationOnExports",
18+
fixAllDescription: ts.Diagnostics.Add_all_missing_type_annotations.message,
19+
newFileContent:
20+
`import { Volume } from './mymodule';
21+
export const foo: Volume = new Volume();`
22+
});

0 commit comments

Comments
 (0)