Better errors for indexing gettable/settable values (#26446)
* give suggestions when index signature given * add tests for noImplicitAny indexing on Object * remove comments regarding error messages * recommend set if el is on RHS of assignment else get * add new baseline tests
This commit is contained in:
parent
3ce3cde493
commit
7016d45447
8 changed files with 289 additions and 19 deletions
|
@ -10117,7 +10117,13 @@ namespace ts {
|
|||
}
|
||||
}
|
||||
else {
|
||||
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType));
|
||||
const suggestion = getSuggestionForNonexistentIndexSignature(objectType, accessExpression);
|
||||
if (suggestion !== undefined) {
|
||||
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature_Did_you_mean_to_call_1, typeToString(objectType), suggestion);
|
||||
}
|
||||
else {
|
||||
error(accessExpression, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(objectType));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20186,6 +20192,35 @@ namespace ts {
|
|||
return suggestion && symbolName(suggestion);
|
||||
}
|
||||
|
||||
function getSuggestionForNonexistentIndexSignature(objectType: Type, expr: ElementAccessExpression): string | undefined {
|
||||
// check if object type has setter or getter
|
||||
const hasProp = (name: "set" | "get", argCount = 1) => {
|
||||
const prop = getPropertyOfObjectType(objectType, <__String>name);
|
||||
if (prop) {
|
||||
const s = getSingleCallSignature(getTypeOfSymbol(prop));
|
||||
if (s && getMinArgumentCount(s) === argCount && typeToString(getTypeAtPosition(s, 0)) === "string") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const suggestedMethod = isAssignmentTarget(expr) ? "set" : "get";
|
||||
if (!hasProp(suggestedMethod)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let suggestion = tryGetPropertyAccessOrIdentifierToString(expr);
|
||||
if (suggestion === undefined) {
|
||||
suggestion = suggestedMethod;
|
||||
}
|
||||
else {
|
||||
suggestion += "." + suggestedMethod;
|
||||
}
|
||||
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a name and a list of symbols whose names are *not* equal to the name, return a spelling suggestion if there is one that is close enough.
|
||||
* Names less than length 3 only check for case-insensitive equality, not levenshtein distance.
|
||||
|
|
|
@ -4272,7 +4272,10 @@
|
|||
"category": "Error",
|
||||
"code": 7051
|
||||
},
|
||||
|
||||
"Element implicitly has an 'any' type because type '{0}' has no index signature. Did you mean to call '{1}' ?": {
|
||||
"category": "Error",
|
||||
"code": 7052
|
||||
},
|
||||
"You cannot rename this element.": {
|
||||
"category": "Error",
|
||||
"code": 8000
|
||||
|
|
|
@ -3960,6 +3960,16 @@ namespace ts {
|
|||
return isPropertyAccessExpression(node) && isEntityNameExpression(node.expression);
|
||||
}
|
||||
|
||||
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined {
|
||||
if (isPropertyAccessExpression(expr)) {
|
||||
return tryGetPropertyAccessOrIdentifierToString(expr.expression) + "." + expr.name;
|
||||
}
|
||||
if (isIdentifier(expr)) {
|
||||
return unescapeLeadingUnderscores(expr.escapedText);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function isPrototypeAccess(node: Node): node is PropertyAccessExpression {
|
||||
return isPropertyAccessExpression(node) && node.name.escapedText === "prototype";
|
||||
}
|
||||
|
|
|
@ -1,8 +1,47 @@
|
|||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(1,9): error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature.
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(7,1): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(8,13): error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(13,13): error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature.
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(19,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(20,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts(21,1): error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
|
||||
|
||||
==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (1 errors) ====
|
||||
var x = {}["hello"];
|
||||
==== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts (7 errors) ====
|
||||
var a = {}["hello"];
|
||||
~~~~~~~~~~~
|
||||
!!! error TS7017: Element implicitly has an 'any' type because type '{}' has no index signature.
|
||||
var y: string = { '': 'foo' }[''];
|
||||
var b: string = { '': 'foo' }[''];
|
||||
|
||||
var c = {
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
c['hello'];
|
||||
~~~~~~~~~~
|
||||
!!! error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
|
||||
const foo = c['hello'];
|
||||
~~~~~~~~~~
|
||||
!!! error TS7052: Element implicitly has an 'any' type because type '{ get: (key: string) => string; }' has no index signature. Did you mean to call 'get' ?
|
||||
|
||||
var d = {
|
||||
set: (key: string) => 'foobar'
|
||||
};
|
||||
const bar = d['hello'];
|
||||
~~~~~~~~~~
|
||||
!!! error TS7017: Element implicitly has an 'any' type because type '{ set: (key: string) => string; }' has no index signature.
|
||||
|
||||
var e = {
|
||||
set: (key: string) => 'foobar',
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
~~~~~~~~~~
|
||||
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
e['hello'] += 1;
|
||||
~~~~~~~~~~
|
||||
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
e['hello'] ++;
|
||||
~~~~~~~~~~
|
||||
!!! error TS7052: Element implicitly has an 'any' type because type '{ set: (key: string) => string; get: (key: string) => string; }' has no index signature. Did you mean to call 'set' ?
|
||||
|
||||
|
|
@ -1,7 +1,44 @@
|
|||
//// [noImplicitAnyStringIndexerOnObject.ts]
|
||||
var x = {}["hello"];
|
||||
var y: string = { '': 'foo' }[''];
|
||||
var a = {}["hello"];
|
||||
var b: string = { '': 'foo' }[''];
|
||||
|
||||
var c = {
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
c['hello'];
|
||||
const foo = c['hello'];
|
||||
|
||||
var d = {
|
||||
set: (key: string) => 'foobar'
|
||||
};
|
||||
const bar = d['hello'];
|
||||
|
||||
var e = {
|
||||
set: (key: string) => 'foobar',
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
e['hello'] += 1;
|
||||
e['hello'] ++;
|
||||
|
||||
|
||||
|
||||
//// [noImplicitAnyStringIndexerOnObject.js]
|
||||
var x = {}["hello"];
|
||||
var y = { '': 'foo' }[''];
|
||||
var a = {}["hello"];
|
||||
var b = { '': 'foo' }[''];
|
||||
var c = {
|
||||
get: function (key) { return 'foobar'; }
|
||||
};
|
||||
c['hello'];
|
||||
var foo = c['hello'];
|
||||
var d = {
|
||||
set: function (key) { return 'foobar'; }
|
||||
};
|
||||
var bar = d['hello'];
|
||||
var e = {
|
||||
set: function (key) { return 'foobar'; },
|
||||
get: function (key) { return 'foobar'; }
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
e['hello'] += 1;
|
||||
e['hello']++;
|
||||
|
|
|
@ -1,9 +1,58 @@
|
|||
=== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts ===
|
||||
var x = {}["hello"];
|
||||
>x : Symbol(x, Decl(noImplicitAnyStringIndexerOnObject.ts, 0, 3))
|
||||
var a = {}["hello"];
|
||||
>a : Symbol(a, Decl(noImplicitAnyStringIndexerOnObject.ts, 0, 3))
|
||||
|
||||
var y: string = { '': 'foo' }[''];
|
||||
>y : Symbol(y, Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 3))
|
||||
var b: string = { '': 'foo' }[''];
|
||||
>b : Symbol(b, Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 3))
|
||||
>'' : Symbol('', Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 17))
|
||||
>'' : Symbol('', Decl(noImplicitAnyStringIndexerOnObject.ts, 1, 17))
|
||||
|
||||
var c = {
|
||||
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
|
||||
|
||||
get: (key: string) => 'foobar'
|
||||
>get : Symbol(get, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 9))
|
||||
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 4, 8))
|
||||
|
||||
};
|
||||
c['hello'];
|
||||
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
|
||||
|
||||
const foo = c['hello'];
|
||||
>foo : Symbol(foo, Decl(noImplicitAnyStringIndexerOnObject.ts, 7, 5))
|
||||
>c : Symbol(c, Decl(noImplicitAnyStringIndexerOnObject.ts, 3, 3))
|
||||
|
||||
var d = {
|
||||
>d : Symbol(d, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 3))
|
||||
|
||||
set: (key: string) => 'foobar'
|
||||
>set : Symbol(set, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 9))
|
||||
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 10, 8))
|
||||
|
||||
};
|
||||
const bar = d['hello'];
|
||||
>bar : Symbol(bar, Decl(noImplicitAnyStringIndexerOnObject.ts, 12, 5))
|
||||
>d : Symbol(d, Decl(noImplicitAnyStringIndexerOnObject.ts, 9, 3))
|
||||
|
||||
var e = {
|
||||
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
|
||||
|
||||
set: (key: string) => 'foobar',
|
||||
>set : Symbol(set, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 9))
|
||||
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 15, 8))
|
||||
|
||||
get: (key: string) => 'foobar'
|
||||
>get : Symbol(get, Decl(noImplicitAnyStringIndexerOnObject.ts, 15, 33))
|
||||
>key : Symbol(key, Decl(noImplicitAnyStringIndexerOnObject.ts, 16, 8))
|
||||
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
|
||||
|
||||
e['hello'] += 1;
|
||||
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
|
||||
|
||||
e['hello'] ++;
|
||||
>e : Symbol(e, Decl(noImplicitAnyStringIndexerOnObject.ts, 14, 3))
|
||||
|
||||
|
||||
|
|
|
@ -1,15 +1,92 @@
|
|||
=== tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts ===
|
||||
var x = {}["hello"];
|
||||
>x : any
|
||||
var a = {}["hello"];
|
||||
>a : any
|
||||
>{}["hello"] : any
|
||||
>{} : {}
|
||||
>"hello" : "hello"
|
||||
|
||||
var y: string = { '': 'foo' }[''];
|
||||
>y : string
|
||||
var b: string = { '': 'foo' }[''];
|
||||
>b : string
|
||||
>{ '': 'foo' }[''] : string
|
||||
>{ '': 'foo' } : { '': string; }
|
||||
>'' : string
|
||||
>'foo' : "foo"
|
||||
>'' : ""
|
||||
|
||||
var c = {
|
||||
>c : { get: (key: string) => string; }
|
||||
>{ get: (key: string) => 'foobar'} : { get: (key: string) => string; }
|
||||
|
||||
get: (key: string) => 'foobar'
|
||||
>get : (key: string) => string
|
||||
>(key: string) => 'foobar' : (key: string) => string
|
||||
>key : string
|
||||
>'foobar' : "foobar"
|
||||
|
||||
};
|
||||
c['hello'];
|
||||
>c['hello'] : any
|
||||
>c : { get: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
|
||||
const foo = c['hello'];
|
||||
>foo : any
|
||||
>c['hello'] : any
|
||||
>c : { get: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
|
||||
var d = {
|
||||
>d : { set: (key: string) => string; }
|
||||
>{ set: (key: string) => 'foobar'} : { set: (key: string) => string; }
|
||||
|
||||
set: (key: string) => 'foobar'
|
||||
>set : (key: string) => string
|
||||
>(key: string) => 'foobar' : (key: string) => string
|
||||
>key : string
|
||||
>'foobar' : "foobar"
|
||||
|
||||
};
|
||||
const bar = d['hello'];
|
||||
>bar : any
|
||||
>d['hello'] : any
|
||||
>d : { set: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
|
||||
var e = {
|
||||
>e : { set: (key: string) => string; get: (key: string) => string; }
|
||||
>{ set: (key: string) => 'foobar', get: (key: string) => 'foobar'} : { set: (key: string) => string; get: (key: string) => string; }
|
||||
|
||||
set: (key: string) => 'foobar',
|
||||
>set : (key: string) => string
|
||||
>(key: string) => 'foobar' : (key: string) => string
|
||||
>key : string
|
||||
>'foobar' : "foobar"
|
||||
|
||||
get: (key: string) => 'foobar'
|
||||
>get : (key: string) => string
|
||||
>(key: string) => 'foobar' : (key: string) => string
|
||||
>key : string
|
||||
>'foobar' : "foobar"
|
||||
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
>e['hello'] = 'modified' : "modified"
|
||||
>e['hello'] : any
|
||||
>e : { set: (key: string) => string; get: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
>'modified' : "modified"
|
||||
|
||||
e['hello'] += 1;
|
||||
>e['hello'] += 1 : any
|
||||
>e['hello'] : any
|
||||
>e : { set: (key: string) => string; get: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
>1 : 1
|
||||
|
||||
e['hello'] ++;
|
||||
>e['hello'] ++ : number
|
||||
>e['hello'] : any
|
||||
>e : { set: (key: string) => string; get: (key: string) => string; }
|
||||
>'hello' : "hello"
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
// @noimplicitany: true
|
||||
|
||||
var x = {}["hello"];
|
||||
var y: string = { '': 'foo' }[''];
|
||||
var a = {}["hello"];
|
||||
var b: string = { '': 'foo' }[''];
|
||||
|
||||
var c = {
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
c['hello'];
|
||||
const foo = c['hello'];
|
||||
|
||||
var d = {
|
||||
set: (key: string) => 'foobar'
|
||||
};
|
||||
const bar = d['hello'];
|
||||
|
||||
var e = {
|
||||
set: (key: string) => 'foobar',
|
||||
get: (key: string) => 'foobar'
|
||||
};
|
||||
e['hello'] = 'modified';
|
||||
e['hello'] += 1;
|
||||
e['hello'] ++;
|
||||
|
||||
|
|
Loading…
Reference in a new issue