fix(40320): Better errors when using properties/methods from newer versions of ECMAScript (#40650)

* Update package-lock.json

* Suggesting a library for a missing property/method

* Added more types and added tests

* Added more tests to cover all the latest features

* Added bigintarrays and dataview methods

* Fixed typo in template

* Transform old error message to use 2nd template slot

* Removed test that has been split up between es2015 and es2016+

* Use empty arrays and remove unnecessary function call

* merge

* Added early bail-out and updated baselines

* Implemented early bail-out (misread)

Co-authored-by: TypeScript Bot <typescriptbot@microsoft.com>
This commit is contained in:
Vincent Boivin
2020-10-02 19:47:37 -04:00
committed by GitHub
parent 420df7f12b
commit 61aadc4ce2
23 changed files with 2533 additions and 98 deletions

View File

@@ -2023,7 +2023,15 @@ namespace ts {
}
}
if (!suggestion) {
error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg!));
if (nameArg) {
const lib = getSuggestedLibForNonExistentName(nameArg);
if (lib) {
error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg), lib);
}
else {
error(errorLocation, nameNotFoundMessage, diagnosticName(nameArg));
}
}
}
suggestionCount++;
}
@@ -20631,7 +20639,17 @@ namespace ts {
case "WeakSet":
case "Iterator":
case "AsyncIterator":
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_es2015_or_later;
case "SharedArrayBuffer":
case "Atomics":
case "AsyncIterable":
case "AsyncIterableIterator":
case "AsyncGenerator":
case "AsyncGeneratorFunction":
case "BigInt":
case "Reflect":
case "BigInt64Array":
case "BigUint64Array":
return Diagnostics.Cannot_find_name_0_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_1_or_later;
default:
if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) {
return Diagnostics.No_value_exists_in_scope_for_the_shorthand_property_0_Either_declare_one_or_provide_an_initializer;
@@ -25887,7 +25905,15 @@ namespace ts {
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
}
else {
errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), Diagnostics.Property_0_does_not_exist_on_type_1, declarationNameToString(propNode), typeToString(containingType));
const missingProperty = declarationNameToString(propNode);
const container = typeToString(containingType);
const lib = getSuggestedLibForNonExistentProperty(missingProperty, containingType);
if (lib) {
errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Do_you_need_to_change_your_target_library_Try_changing_the_lib_compiler_option_to_2_or_later, missingProperty, container, lib);
}
else {
errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), Diagnostics.Property_0_does_not_exist_on_type_1, missingProperty, container);
}
}
}
}
@@ -25903,6 +25929,34 @@ namespace ts {
return prop !== undefined && prop.valueDeclaration && hasSyntacticModifier(prop.valueDeclaration, ModifierFlags.Static);
}
function getSuggestedLibForNonExistentName(name: __String | Identifier) {
const missingName = diagnosticName(name);
const allFeatures = getScriptTargetFeatures();
const libTargets = getOwnKeys(allFeatures);
for (const libTarget of libTargets) {
const containingTypes = getOwnKeys(allFeatures[libTarget]);
if (containingTypes !== undefined && contains(containingTypes, missingName)) {
return libTarget;
}
}
}
function getSuggestedLibForNonExistentProperty(missingProperty: string, containingType: Type) {
const container = getApparentType(containingType).symbol;
if (!container) {
return undefined;
}
const allFeatures = getScriptTargetFeatures();
const libTargets = getOwnKeys(allFeatures);
for (const libTarget of libTargets) {
const featuresOfLib = allFeatures[libTarget];
const featuresOfContainingType = featuresOfLib[symbolName(container)];
if (featuresOfContainingType !== undefined && contains(featuresOfContainingType, missingProperty)) {
return libTarget;
}
}
}
function getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
}
@@ -25992,6 +26046,7 @@ namespace ts {
*/
function getSpellingSuggestionForName(name: string, symbols: Symbol[], meaning: SymbolFlags): Symbol | undefined {
return getSpellingSuggestion(name, symbols, getCandidateName);
function getCandidateName(candidate: Symbol) {
const candidateName = symbolName(candidate);
if (startsWith(candidateName, "\"")) {

View File

@@ -2197,6 +2197,10 @@
"category": "Error",
"code": 2549
},
"Property '{0}' does not exist on type '{1}'. Do you need to change your target library? Try changing the `lib` compiler option to '{2}' or later.": {
"category": "Error",
"code": 2550
},
"Property '{0}' does not exist on type '{1}'. Did you mean '{2}'?": {
"category": "Error",
"code": 2551
@@ -2313,7 +2317,7 @@
"category": "Error",
"code": 2582
},
"Cannot find name '{0}'. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.": {
"Cannot find name '{0}'. Do you need to change your target library? Try changing the `lib` compiler option to '{1}' or later.": {
"category": "Error",
"code": 2583
},

View File

@@ -540,6 +540,76 @@ namespace ts {
return emitNode && emitNode.flags || 0;
}
interface ScriptTargetFeatures {
[key: string]: { [key: string]: string[] | undefined };
};
export function getScriptTargetFeatures(): ScriptTargetFeatures {
return {
es2015: {
Array: ["find", "findIndex", "fill", "copyWithin", "entries", "keys", "values"],
RegExp: ["flags", "sticky", "unicode"],
Reflect: ["apply", "construct", "defineProperty", "deleteProperty", "get"," getOwnPropertyDescriptor", "getPrototypeOf", "has", "isExtensible", "ownKeys", "preventExtensions", "set", "setPrototypeOf"],
ArrayConstructor: ["from", "of"],
ObjectConstructor: ["assign", "getOwnPropertySymbols", "keys", "is", "setPrototypeOf"],
NumberConstructor: ["isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt"],
Math: ["clz32", "imul", "sign", "log10", "log2", "log1p", "expm1", "cosh", "sinh", "tanh", "acosh", "asinh", "atanh", "hypot", "trunc", "fround", "cbrt"],
Map: ["entries", "keys", "values"],
Set: ["entries", "keys", "values"],
Promise: ["all", "race", "reject", "resolve"],
Symbol: ["for", "keyFor"],
WeakMap: ["entries", "keys", "values"],
WeakSet: ["entries", "keys", "values"],
Iterator: emptyArray,
AsyncIterator: emptyArray,
String: ["codePointAt", "includes", "endsWith", "normalize", "repeat", "startsWith", "anchor", "big", "blink", "bold", "fixed", "fontcolor", "fontsize", "italics", "link", "small", "strike", "sub", "sup"],
StringConstructor: ["fromCodePoint", "raw"]
},
es2016: {
Array: ["includes"]
},
es2017: {
Atomics: emptyArray,
SharedArrayBuffer: emptyArray,
String: ["padStart", "padEnd"],
ObjectConstructor: ["values", "entries", "getOwnPropertyDescriptors"],
DateTimeFormat: ["formatToParts"]
},
es2018: {
Promise: ["finally"],
RegExpMatchArray: ["groups"],
RegExpExecArray: ["groups"],
RegExp: ["dotAll"],
Intl: ["PluralRules"],
AsyncIterable: emptyArray,
AsyncIterableIterator: emptyArray,
AsyncGenerator: emptyArray,
AsyncGeneratorFunction: emptyArray,
},
es2019: {
Array: ["flat", "flatMap"],
ObjectConstructor: ["fromEntries"],
String: ["trimStart", "trimEnd", "trimLeft", "trimRight"],
Symbol: ["description"]
},
es2020: {
BigInt: emptyArray,
BigInt64Array: emptyArray,
BigUint64Array: emptyArray,
Promise: ["allSettled"],
SymbolConstructor: ["matchAll"],
String: ["matchAll"],
DataView: ["setBigInt64", "setBigUint64", "getBigInt64", "getBigUint64"],
RelativeTimeFormat: ["format", "formatToParts", "resolvedOptions"]
},
esnext: {
Promise: ["any"],
String: ["replaceAll"],
NumberFormat: ["formatToParts"]
}
};
}
export const enum GetLiteralTextFlags {
None = 0,
NeverAsciiEscape = 1 << 0,
@@ -5733,7 +5803,6 @@ namespace ts {
if (arguments.length > 2) {
text = formatStringFromArgs(text, arguments, 2);
}
return {
messageText: text,
category: message.category,