Skip to content

Commit 60efb65

Browse files
authored
infer-from-usage suggestions can't be ignored, and always do something when invoked. (#28206)
* Do not ts-ignore noImplicitAny suggestions Still need to write tests. * Add tests * More tests * Update baselines
1 parent 24febc2 commit 60efb65

33 files changed

+210
-37
lines changed

src/compiler/checker.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13367,12 +13367,12 @@ namespace ts {
1336713367
case SyntaxKind.BinaryExpression:
1336813368
case SyntaxKind.PropertyDeclaration:
1336913369
case SyntaxKind.PropertySignature:
13370-
diagnostic = Diagnostics.Member_0_implicitly_has_an_1_type;
13370+
diagnostic = noImplicitAny ? Diagnostics.Member_0_implicitly_has_an_1_type : Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
1337113371
break;
1337213372
case SyntaxKind.Parameter:
1337313373
diagnostic = (<ParameterDeclaration>declaration).dotDotDotToken ?
13374-
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type :
13375-
Diagnostics.Parameter_0_implicitly_has_an_1_type;
13374+
noImplicitAny ? Diagnostics.Rest_parameter_0_implicitly_has_an_any_type : Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage :
13375+
noImplicitAny ? Diagnostics.Parameter_0_implicitly_has_an_1_type : Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
1337613376
break;
1337713377
case SyntaxKind.BindingElement:
1337813378
diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type;
@@ -13388,15 +13388,15 @@ namespace ts {
1338813388
error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
1338913389
return;
1339013390
}
13391-
diagnostic = Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type;
13391+
diagnostic = noImplicitAny ? Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type : Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage;
1339213392
break;
1339313393
case SyntaxKind.MappedType:
1339413394
if (noImplicitAny) {
1339513395
error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type);
1339613396
}
1339713397
return;
1339813398
default:
13399-
diagnostic = Diagnostics.Variable_0_implicitly_has_an_1_type;
13399+
diagnostic = noImplicitAny ? Diagnostics.Variable_0_implicitly_has_an_1_type : Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage;
1340013400
}
1340113401
errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString);
1340213402
}

src/compiler/diagnosticMessages.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4041,6 +4041,39 @@
40414041
"category": "Error",
40424042
"code": 7042
40434043
},
4044+
"Variable '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
4045+
"category": "Suggestion",
4046+
"code": 7043
4047+
},
4048+
"Parameter '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
4049+
"category": "Suggestion",
4050+
"code": 7044
4051+
},
4052+
"Member '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
4053+
"category": "Suggestion",
4054+
"code": 7045
4055+
},
4056+
"Variable '{0}' implicitly has type '{1}' in some locations, but a better type may be inferred from usage.": {
4057+
"category": "Suggestion",
4058+
"code": 7046
4059+
},
4060+
"Rest parameter '{0}' implicitly has an 'any[]' type, but a better type may be inferred from usage.": {
4061+
"category": "Suggestion",
4062+
"code": 7047
4063+
},
4064+
"Property '{0}' implicitly has type 'any', but a better type for its get accessor may be inferred from usage.": {
4065+
"category": "Suggestion",
4066+
"code": 7048
4067+
},
4068+
"Property '{0}' implicitly has type 'any', but a better type for its set accessor may be inferred from usage.": {
4069+
"category": "Suggestion",
4070+
"code": 7049
4071+
},
4072+
"'{0}' implicitly has an '{1}' return type, but a better type may be inferred from usage.": {
4073+
"category": "Suggestion",
4074+
"code": 7050
4075+
},
4076+
40444077
"You cannot rename this element.": {
40454078
"category": "Error",
40464079
"code": 8000

src/services/codefixes/inferFromUsage.ts

Lines changed: 62 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,27 @@ namespace ts.codefix {
2121

2222
// Property declarations
2323
Diagnostics.Member_0_implicitly_has_an_1_type.code,
24+
25+
//// Suggestions
26+
// Variable declarations
27+
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code,
28+
29+
// Variable uses
30+
Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
31+
32+
// Parameter declarations
33+
Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
34+
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code,
35+
36+
// Get Accessor declarations
37+
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code,
38+
Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code,
39+
40+
// Set Accessor declarations
41+
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code,
42+
43+
// Property declarations
44+
Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
2445
];
2546
registerCodeFix({
2647
errorCodes,
@@ -47,20 +68,46 @@ namespace ts.codefix {
4768
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
4869
switch (errorCode) {
4970
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
71+
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
5072
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
5173
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
74+
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
5275
return Diagnostics.Infer_parameter_types_from_usage;
5376
default:
5477
return Diagnostics.Infer_type_of_0_from_usage;
5578
}
5679
}
5780

81+
/** Map suggestion code to error code */
82+
function mapSuggestionDiagnostic(errorCode: number) {
83+
switch (errorCode) {
84+
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code:
85+
return Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code;
86+
case Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
87+
return Diagnostics.Variable_0_implicitly_has_an_1_type.code;
88+
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
89+
return Diagnostics.Parameter_0_implicitly_has_an_1_type.code;
90+
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
91+
return Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code;
92+
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code:
93+
return Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code;
94+
case Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code:
95+
return Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code;
96+
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code:
97+
return Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code;
98+
case Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
99+
return Diagnostics.Member_0_implicitly_has_an_1_type.code;
100+
}
101+
return errorCode;
102+
}
103+
58104
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined {
59105
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
60106
return undefined;
61107
}
62108

63109
const { parent } = token;
110+
errorCode = mapSuggestionDiagnostic(errorCode);
64111
switch (errorCode) {
65112
// Variable and Property declarations
66113
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
@@ -71,7 +118,7 @@ namespace ts.codefix {
71118
}
72119
if (isPropertyAccessExpression(parent)) {
73120
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
74-
const typeNode = type && getTypeNodeIfAccessible(type, parent, program, host);
121+
const typeNode = getTypeNodeIfAccessible(type, parent, program, host);
75122
if (typeNode) {
76123
// Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags
77124
const typeTag = createJSDocTypeTag(createJSDocTypeExpression(typeNode), /*comment*/ "");
@@ -107,7 +154,7 @@ namespace ts.codefix {
107154
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
108155
if (markSeen(containingFunction)) {
109156
const param = cast(parent, isParameter);
110-
annotateParameters(changes, param, containingFunction, sourceFile, program, host, cancellationToken);
157+
annotateParameters(changes, sourceFile, param, containingFunction, program, host, cancellationToken);
111158
return param;
112159
}
113160
return undefined;
@@ -152,15 +199,15 @@ namespace ts.codefix {
152199
return false;
153200
}
154201

155-
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
202+
function annotateParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
156203
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
157204
return;
158205
}
159206

160207
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
161208
containingFunction.parameters.map<ParameterInference>(p => ({
162209
declaration: p,
163-
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined
210+
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType()
164211
}));
165212
Debug.assert(containingFunction.parameters.length === parameterInferences.length);
166213

@@ -179,8 +226,10 @@ namespace ts.codefix {
179226
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
180227
const param = firstOrUndefined(setAccessorDeclaration.parameters);
181228
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
182-
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) ||
183-
inferTypeForVariableFromUsage(param.name, program, cancellationToken);
229+
let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken);
230+
if (type === program.getTypeChecker().getAnyType()) {
231+
type = inferTypeForVariableFromUsage(param.name, program, cancellationToken);
232+
}
184233
if (isInJSFile(setAccessorDeclaration)) {
185234
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
186235
}
@@ -190,8 +239,8 @@ namespace ts.codefix {
190239
}
191240
}
192241

193-
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program, host: LanguageServiceHost): void {
194-
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program, host);
242+
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
243+
const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
195244
if (typeNode) {
196245
if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
197246
const parent = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : declaration;
@@ -285,7 +334,7 @@ namespace ts.codefix {
285334
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
286335
}
287336

288-
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type | undefined {
337+
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type {
289338
return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken);
290339
}
291340

@@ -307,7 +356,7 @@ namespace ts.codefix {
307356

308357
interface ParameterInference {
309358
readonly declaration: ParameterDeclaration;
310-
readonly type?: Type;
359+
readonly type: Type;
311360
readonly isOptional?: boolean;
312361
}
313362

@@ -329,13 +378,13 @@ namespace ts.codefix {
329378
stringIndexContext?: UsageContext;
330379
}
331380

332-
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type | undefined {
381+
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type {
333382
const usageContext: UsageContext = {};
334383
for (const reference of references) {
335384
cancellationToken.throwIfCancellationRequested();
336385
inferTypeFromContext(reference, checker, usageContext);
337386
}
338-
return getTypeFromUsageContext(usageContext, checker);
387+
return getTypeFromUsageContext(usageContext, checker) || checker.getAnyType();
339388
}
340389

341390
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): ParameterInference[] | undefined {
@@ -374,7 +423,7 @@ namespace ts.codefix {
374423
}
375424
}
376425
if (!types.length) {
377-
return { declaration: parameter };
426+
return { declaration: parameter, type: checker.getAnyType() };
378427
}
379428
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
380429
return {

tests/cases/fourslash/annotateWithTypeFromJSDoc1.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66

77
verify.getSuggestionDiagnostics([
88
{ message: "JSDoc types may be moved to TypeScript types.", code: 80004 },
9-
{ message: "Variable 'x' implicitly has an 'any' type.", code: 7005 }]);
9+
{ message: "Variable 'x' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7043 }]);
1010

1111
verify.codeFix({
1212
description: "Annotate with type from JSDoc",
13+
index: 0,
1314
newFileContent:
1415
`/** @type {number} */
1516
var x: number;`,

tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
verify.codeFix({
1010
description: "Annotate with type from JSDoc",
11+
index: 0,
1112
newFileContent:
1213
`/**
1314
* @param {?} x

tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
verify.codeFix({
1313
description: "Annotate with type from JSDoc",
14+
index: 0,
1415
newFileContent:
1516
`class C {
1617
/**

tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
verify.codeFix({
1919
description: "Annotate with type from JSDoc",
20+
index: 9,
2021
newFileContent:
2122
`/**
2223
* @param {Boolean} x

tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
verify.codeFix({
1111
description: "Annotate with type from JSDoc",
12+
index: 0,
1213
newFileContent:
1314
`class C {
1415
/**

tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
verify.codeFix({
88
description: "Annotate with type from JSDoc",
9+
index: 0,
910
newFileContent:
1011
`class C {
1112
/** @param {number} value */

tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
verify.codeFix({
1313
description: "Annotate with type from JSDoc",
14+
index: 2,
1415
newFileContent:
1516
`/**
1617
* @template T

tests/cases/fourslash/annotateWithTypeFromJSDoc22.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
verify.codeFix({
1111
description: "Annotate with type from JSDoc",
12+
index: 2,
1213
newFileContent:
1314
`
1415
/** @param {Object<string, boolean>} sb

tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
const [r0, r1, r2, r3, r4] = test.ranges();
1414
verify.getSuggestionDiagnostics([
1515
{message: "JSDoc types may be moved to TypeScript types.", code: 80004, range: r0},
16-
{message: "Parameter 'x' implicitly has an 'any' type.", code: 7006, range: r1 },
17-
{message: "Parameter 'y' implicitly has an 'any' type.", code: 7006, range: r2 },
18-
{message: "Parameter 'alpha' implicitly has an 'any' type.", code: 7006, range: r3 },
19-
{message: "Parameter 'beta' implicitly has an 'any' type.", code: 7006, range: r4 }]);
16+
{message: "Parameter 'x' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r1 },
17+
{message: "Parameter 'y' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r2 },
18+
{message: "Parameter 'alpha' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r3 },
19+
{message: "Parameter 'beta' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r4 }]);
2020

2121
verify.codeFix({
2222
description: "Annotate with type from JSDoc",
23+
index: 0,
2324
newFileContent:
2425
// TODO: GH#22358
2526
`/**

tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
verify.codeFix({
1717
description: "Annotate with type from JSDoc",
18+
index: 7,
1819
newFileContent:
1920
`/**
2021
* @param {*} x

tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
verify.codeFix({
99
description: "Annotate with type from JSDoc",
10+
index: 0,
1011
newFileContent:
1112
`declare class C {
1213
/** @type {number | null} */

tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
verify.codeFix({
1212
description: "Annotate with type from JSDoc",
13+
index: 0,
1314
newFileContent:
1415
`/**
1516
* @param {number} x

tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@
99
//// readonly [prop: K]?: any;
1010
//// }
1111

12-
verify.not.codeFixAvailable()
12+
verify.codeFixAvailable([
13+
{ "description": "Infer type of 'any' from usage" }
14+
])

tests/cases/fourslash/codeFixForgottenThisPropertyAccess02.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88

99
verify.codeFix({
1010
description: "Add 'this.' to unresolved variable",
11+
index: 0,
1112
newRangeContent: "this.foo = 10",
1213
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/// <reference path='fourslash.ts' />
2+
// @noImplicitAny: false
3+
////function coll(callback) {
4+
////}
5+
verify.codeFix({
6+
description: "Infer parameter types from usage",
7+
index: 0,
8+
newFileContent:
9+
`function coll(callback: any) {
10+
}`,
11+
});

0 commit comments

Comments
 (0)