Skip to content

Commit 6cee7c3

Browse files
authored
Better nameless parameter implicit any error (microsoft#28554)
* Initial version, doesn't work for primitives yet. Need to find out why. * Primitives now work, plus improve error message null and void don't even parse without parameter names so they are not tested. * Codefix: Add names to nameless parameters * Improve error wording * Add detail to error message
1 parent 079f043 commit 6cee7c3

12 files changed

+205
-25
lines changed

src/compiler/checker.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13629,6 +13629,16 @@ namespace ts {
1362913629
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;
1363013630
break;
1363113631
case SyntaxKind.Parameter:
13632+
const param = declaration as ParameterDeclaration;
13633+
if (isIdentifier(param.name) &&
13634+
(isCallSignatureDeclaration(param.parent) || isMethodSignature(param.parent) || isFunctionTypeNode(param.parent)) &&
13635+
param.parent.parameters.indexOf(param) > -1 &&
13636+
(resolveName(param, param.name.escapedText, SymbolFlags.Type, undefined, param.name.escapedText, /*isUse*/ true) ||
13637+
param.name.originalKeywordKind && isTypeNodeKind(param.name.originalKeywordKind))) {
13638+
const newName = "arg" + param.parent.parameters.indexOf(param);
13639+
errorOrSuggestion(noImplicitAny, declaration, Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1, newName, declarationNameToString(param.name));
13640+
return;
13641+
}
1363213642
diagnostic = (<ParameterDeclaration>declaration).dotDotDotToken ?
1363313643
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 :
1363413644
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;

src/compiler/diagnosticMessages.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4125,6 +4125,10 @@
41254125
"category": "Suggestion",
41264126
"code": 7050
41274127
},
4128+
"Parameter has a name but no type. Did you mean '{0}: {1}'?": {
4129+
"category": "Error",
4130+
"code": 7051
4131+
},
41284132

41294133
"You cannot rename this element.": {
41304134
"category": "Error",
@@ -4508,6 +4512,10 @@
45084512
"category": "Message",
45094513
"code": 90033
45104514
},
4515+
"Add parameter name": {
4516+
"category": "Message",
4517+
"code": 90034
4518+
},
45114519
"Convert function to an ES2015 class": {
45124520
"category": "Message",
45134521
"code": 95001
@@ -4787,5 +4795,9 @@
47874795
"Add missing 'new' operator to all calls": {
47884796
"category": "Message",
47894797
"code": 95072
4798+
},
4799+
"Add names to all parameters without names": {
4800+
"category": "Message",
4801+
"code": 95073
47904802
}
47914803
}

src/compiler/utilities.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4566,6 +4566,31 @@ namespace ts {
45664566
export function isObjectTypeDeclaration(node: Node): node is ObjectTypeDeclaration {
45674567
return isClassLike(node) || isInterfaceDeclaration(node) || isTypeLiteralNode(node);
45684568
}
4569+
4570+
export function isTypeNodeKind(kind: SyntaxKind) {
4571+
return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode)
4572+
|| kind === SyntaxKind.AnyKeyword
4573+
|| kind === SyntaxKind.UnknownKeyword
4574+
|| kind === SyntaxKind.NumberKeyword
4575+
|| kind === SyntaxKind.BigIntKeyword
4576+
|| kind === SyntaxKind.ObjectKeyword
4577+
|| kind === SyntaxKind.BooleanKeyword
4578+
|| kind === SyntaxKind.StringKeyword
4579+
|| kind === SyntaxKind.SymbolKeyword
4580+
|| kind === SyntaxKind.ThisKeyword
4581+
|| kind === SyntaxKind.VoidKeyword
4582+
|| kind === SyntaxKind.UndefinedKeyword
4583+
|| kind === SyntaxKind.NullKeyword
4584+
|| kind === SyntaxKind.NeverKeyword
4585+
|| kind === SyntaxKind.ExpressionWithTypeArguments
4586+
|| kind === SyntaxKind.JSDocAllType
4587+
|| kind === SyntaxKind.JSDocUnknownType
4588+
|| kind === SyntaxKind.JSDocNullableType
4589+
|| kind === SyntaxKind.JSDocNonNullableType
4590+
|| kind === SyntaxKind.JSDocOptionalType
4591+
|| kind === SyntaxKind.JSDocFunctionType
4592+
|| kind === SyntaxKind.JSDocVariadicType;
4593+
}
45694594
}
45704595

45714596
namespace ts {
@@ -6289,31 +6314,6 @@ namespace ts {
62896314

62906315
// Type
62916316

6292-
function isTypeNodeKind(kind: SyntaxKind) {
6293-
return (kind >= SyntaxKind.FirstTypeNode && kind <= SyntaxKind.LastTypeNode)
6294-
|| kind === SyntaxKind.AnyKeyword
6295-
|| kind === SyntaxKind.UnknownKeyword
6296-
|| kind === SyntaxKind.NumberKeyword
6297-
|| kind === SyntaxKind.BigIntKeyword
6298-
|| kind === SyntaxKind.ObjectKeyword
6299-
|| kind === SyntaxKind.BooleanKeyword
6300-
|| kind === SyntaxKind.StringKeyword
6301-
|| kind === SyntaxKind.SymbolKeyword
6302-
|| kind === SyntaxKind.ThisKeyword
6303-
|| kind === SyntaxKind.VoidKeyword
6304-
|| kind === SyntaxKind.UndefinedKeyword
6305-
|| kind === SyntaxKind.NullKeyword
6306-
|| kind === SyntaxKind.NeverKeyword
6307-
|| kind === SyntaxKind.ExpressionWithTypeArguments
6308-
|| kind === SyntaxKind.JSDocAllType
6309-
|| kind === SyntaxKind.JSDocUnknownType
6310-
|| kind === SyntaxKind.JSDocNullableType
6311-
|| kind === SyntaxKind.JSDocNonNullableType
6312-
|| kind === SyntaxKind.JSDocOptionalType
6313-
|| kind === SyntaxKind.JSDocFunctionType
6314-
|| kind === SyntaxKind.JSDocVariadicType;
6315-
}
6316-
63176317
/**
63186318
* Node test that determines whether a node is a valid type node.
63196319
* This differs from the `isPartOfTypeNode` function which determines whether a node is *part*
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
const fixId = "addNameToNamelessParameter";
4+
const errorCodes = [Diagnostics.Parameter_has_a_name_but_no_type_Did_you_mean_0_Colon_1.code];
5+
registerCodeFix({
6+
errorCodes,
7+
getCodeActions: (context) => {
8+
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span.start));
9+
return [createCodeFixAction(fixId, changes, Diagnostics.Add_parameter_name, fixId, Diagnostics.Add_names_to_all_parameters_without_names)];
10+
},
11+
fixIds: [fixId],
12+
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start)),
13+
});
14+
15+
function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number) {
16+
const token = getTokenAtPosition(sourceFile, pos);
17+
if (!isIdentifier(token)) {
18+
return Debug.fail("add-name-to-nameless-parameter operates on identifiers, but got a " + formatSyntaxKind(token.kind));
19+
}
20+
const param = token.parent;
21+
if (!isParameter(param)) {
22+
return Debug.fail("Tried to add a parameter name to a non-parameter: " + formatSyntaxKind(token.kind));
23+
}
24+
const i = param.parent.parameters.indexOf(param);
25+
Debug.assert(!param.type, "Tried to add a parameter name to a parameter that already had one.");
26+
Debug.assert(i > -1, "Parameter not found in parent parameter list.");
27+
const replacement = createParameter(
28+
/*decorators*/ undefined,
29+
param.modifiers,
30+
param.dotDotDotToken,
31+
"arg" + i,
32+
param.questionToken,
33+
createTypeReferenceNode(token, /*typeArguments*/ undefined),
34+
param.initializer);
35+
changeTracker.replaceNode(sourceFile, token, replacement);
36+
}
37+
}

src/services/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"refactorProvider.ts",
4646
"codefixes/addConvertToUnknownForNonOverlappingTypes.ts",
4747
"codefixes/addMissingInvocationForDecorator.ts",
48+
"codefixes/addNameToNamelessParameter.ts",
4849
"codefixes/annotateWithTypeFromJSDoc.ts",
4950
"codefixes/inferFromUsage.ts",
5051
"codefixes/convertFunctionToEs6Class.ts",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(2,17): error TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?
2+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(2,25): error TS7051: Parameter has a name but no type. Did you mean 'arg1: C'?
3+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(3,19): error TS7051: Parameter has a name but no type. Did you mean 'arg0: C'?
4+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(3,22): error TS7051: Parameter has a name but no type. Did you mean 'arg1: number'?
5+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(4,20): error TS7051: Parameter has a name but no type. Did you mean 'arg0: boolean'?
6+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(4,29): error TS7051: Parameter has a name but no type. Did you mean 'arg1: C'?
7+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(4,32): error TS7051: Parameter has a name but no type. Did you mean 'arg2: object'?
8+
tests/cases/compiler/noImplicitAnyNamelessParameter.ts(4,40): error TS7051: Parameter has a name but no type. Did you mean 'arg3: undefined'?
9+
10+
11+
==== tests/cases/compiler/noImplicitAnyNamelessParameter.ts (8 errors) ====
12+
class C { }
13+
declare var x: (string, C) => void;
14+
~~~~~~
15+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?
16+
~
17+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg1: C'?
18+
declare var y: { (C, number): void };
19+
~
20+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg0: C'?
21+
~~~~~~
22+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg1: number'?
23+
declare var z: { m(boolean, C, object, undefined): void }
24+
~~~~~~~
25+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg0: boolean'?
26+
~
27+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg1: C'?
28+
~~~~~~
29+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg2: object'?
30+
~~~~~~~~~
31+
!!! error TS7051: Parameter has a name but no type. Did you mean 'arg3: undefined'?
32+
// note: null and void do not parse correctly without a preceding parameter name
33+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//// [noImplicitAnyNamelessParameter.ts]
2+
class C { }
3+
declare var x: (string, C) => void;
4+
declare var y: { (C, number): void };
5+
declare var z: { m(boolean, C, object, undefined): void }
6+
// note: null and void do not parse correctly without a preceding parameter name
7+
8+
9+
//// [noImplicitAnyNamelessParameter.js]
10+
var C = /** @class */ (function () {
11+
function C() {
12+
}
13+
return C;
14+
}());
15+
// note: null and void do not parse correctly without a preceding parameter name
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/noImplicitAnyNamelessParameter.ts ===
2+
class C { }
3+
>C : Symbol(C, Decl(noImplicitAnyNamelessParameter.ts, 0, 0))
4+
5+
declare var x: (string, C) => void;
6+
>x : Symbol(x, Decl(noImplicitAnyNamelessParameter.ts, 1, 11))
7+
>string : Symbol(string, Decl(noImplicitAnyNamelessParameter.ts, 1, 16))
8+
>C : Symbol(C, Decl(noImplicitAnyNamelessParameter.ts, 1, 23))
9+
10+
declare var y: { (C, number): void };
11+
>y : Symbol(y, Decl(noImplicitAnyNamelessParameter.ts, 2, 11))
12+
>C : Symbol(C, Decl(noImplicitAnyNamelessParameter.ts, 2, 18))
13+
>number : Symbol(number, Decl(noImplicitAnyNamelessParameter.ts, 2, 20))
14+
15+
declare var z: { m(boolean, C, object, undefined): void }
16+
>z : Symbol(z, Decl(noImplicitAnyNamelessParameter.ts, 3, 11))
17+
>m : Symbol(m, Decl(noImplicitAnyNamelessParameter.ts, 3, 16))
18+
>boolean : Symbol(boolean, Decl(noImplicitAnyNamelessParameter.ts, 3, 19))
19+
>C : Symbol(C, Decl(noImplicitAnyNamelessParameter.ts, 3, 27))
20+
>object : Symbol(object, Decl(noImplicitAnyNamelessParameter.ts, 3, 30))
21+
>undefined : Symbol(undefined, Decl(noImplicitAnyNamelessParameter.ts, 3, 38))
22+
23+
// note: null and void do not parse correctly without a preceding parameter name
24+
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
=== tests/cases/compiler/noImplicitAnyNamelessParameter.ts ===
2+
class C { }
3+
>C : C
4+
5+
declare var x: (string, C) => void;
6+
>x : (string: any, C: any) => void
7+
>string : any
8+
>C : any
9+
10+
declare var y: { (C, number): void };
11+
>y : (C: any, number: any) => void
12+
>C : any
13+
>number : any
14+
15+
declare var z: { m(boolean, C, object, undefined): void }
16+
>z : { m(boolean: any, C: any, object: any, undefined: any): void; }
17+
>m : (boolean: any, C: any, object: any, undefined: any) => void
18+
>boolean : any
19+
>C : any
20+
>object : any
21+
>undefined : any
22+
23+
// note: null and void do not parse correctly without a preceding parameter name
24+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @noImplicitAny: true
2+
class C { }
3+
declare var x: (string, C) => void;
4+
declare var y: { (C, number): void };
5+
declare var z: { m(boolean, C, object, undefined): void }
6+
// note: null and void do not parse correctly without a preceding parameter name

0 commit comments

Comments
 (0)