Skip to content

Commit ca94d8e

Browse files
authored
Infer from usage better import types (#27626)
* Use host to improve SymbolTracker implementation * inferFromUsage: Provide a better moduleResolverHost This produces better paths on import types.
1 parent f6ca105 commit ca94d8e

File tree

2 files changed

+30
-21
lines changed

2 files changed

+30
-21
lines changed

src/services/codefixes/inferFromUsage.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,24 @@ namespace ts.codefix {
2525
registerCodeFix({
2626
errorCodes,
2727
getCodeActions(context) {
28-
const { sourceFile, program, span: { start }, errorCode, cancellationToken } = context;
28+
const { sourceFile, program, span: { start }, errorCode, cancellationToken, host } = context;
2929
if (isSourceFileJS(sourceFile)) {
3030
return undefined; // TODO: GH#20113
3131
}
3232

3333
const token = getTokenAtPosition(sourceFile, start);
3434
let declaration!: Declaration | undefined;
35-
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeenseen*/ returnTrue); });
35+
const changes = textChanges.ChangeTracker.with(context, changes => { declaration = doChange(changes, sourceFile, token, errorCode, program, cancellationToken, /*markSeen*/ returnTrue, host); });
3636
const name = declaration && getNameOfDeclaration(declaration);
3737
return !name || changes.length === 0 ? undefined
3838
: [createCodeFixAction(fixId, changes, [getDiagnostic(errorCode, token), name.getText(sourceFile)], fixId, Diagnostics.Infer_all_types_from_usage)];
3939
},
4040
fixIds: [fixId],
4141
getAllCodeActions(context) {
42-
const { sourceFile, program, cancellationToken } = context;
42+
const { sourceFile, program, cancellationToken, host } = context;
4343
const markSeen = nodeSeenTracker();
4444
return codeFixAll(context, errorCodes, (changes, err) => {
45-
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen);
45+
doChange(changes, sourceFile, getTokenAtPosition(err.file, err.start), err.code, program, cancellationToken, markSeen, host);
4646
});
4747
},
4848
});
@@ -58,7 +58,7 @@ namespace ts.codefix {
5858
}
5959
}
6060

61-
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker): Declaration | undefined {
61+
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined {
6262
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken) {
6363
return undefined;
6464
}
@@ -69,15 +69,15 @@ namespace ts.codefix {
6969
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
7070
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code:
7171
if ((isVariableDeclaration(parent) && markSeen(parent)) || isPropertyDeclaration(parent) || isPropertySignature(parent)) { // handle bad location
72-
annotateVariableDeclaration(changes, sourceFile, parent, program, cancellationToken);
72+
annotateVariableDeclaration(changes, sourceFile, parent, program, host, cancellationToken);
7373
return parent;
7474
}
7575
return undefined;
7676

7777
case Diagnostics.Variable_0_implicitly_has_an_1_type.code: {
7878
const symbol = program.getTypeChecker().getSymbolAtLocation(token);
7979
if (symbol && symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && markSeen(symbol.valueDeclaration)) {
80-
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, cancellationToken);
80+
annotateVariableDeclaration(changes, sourceFile, symbol.valueDeclaration, program, host, cancellationToken);
8181
return symbol.valueDeclaration;
8282
}
8383
return undefined;
@@ -93,14 +93,14 @@ namespace ts.codefix {
9393
// Parameter declarations
9494
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
9595
if (isSetAccessor(containingFunction)) {
96-
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
96+
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
9797
return containingFunction;
9898
}
9999
// falls through
100100
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
101101
if (markSeen(containingFunction)) {
102102
const param = cast(parent, isParameter);
103-
annotateParameters(changes, param, containingFunction, sourceFile, program, cancellationToken);
103+
annotateParameters(changes, param, containingFunction, sourceFile, program, host, cancellationToken);
104104
return param;
105105
}
106106
return undefined;
@@ -109,15 +109,15 @@ namespace ts.codefix {
109109
case Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code:
110110
case Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code:
111111
if (isGetAccessor(containingFunction) && isIdentifier(containingFunction.name)) {
112-
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program);
112+
annotate(changes, sourceFile, containingFunction, inferTypeForVariableFromUsage(containingFunction.name, program, cancellationToken), program, host);
113113
return containingFunction;
114114
}
115115
return undefined;
116116

117117
// Set Accessor declarations
118118
case Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code:
119119
if (isSetAccessor(containingFunction)) {
120-
annotateSetAccessor(changes, sourceFile, containingFunction, program, cancellationToken);
120+
annotateSetAccessor(changes, sourceFile, containingFunction, program, host, cancellationToken);
121121
return containingFunction;
122122
}
123123
return undefined;
@@ -127,9 +127,9 @@ namespace ts.codefix {
127127
}
128128
}
129129

130-
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, cancellationToken: CancellationToken): void {
130+
function annotateVariableDeclaration(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: VariableDeclaration | PropertyDeclaration | PropertySignature, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
131131
if (isIdentifier(declaration.name)) {
132-
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program);
132+
annotate(changes, sourceFile, declaration, inferTypeForVariableFromUsage(declaration.name, program, cancellationToken), program, host);
133133
}
134134
}
135135

@@ -145,7 +145,7 @@ namespace ts.codefix {
145145
return false;
146146
}
147147

148-
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): void {
148+
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
149149
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
150150
return;
151151
}
@@ -159,26 +159,27 @@ namespace ts.codefix {
159159

160160
zipWith(containingFunction.parameters, types, (parameter, type) => {
161161
if (!parameter.type && !parameter.initializer) {
162-
annotate(changes, sourceFile, parameter, type, program);
162+
annotate(changes, sourceFile, parameter, type, program, host);
163163
}
164164
});
165165
}
166166

167-
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, cancellationToken: CancellationToken): void {
167+
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
168168
const param = firstOrUndefined(setAccessorDeclaration.parameters);
169169
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
170170
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) ||
171171
inferTypeForVariableFromUsage(param.name, program, cancellationToken);
172-
annotate(changes, sourceFile, param, type, program);
172+
annotate(changes, sourceFile, param, type, program, host);
173173
}
174174
}
175175

176-
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program): void {
177-
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program.getTypeChecker());
176+
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program, host: LanguageServiceHost): void {
177+
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program, host);
178178
if (typeNode) changes.tryInsertTypeAnnotation(sourceFile, declaration, typeNode);
179179
}
180180

181-
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, checker: TypeChecker): TypeNode | undefined {
181+
function getTypeNodeIfAccessible(type: Type, enclosingScope: Node, program: Program, host: LanguageServiceHost): TypeNode | undefined {
182+
const checker = program.getTypeChecker();
182183
let typeIsAccessible = true;
183184
const notAccessible = () => { typeIsAccessible = false; };
184185
const res = checker.typeToTypeNode(type, enclosingScope, /*flags*/ undefined, {
@@ -189,6 +190,14 @@ namespace ts.codefix {
189190
reportInaccessibleThisError: notAccessible,
190191
reportPrivateInBaseOfClassExpression: notAccessible,
191192
reportInaccessibleUniqueSymbolError: notAccessible,
193+
moduleResolverHost: {
194+
readFile: host.readFile,
195+
fileExists: host.fileExists,
196+
directoryExists: host.directoryExists,
197+
getSourceFiles: program.getSourceFiles,
198+
getCurrentDirectory: program.getCurrentDirectory,
199+
getCommonSourceDirectory: program.getCommonSourceDirectory,
200+
}
192201
});
193202
return typeIsAccessible ? res : undefined;
194203
}

tests/cases/fourslash/codeFixInferFromUsageSetterWithInaccessibleType.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ verify.codeFix({
1818
description: "Infer type of 'x' from usage",
1919
newFileContent:
2020
`export class C {
21-
set x(val: Promise<typeof import("/a")>) { val; }
21+
set x(val: Promise<typeof import("./a")>) { val; }
2222
method() { this.x = import("./a"); }
2323
}`,
2424
});

0 commit comments

Comments
 (0)