Skip to content

Commit

Permalink
infer-from-usage suggestions can't be ignored, and always do somethin…
Browse files Browse the repository at this point in the history
…g when invoked. (microsoft#28206)

* Do not ts-ignore noImplicitAny suggestions

Still need to write tests.

* Add tests

* More tests

* Update baselines
  • Loading branch information
sandersn authored Oct 29, 2018
1 parent 24febc2 commit 60efb65
Show file tree
Hide file tree
Showing 33 changed files with 210 additions and 37 deletions.
10 changes: 5 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13388,15 +13388,15 @@ 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) {
error(declaration, Diagnostics.Mapped_object_type_implicitly_has_an_any_template_type);
}
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);
}
Expand Down
33 changes: 33 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 62 additions & 13 deletions src/services/codefixes/inferFromUsage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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*/ "");
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -152,15 +199,15 @@ 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;
}

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);

Expand All @@ -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);
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

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

interface ParameterInference {
readonly declaration: ParameterDeclaration;
readonly type?: Type;
readonly type: Type;
readonly isOptional?: boolean;
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion tests/cases/fourslash/annotateWithTypeFromJSDoc1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;`,
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc10.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/**
* @param {?} x
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc12.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/**
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc15.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 9,
newFileContent:
`/**
* @param {Boolean} x
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc17.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/**
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc18.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`class C {
/** @param {number} value */
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc19.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 2,
newFileContent:
`/**
* @template T
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc22.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 2,
newFileContent:
`
/** @param {Object<string, boolean>} sb
Expand Down
9 changes: 5 additions & 4 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
`/**
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 7,
newFileContent:
`/**
* @param {*} x
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`declare class C {
/** @type {number | null} */
Expand Down
1 change: 1 addition & 0 deletions tests/cases/fourslash/annotateWithTypeFromJSDoc7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

verify.codeFix({
description: "Annotate with type from JSDoc",
index: 0,
newFileContent:
`/**
* @param {number} x
Expand Down
4 changes: 3 additions & 1 deletion tests/cases/fourslash/codeFixConvertToMappedObjectType12.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
//// readonly [prop: K]?: any;
//// }

verify.not.codeFixAvailable()
verify.codeFixAvailable([
{ "description": "Infer type of 'any' from usage" }
])
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

verify.codeFix({
description: "Add 'this.' to unresolved variable",
index: 0,
newRangeContent: "this.foo = 10",
});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/codeFixInferFromUsageAlwaysInfer.ts
Original file line number Diff line number Diff line change
@@ -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) {
}`,
});
Loading

0 comments on commit 60efb65

Please sign in to comment.