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
This commit is contained in:
Nathan Shively-Sanders 2018-10-29 13:23:33 -07:00 committed by GitHub
parent 24febc2445
commit 60efb65931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 210 additions and 37 deletions

View File

@ -13367,12 +13367,12 @@ namespace ts {
case SyntaxKind.BinaryExpression:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
diagnostic = Diagnostics.Member_0_implicitly_has_an_1_type;
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;
break;
case SyntaxKind.Parameter:
diagnostic = (<ParameterDeclaration>declaration).dotDotDotToken ?
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type :
Diagnostics.Parameter_0_implicitly_has_an_1_type;
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 :
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;
break;
case SyntaxKind.BindingElement:
diagnostic = Diagnostics.Binding_element_0_implicitly_has_an_1_type;
@ -13388,7 +13388,7 @@ namespace ts {
error(declaration, Diagnostics.Function_expression_which_lacks_return_type_annotation_implicitly_has_an_0_return_type, typeAsString);
return;
}
diagnostic = Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type;
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;
break;
case SyntaxKind.MappedType:
if (noImplicitAny) {
@ -13396,7 +13396,7 @@ namespace ts {
}
return;
default:
diagnostic = Diagnostics.Variable_0_implicitly_has_an_1_type;
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;
}
errorOrSuggestion(noImplicitAny, declaration, diagnostic, declarationNameToString(getNameOfDeclaration(declaration)), typeAsString);
}

View File

@ -4041,6 +4041,39 @@
"category": "Error",
"code": 7042
},
"Variable '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7043
},
"Parameter '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7044
},
"Member '{0}' implicitly has an '{1}' type, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7045
},
"Variable '{0}' implicitly has type '{1}' in some locations, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7046
},
"Rest parameter '{0}' implicitly has an 'any[]' type, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7047
},
"Property '{0}' implicitly has type 'any', but a better type for its get accessor may be inferred from usage.": {
"category": "Suggestion",
"code": 7048
},
"Property '{0}' implicitly has type 'any', but a better type for its set accessor may be inferred from usage.": {
"category": "Suggestion",
"code": 7049
},
"'{0}' implicitly has an '{1}' return type, but a better type may be inferred from usage.": {
"category": "Suggestion",
"code": 7050
},
"You cannot rename this element.": {
"category": "Error",
"code": 8000

View File

@ -21,6 +21,27 @@ namespace ts.codefix {
// Property declarations
Diagnostics.Member_0_implicitly_has_an_1_type.code,
//// Suggestions
// Variable declarations
Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code,
// Variable uses
Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
// Parameter declarations
Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code,
// Get Accessor declarations
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code,
Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code,
// Set Accessor declarations
Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code,
// Property declarations
Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code,
];
registerCodeFix({
errorCodes,
@ -47,20 +68,46 @@ namespace ts.codefix {
function getDiagnostic(errorCode: number, token: Node): DiagnosticMessage {
switch (errorCode) {
case Diagnostics.Parameter_0_implicitly_has_an_1_type.code:
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
return isSetAccessorDeclaration(getContainingFunction(token)!) ? Diagnostics.Infer_type_of_0_from_usage : Diagnostics.Infer_parameter_types_from_usage; // TODO: GH#18217
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Infer_parameter_types_from_usage;
default:
return Diagnostics.Infer_type_of_0_from_usage;
}
}
/** Map suggestion code to error code */
function mapSuggestionDiagnostic(errorCode: number) {
switch (errorCode) {
case Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Variable_0_implicitly_has_type_1_in_some_locations_where_its_type_cannot_be_determined.code;
case Diagnostics.Variable_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Variable_0_implicitly_has_an_1_type.code;
case Diagnostics.Parameter_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Parameter_0_implicitly_has_an_1_type.code;
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code;
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_get_accessor_may_be_inferred_from_usage.code:
return Diagnostics.Property_0_implicitly_has_type_any_because_its_get_accessor_lacks_a_return_type_annotation.code;
case Diagnostics._0_implicitly_has_an_1_return_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics._0_which_lacks_return_type_annotation_implicitly_has_an_1_return_type.code;
case Diagnostics.Property_0_implicitly_has_type_any_but_a_better_type_for_its_set_accessor_may_be_inferred_from_usage.code:
return Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation.code;
case Diagnostics.Member_0_implicitly_has_an_1_type_but_a_better_type_may_be_inferred_from_usage.code:
return Diagnostics.Member_0_implicitly_has_an_1_type.code;
}
return errorCode;
}
function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, token: Node, errorCode: number, program: Program, cancellationToken: CancellationToken, markSeen: NodeSeenTracker, host: LanguageServiceHost): Declaration | undefined {
if (!isParameterPropertyModifier(token.kind) && token.kind !== SyntaxKind.Identifier && token.kind !== SyntaxKind.DotDotDotToken && token.kind !== SyntaxKind.ThisKeyword) {
return undefined;
}
const { parent } = token;
errorCode = mapSuggestionDiagnostic(errorCode);
switch (errorCode) {
// Variable and Property declarations
case Diagnostics.Member_0_implicitly_has_an_1_type.code:
@ -71,7 +118,7 @@ namespace ts.codefix {
}
if (isPropertyAccessExpression(parent)) {
const type = inferTypeForVariableFromUsage(parent.name, program, cancellationToken);
const typeNode = type && getTypeNodeIfAccessible(type, parent, program, host);
const typeNode = getTypeNodeIfAccessible(type, parent, program, host);
if (typeNode) {
// Note that the codefix will never fire with an existing `@type` tag, so there is no need to merge tags
const typeTag = createJSDocTypeTag(createJSDocTypeExpression(typeNode), /*comment*/ "");
@ -107,7 +154,7 @@ namespace ts.codefix {
case Diagnostics.Rest_parameter_0_implicitly_has_an_any_type.code:
if (markSeen(containingFunction)) {
const param = cast(parent, isParameter);
annotateParameters(changes, param, containingFunction, sourceFile, program, host, cancellationToken);
annotateParameters(changes, sourceFile, param, containingFunction, program, host, cancellationToken);
return param;
}
return undefined;
@ -152,7 +199,7 @@ namespace ts.codefix {
return false;
}
function annotateParameters(changes: textChanges.ChangeTracker, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, sourceFile: SourceFile, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
function annotateParameters(changes: textChanges.ChangeTracker, sourceFile: SourceFile, parameterDeclaration: ParameterDeclaration, containingFunction: FunctionLike, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
if (!isIdentifier(parameterDeclaration.name) || !isApplicableFunctionForInference(containingFunction)) {
return;
}
@ -160,7 +207,7 @@ namespace ts.codefix {
const parameterInferences = inferTypeForParametersFromUsage(containingFunction, sourceFile, program, cancellationToken) ||
containingFunction.parameters.map<ParameterInference>(p => ({
declaration: p,
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : undefined
type: isIdentifier(p.name) ? inferTypeForVariableFromUsage(p.name, program, cancellationToken) : program.getTypeChecker().getAnyType()
}));
Debug.assert(containingFunction.parameters.length === parameterInferences.length);
@ -179,8 +226,10 @@ namespace ts.codefix {
function annotateSetAccessor(changes: textChanges.ChangeTracker, sourceFile: SourceFile, setAccessorDeclaration: SetAccessorDeclaration, program: Program, host: LanguageServiceHost, cancellationToken: CancellationToken): void {
const param = firstOrUndefined(setAccessorDeclaration.parameters);
if (param && isIdentifier(setAccessorDeclaration.name) && isIdentifier(param.name)) {
const type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken) ||
inferTypeForVariableFromUsage(param.name, program, cancellationToken);
let type = inferTypeForVariableFromUsage(setAccessorDeclaration.name, program, cancellationToken);
if (type === program.getTypeChecker().getAnyType()) {
type = inferTypeForVariableFromUsage(param.name, program, cancellationToken);
}
if (isInJSFile(setAccessorDeclaration)) {
annotateJSDocParameters(changes, sourceFile, [{ declaration: param, type }], program, host);
}
@ -190,8 +239,8 @@ namespace ts.codefix {
}
}
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type | undefined, program: Program, host: LanguageServiceHost): void {
const typeNode = type && getTypeNodeIfAccessible(type, declaration, program, host);
function annotate(changes: textChanges.ChangeTracker, sourceFile: SourceFile, declaration: textChanges.TypeAnnotatable, type: Type, program: Program, host: LanguageServiceHost): void {
const typeNode = getTypeNodeIfAccessible(type, declaration, program, host);
if (typeNode) {
if (isInJSFile(sourceFile) && declaration.kind !== SyntaxKind.PropertySignature) {
const parent = isVariableDeclaration(declaration) ? tryCast(declaration.parent.parent, isVariableStatement) : declaration;
@ -285,7 +334,7 @@ namespace ts.codefix {
entry.kind !== FindAllReferences.EntryKind.Span ? tryCast(entry.node, isIdentifier) : undefined);
}
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type | undefined {
function inferTypeForVariableFromUsage(token: Identifier, program: Program, cancellationToken: CancellationToken): Type {
return InferFromReference.inferTypeFromReferences(getReferences(token, program, cancellationToken), program.getTypeChecker(), cancellationToken);
}
@ -307,7 +356,7 @@ namespace ts.codefix {
interface ParameterInference {
readonly declaration: ParameterDeclaration;
readonly type?: Type;
readonly type: Type;
readonly isOptional?: boolean;
}
@ -329,13 +378,13 @@ namespace ts.codefix {
stringIndexContext?: UsageContext;
}
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type | undefined {
export function inferTypeFromReferences(references: ReadonlyArray<Identifier>, checker: TypeChecker, cancellationToken: CancellationToken): Type {
const usageContext: UsageContext = {};
for (const reference of references) {
cancellationToken.throwIfCancellationRequested();
inferTypeFromContext(reference, checker, usageContext);
}
return getTypeFromUsageContext(usageContext, checker);
return getTypeFromUsageContext(usageContext, checker) || checker.getAnyType();
}
export function inferTypeForParametersFromReferences(references: ReadonlyArray<Identifier>, declaration: FunctionLikeDeclaration, checker: TypeChecker, cancellationToken: CancellationToken): ParameterInference[] | undefined {
@ -374,7 +423,7 @@ namespace ts.codefix {
}
}
if (!types.length) {
return { declaration: parameter };
return { declaration: parameter, type: checker.getAnyType() };
}
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
return {

View File

@ -6,10 +6,11 @@
verify.getSuggestionDiagnostics([
{ message: "JSDoc types may be moved to TypeScript types.", code: 80004 },
{ message: "Variable 'x' implicitly has an 'any' type.", code: 7005 }]);
{ message: "Variable 'x' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7043 }]);
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/** @type {number} */
var x: number;`,

View File

@ -8,6 +8,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/**
* @param {?} x

View File

@ -11,6 +11,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/**

View File

@ -17,6 +17,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 9,
newFileContent:
`/**
* @param {Boolean} x

View File

@ -9,6 +9,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/**

View File

@ -6,6 +6,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/** @param {number} value */

View File

@ -11,6 +11,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 2,
newFileContent:
`/**
* @template T

View File

@ -9,6 +9,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 2,
newFileContent:
`
/** @param {Object<string, boolean>} sb

View File

@ -13,13 +13,14 @@
const [r0, r1, r2, r3, r4] = test.ranges();
verify.getSuggestionDiagnostics([
{message: "JSDoc types may be moved to TypeScript types.", code: 80004, range: r0},
{message: "Parameter 'x' implicitly has an 'any' type.", code: 7006, range: r1 },
{message: "Parameter 'y' implicitly has an 'any' type.", code: 7006, range: r2 },
{message: "Parameter 'alpha' implicitly has an 'any' type.", code: 7006, range: r3 },
{message: "Parameter 'beta' implicitly has an 'any' type.", code: 7006, range: r4 }]);
{message: "Parameter 'x' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r1 },
{message: "Parameter 'y' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r2 },
{message: "Parameter 'alpha' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r3 },
{message: "Parameter 'beta' implicitly has an 'any' type, but a better type may be inferred from usage.", code: 7044, range: r4 }]);
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
// TODO: GH#22358
`/**

View File

@ -15,6 +15,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 7,
newFileContent:
`/**
* @param {*} x

View File

@ -7,6 +7,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`declare class C {
/** @type {number | null} */

View File

@ -10,6 +10,7 @@
verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/**
* @param {number} x

View File

@ -9,4 +9,6 @@
//// readonly [prop: K]?: any;
//// }
verify.not.codeFixAvailable()
verify.codeFixAvailable([
{ "description": "Infer type of 'any' from usage" }
])

View File

@ -8,5 +8,6 @@
verify.codeFix({
description: "Add 'this.' to unresolved variable",
index: 0,
newRangeContent: "this.foo = 10",
});

View File

@ -0,0 +1,11 @@
/// <reference path='fourslash.ts' />
// @noImplicitAny: false
////function coll(callback) {
////}
verify.codeFix({
description: "Infer parameter types from usage",
index: 0,
newFileContent:
`function coll(callback: any) {
}`,
});

View File

@ -0,0 +1,18 @@
/// <reference path='fourslash.ts' />
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @noImplicitAny: false
// @Filename: important.js
////function coll(callback) {
////}
verify.codeFix({
description: "Infer parameter types from usage",
index: 0,
newFileContent:
`/**
* @param {any} callback
*/
function coll(callback) {
}`,
});

View File

@ -10,4 +10,4 @@ verify.rangeAfterCodeFix(`var x: number;
function f() {
x++;
}
`, /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 1);
`, /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 1);

View File

@ -0,0 +1,13 @@
/// <reference path='fourslash.ts' />
// @noImplicitAny: false
////[|var x;
////function f() {
//// x++;
////}|]
verify.rangeAfterCodeFix(`var x: number;
function f() {
x++;
}
`, /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />
// @allowJs: true
// @checkJs: true
// @noEmit: true
// @noImplicitAny: false
// @Filename: important.js
////[|function f(foo) {
//// foo += 2
//// return foo
////}|]
verify.rangeAfterCodeFix(`/**
* @param {number} foo
*/
function f(foo) {
foo += 2
return foo
}
`);

View File

@ -7,9 +7,9 @@
const [r0, r1] = test.ranges();
verify.getSuggestionDiagnostics([
{
message: "Parameter 'p' implicitly has an 'any' type.",
message: "Parameter 'p' implicitly has an 'any' type, but a better type may be inferred from usage.",
range: r0,
code: 7006,
code: 7044,
},
{
message: "'p' is declared but its value is never read.",
@ -26,5 +26,9 @@ verify.getSuggestionDiagnostics([
]);
verify.codeFixAvailable(
["Remove declaration for: 'p'", "Prefix 'p' with an underscore", "Remove declaration for: 'x'"]
.map(description => ({ description })));
[
"Infer parameter types from usage",
"Remove declaration for: 'p'",
"Prefix 'p' with an underscore",
"Remove declaration for: 'x'"
].map(description => ({ description })));

View File

@ -6,4 +6,7 @@
////}
////f(
verify.not.codeFixAvailable();
goTo.marker('1');
verify.codeFixAvailable([
{ "description": "Infer parameter types from usage" }
]);

View File

@ -3,4 +3,6 @@
// @noImplicitAny: true
////function f(new C(100, 3, undefined)
verify.codeFixAvailable([]); // Parse error, so no unused diagnostics
verify.codeFixAvailable([
{ "description": "Infer parameter types from usage" }
]); // Parse error, so no unused diagnostics

View File

@ -6,8 +6,8 @@
// Only give suggestions for nodes that do NOT have parse errors
verify.getSuggestionDiagnostics([{
message: "Variable 'd' implicitly has an 'any' type.",
code: 7005,
message: "Variable 'd' implicitly has an 'any' type, but a better type may be inferred from usage.",
code: 7043,
range: {
fileName: "/a.ts",
pos: 23,

View File

@ -11,5 +11,5 @@
verify.rangeAfterCodeFix(`namespace greeter {
export class class2 {
}
}`);
}`, /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@ -6,4 +6,4 @@
//// use(y, z);
////}
verify.rangeAfterCodeFix("var y = 0,z = 1;");
verify.rangeAfterCodeFix("var y = 0,z = 1;", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@ -9,4 +9,4 @@
//// }
////}
verify.rangeAfterCodeFix("var y = 10;");
verify.rangeAfterCodeFix("var y = 10;", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@ -9,4 +9,4 @@
//// }
////}
verify.rangeAfterCodeFix("var y;");
verify.rangeAfterCodeFix("var y;", /*includeWhiteSpace*/ undefined, /*errorCode*/ undefined, 0);

View File

@ -8,5 +8,6 @@
verify.codeFix({
description: "Remove declaration for: 'greeting'",
index: 0,
newRangeContent: "public greeting1;\n",
});

View File

@ -10,5 +10,6 @@
verify.codeFix({
description: "Remove declaration for: 'b'",
index: 0,
newRangeContent: 'let a = "dummy entry", c = 0;',
});

View File

@ -10,5 +10,6 @@
verify.codeFix({
description: "Remove declaration for: 'c'",
index: 0,
newRangeContent: 'let a = "dummy entry", b;',
});