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 <drosenwasser@microsoft.com>

* isEmptyObjectType; unescapeLeadingUnderscores

* Single tick quotes for now

* Undo accidental diagnostic change

* Correct new baseline

Co-authored-by: Daniel Rosenwasser <drosenwasser@microsoft.com>
Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
Josh Goldberg 2021-04-07 15:54:27 -04:00 committed by GitHub
parent 905a1fea39
commit a4c683be12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 208 additions and 1 deletions

View File

@ -22270,6 +22270,10 @@ namespace ts {
return type.flags & TypeFlags.Union ? every((<UnionType>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 = (<UnionType>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);

View File

@ -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",

View File

@ -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'.

View File

@ -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);

View File

@ -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))

View File

@ -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

View File

@ -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;