From a4c683be12f074d2a5555feb344020913eb0788e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 7 Apr 2021 15:54:27 -0400 Subject: [PATCH] Again: Improve error messages for empty DOM interface property access (#43007) * Again: Improve error messages for empty DOM interface property access * containerSeemsToBeEmptyDomElement Co-authored-by: Daniel Rosenwasser * isEmptyObjectType; unescapeLeadingUnderscores * Single tick quotes for now * Undo accidental diagnostic change * Correct new baseline Co-authored-by: Daniel Rosenwasser Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> --- src/compiler/checker.ts | 15 ++++- src/compiler/diagnosticMessages.json | 4 ++ .../reference/missingDomElements.errors.txt | 39 ++++++++++++ .../baselines/reference/missingDomElements.js | 28 +++++++++ .../reference/missingDomElements.symbols | 43 +++++++++++++ .../reference/missingDomElements.types | 61 +++++++++++++++++++ tests/cases/compiler/missingDomElements.ts | 19 ++++++ 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/missingDomElements.errors.txt create mode 100644 tests/baselines/reference/missingDomElements.js create mode 100644 tests/baselines/reference/missingDomElements.symbols create mode 100644 tests/baselines/reference/missingDomElements.types create mode 100644 tests/cases/compiler/missingDomElements.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87ae61a3518..a22dfd98e5e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -22270,6 +22270,10 @@ namespace ts { return type.flags & TypeFlags.Union ? every((type).types, f) : f(type); } + function everyContainedType(type: Type, f: (t: Type) => boolean): boolean { + return type.flags & TypeFlags.UnionOrIntersection ? every((type as UnionOrIntersectionType).types, f) : f(type); + } + function filterType(type: Type, f: (t: Type) => boolean): Type { if (type.flags & TypeFlags.Union) { const types = (type).types; @@ -27336,7 +27340,10 @@ 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, missingProperty, container); + const diagnostic = containerSeemsToBeEmptyDomElement(containingType) + ? Diagnostics.Property_0_does_not_exist_on_type_1_Try_changing_the_lib_compiler_option_to_include_dom + : Diagnostics.Property_0_does_not_exist_on_type_1; + errorInfo = chainDiagnosticMessages(elaborateNeverIntersection(errorInfo, containingType), diagnostic, missingProperty, container); } } } @@ -27348,6 +27355,12 @@ namespace ts { diagnostics.add(resultDiagnostic); } + function containerSeemsToBeEmptyDomElement(containingType: Type) { + return (compilerOptions.lib && !compilerOptions.lib.includes("dom")) && + everyContainedType(containingType, type => type.symbol && /^(EventTarget|Node|((HTML[a-zA-Z]*)?Element))$/.test(unescapeLeadingUnderscores(type.symbol.escapedName))) && + isEmptyObjectType(containingType); + } + function typeHasStaticProperty(propName: __String, containingType: Type): boolean { const prop = containingType.symbol && getPropertyOfType(getTypeOfSymbol(containingType.symbol), propName); return prop !== undefined && !!prop.valueDeclaration && hasSyntacticModifier(prop.valueDeclaration, ModifierFlags.Static); diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c0ffe0bec42..38f61060173 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3324,6 +3324,10 @@ "category": "Error", "code": 2811 }, + "Property '{0}' does not exist on type '{1}'. Try changing the 'lib' compiler option to include 'dom'.": { + "category": "Error", + "code": 2812 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/missingDomElements.errors.txt b/tests/baselines/reference/missingDomElements.errors.txt new file mode 100644 index 00000000000..858042dd31d --- /dev/null +++ b/tests/baselines/reference/missingDomElements.errors.txt @@ -0,0 +1,39 @@ +tests/cases/compiler/missingDomElements.ts(6,24): error TS2812: Property 'textContent' does not exist on type 'Element'. Try changing the 'lib' compiler option to include 'dom'. +tests/cases/compiler/missingDomElements.ts(7,28): error TS2812: Property 'textContent' does not exist on type 'HTMLElement'. Try changing the 'lib' compiler option to include 'dom'. +tests/cases/compiler/missingDomElements.ts(8,33): error TS2812: Property 'textContent' does not exist on type 'HTMLInputElement'. Try changing the 'lib' compiler option to include 'dom'. +tests/cases/compiler/missingDomElements.ts(9,47): error TS2812: Property 'textContent' does not exist on type 'EventTarget & HTMLInputElement'. Try changing the 'lib' compiler option to include 'dom'. +tests/cases/compiler/missingDomElements.ts(16,32): error TS2339: Property 'textContent' does not exist on type 'HTMLElementFake'. +tests/cases/compiler/missingDomElements.ts(17,21): error TS2339: Property 'textContent' does not exist on type 'Node'. + + +==== tests/cases/compiler/missingDomElements.ts (6 errors) ==== + interface Element {} + interface EventTarget {} + interface HTMLElement {} + interface HTMLInputElement {} + + ({} as any as Element).textContent; + ~~~~~~~~~~~ +!!! error TS2812: Property 'textContent' does not exist on type 'Element'. Try changing the 'lib' compiler option to include 'dom'. + ({} as any as HTMLElement).textContent; + ~~~~~~~~~~~ +!!! error TS2812: Property 'textContent' does not exist on type 'HTMLElement'. Try changing the 'lib' compiler option to include 'dom'. + ({} as any as HTMLInputElement).textContent; + ~~~~~~~~~~~ +!!! error TS2812: Property 'textContent' does not exist on type 'HTMLInputElement'. Try changing the 'lib' compiler option to include 'dom'. + ({} as any as EventTarget & HTMLInputElement).textContent + ~~~~~~~~~~~ +!!! error TS2812: Property 'textContent' does not exist on type 'EventTarget & HTMLInputElement'. Try changing the 'lib' compiler option to include 'dom'. + + interface HTMLElementFake {} + interface Node { + actuallyNotTheSame: number; + }; + + ({} as any as HTMLElementFake).textContent; + ~~~~~~~~~~~ +!!! error TS2339: Property 'textContent' does not exist on type 'HTMLElementFake'. + ({} as any as Node).textContent; + ~~~~~~~~~~~ +!!! error TS2339: Property 'textContent' does not exist on type 'Node'. + \ No newline at end of file diff --git a/tests/baselines/reference/missingDomElements.js b/tests/baselines/reference/missingDomElements.js new file mode 100644 index 00000000000..57b4a534729 --- /dev/null +++ b/tests/baselines/reference/missingDomElements.js @@ -0,0 +1,28 @@ +//// [missingDomElements.ts] +interface Element {} +interface EventTarget {} +interface HTMLElement {} +interface HTMLInputElement {} + +({} as any as Element).textContent; +({} as any as HTMLElement).textContent; +({} as any as HTMLInputElement).textContent; +({} as any as EventTarget & HTMLInputElement).textContent + +interface HTMLElementFake {} +interface Node { + actuallyNotTheSame: number; +}; + +({} as any as HTMLElementFake).textContent; +({} as any as Node).textContent; + + +//// [missingDomElements.js] +({}.textContent); +({}.textContent); +({}.textContent); +({}.textContent); +; +({}.textContent); +({}.textContent); diff --git a/tests/baselines/reference/missingDomElements.symbols b/tests/baselines/reference/missingDomElements.symbols new file mode 100644 index 00000000000..4ffc22e57f6 --- /dev/null +++ b/tests/baselines/reference/missingDomElements.symbols @@ -0,0 +1,43 @@ +=== tests/cases/compiler/missingDomElements.ts === +interface Element {} +>Element : Symbol(Element, Decl(missingDomElements.ts, 0, 0)) + +interface EventTarget {} +>EventTarget : Symbol(EventTarget, Decl(missingDomElements.ts, 0, 20)) + +interface HTMLElement {} +>HTMLElement : Symbol(HTMLElement, Decl(missingDomElements.ts, 1, 24)) + +interface HTMLInputElement {} +>HTMLInputElement : Symbol(HTMLInputElement, Decl(missingDomElements.ts, 2, 24)) + +({} as any as Element).textContent; +>Element : Symbol(Element, Decl(missingDomElements.ts, 0, 0)) + +({} as any as HTMLElement).textContent; +>HTMLElement : Symbol(HTMLElement, Decl(missingDomElements.ts, 1, 24)) + +({} as any as HTMLInputElement).textContent; +>HTMLInputElement : Symbol(HTMLInputElement, Decl(missingDomElements.ts, 2, 24)) + +({} as any as EventTarget & HTMLInputElement).textContent +>EventTarget : Symbol(EventTarget, Decl(missingDomElements.ts, 0, 20)) +>HTMLInputElement : Symbol(HTMLInputElement, Decl(missingDomElements.ts, 2, 24)) + +interface HTMLElementFake {} +>HTMLElementFake : Symbol(HTMLElementFake, Decl(missingDomElements.ts, 8, 57)) + +interface Node { +>Node : Symbol(Node, Decl(missingDomElements.ts, 10, 28)) + + actuallyNotTheSame: number; +>actuallyNotTheSame : Symbol(Node.actuallyNotTheSame, Decl(missingDomElements.ts, 11, 16)) + +}; + +({} as any as HTMLElementFake).textContent; +>HTMLElementFake : Symbol(HTMLElementFake, Decl(missingDomElements.ts, 8, 57)) + +({} as any as Node).textContent; +>Node : Symbol(Node, Decl(missingDomElements.ts, 10, 28)) + diff --git a/tests/baselines/reference/missingDomElements.types b/tests/baselines/reference/missingDomElements.types new file mode 100644 index 00000000000..0e1302ae86b --- /dev/null +++ b/tests/baselines/reference/missingDomElements.types @@ -0,0 +1,61 @@ +=== tests/cases/compiler/missingDomElements.ts === +interface Element {} +interface EventTarget {} +interface HTMLElement {} +interface HTMLInputElement {} + +({} as any as Element).textContent; +>({} as any as Element).textContent : any +>({} as any as Element) : Element +>{} as any as Element : Element +>{} as any : any +>{} : {} +>textContent : any + +({} as any as HTMLElement).textContent; +>({} as any as HTMLElement).textContent : any +>({} as any as HTMLElement) : HTMLElement +>{} as any as HTMLElement : HTMLElement +>{} as any : any +>{} : {} +>textContent : any + +({} as any as HTMLInputElement).textContent; +>({} as any as HTMLInputElement).textContent : any +>({} as any as HTMLInputElement) : HTMLInputElement +>{} as any as HTMLInputElement : HTMLInputElement +>{} as any : any +>{} : {} +>textContent : any + +({} as any as EventTarget & HTMLInputElement).textContent +>({} as any as EventTarget & HTMLInputElement).textContent : any +>({} as any as EventTarget & HTMLInputElement) : EventTarget & HTMLInputElement +>{} as any as EventTarget & HTMLInputElement : EventTarget & HTMLInputElement +>{} as any : any +>{} : {} +>textContent : any + +interface HTMLElementFake {} +interface Node { + actuallyNotTheSame: number; +>actuallyNotTheSame : number + +}; + +({} as any as HTMLElementFake).textContent; +>({} as any as HTMLElementFake).textContent : any +>({} as any as HTMLElementFake) : HTMLElementFake +>{} as any as HTMLElementFake : HTMLElementFake +>{} as any : any +>{} : {} +>textContent : any + +({} as any as Node).textContent; +>({} as any as Node).textContent : any +>({} as any as Node) : Node +>{} as any as Node : Node +>{} as any : any +>{} : {} +>textContent : any + diff --git a/tests/cases/compiler/missingDomElements.ts b/tests/cases/compiler/missingDomElements.ts new file mode 100644 index 00000000000..df5ad56a792 --- /dev/null +++ b/tests/cases/compiler/missingDomElements.ts @@ -0,0 +1,19 @@ +// @lib: esnext + +interface Element {} +interface EventTarget {} +interface HTMLElement {} +interface HTMLInputElement {} + +({} as any as Element).textContent; +({} as any as HTMLElement).textContent; +({} as any as HTMLInputElement).textContent; +({} as any as EventTarget & HTMLInputElement).textContent + +interface HTMLElementFake {} +interface Node { + actuallyNotTheSame: number; +}; + +({} as any as HTMLElementFake).textContent; +({} as any as Node).textContent;