diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 60a77ace4b04c..e5cd1f43ab934 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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. diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 45ec2a9ad427a..b754dbd9ea14b 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -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 diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 188f2f8630209..45dc980296be7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -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"; } diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt index b3f3b4bc6e050..6df52aea46a88 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.errors.txt @@ -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' }['']; \ No newline at end of file + 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' ? + + \ No newline at end of file diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js index 2ea3291e5db7a..a02740351cff5 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.js @@ -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']++; diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols index aa3990b0d7c62..f86c0d14b6ff7 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.symbols @@ -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)) + + diff --git a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types index a1b78eaca57da..dcf01d8c5e8df 100644 --- a/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types +++ b/tests/baselines/reference/noImplicitAnyStringIndexerOnObject.types @@ -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" + + diff --git a/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts b/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts index 9a84cc328b286..1cd967c294fb5 100644 --- a/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts +++ b/tests/cases/compiler/noImplicitAnyStringIndexerOnObject.ts @@ -1,4 +1,24 @@ // @noimplicitany: true -var x = {}["hello"]; -var y: string = { '': 'foo' }['']; \ No newline at end of file +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'] ++; +