diff --git a/baselines/dom.generated.d.ts b/baselines/dom.generated.d.ts index a6556cb39..f77d88b30 100644 --- a/baselines/dom.generated.d.ts +++ b/baselines/dom.generated.d.ts @@ -14051,9 +14051,12 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, NonDocumentTyp * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches) */ - matches(selectors: K): this is HTMLElementTagNameMap[K]; - matches(selectors: K): this is SVGElementTagNameMap[K]; - matches(selectors: K): this is MathMLElementTagNameMap[K]; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches(selectors: K): boolean; + matches(selectors: K): boolean; + matches(selectors: K): boolean; matches(selectors: string): boolean; /** * The **`releasePointerCapture()`** method of the Element interface releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer. @@ -43880,6 +43883,34 @@ interface MathMLElementTagNameMap { "semantics": MathMLElement; } +type ElementMatchesNarrowingMap = + ElementType extends Element + ? Element extends ElementType + ? ElementMap + : [ElementMatchesBase] extends [never] + ? {} + : ElementMatchesStrictMap> + : {}; + +type ElementMatchesBase = + HTMLElement extends ElementType + ? HTMLElement + : SVGElement extends ElementType + ? SVGElement + : MathMLElement extends ElementType + ? MathMLElement + : never; + +type ElementMatchesStrictMap = { + [Key in keyof ElementMap as ( + ElementMap[Key] extends BaseElement + ? BaseElement extends ElementMap[Key] + ? never + : Key + : never + )]: ElementMap[Key]; +}; + /** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */ type ElementTagNameMap = HTMLElementTagNameMap & Pick>; diff --git a/baselines/ts5.5/dom.generated.d.ts b/baselines/ts5.5/dom.generated.d.ts index e1e2ad25d..1eaf2218e 100644 --- a/baselines/ts5.5/dom.generated.d.ts +++ b/baselines/ts5.5/dom.generated.d.ts @@ -14038,9 +14038,12 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, NonDocumentTyp * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches) */ - matches(selectors: K): this is HTMLElementTagNameMap[K]; - matches(selectors: K): this is SVGElementTagNameMap[K]; - matches(selectors: K): this is MathMLElementTagNameMap[K]; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches(selectors: K): boolean; + matches(selectors: K): boolean; + matches(selectors: K): boolean; matches(selectors: string): boolean; /** * The **`releasePointerCapture()`** method of the Element interface releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer. @@ -43854,6 +43857,34 @@ interface MathMLElementTagNameMap { "semantics": MathMLElement; } +type ElementMatchesNarrowingMap = + ElementType extends Element + ? Element extends ElementType + ? ElementMap + : [ElementMatchesBase] extends [never] + ? {} + : ElementMatchesStrictMap> + : {}; + +type ElementMatchesBase = + HTMLElement extends ElementType + ? HTMLElement + : SVGElement extends ElementType + ? SVGElement + : MathMLElement extends ElementType + ? MathMLElement + : never; + +type ElementMatchesStrictMap = { + [Key in keyof ElementMap as ( + ElementMap[Key] extends BaseElement + ? BaseElement extends ElementMap[Key] + ? never + : Key + : never + )]: ElementMap[Key]; +}; + /** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */ type ElementTagNameMap = HTMLElementTagNameMap & Pick>; diff --git a/baselines/ts5.6/dom.generated.d.ts b/baselines/ts5.6/dom.generated.d.ts index be2edd668..ff7f2cd35 100644 --- a/baselines/ts5.6/dom.generated.d.ts +++ b/baselines/ts5.6/dom.generated.d.ts @@ -14048,9 +14048,12 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, NonDocumentTyp * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches) */ - matches(selectors: K): this is HTMLElementTagNameMap[K]; - matches(selectors: K): this is SVGElementTagNameMap[K]; - matches(selectors: K): this is MathMLElementTagNameMap[K]; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches(selectors: K): boolean; + matches(selectors: K): boolean; + matches(selectors: K): boolean; matches(selectors: string): boolean; /** * The **`releasePointerCapture()`** method of the Element interface releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer. @@ -43877,6 +43880,34 @@ interface MathMLElementTagNameMap { "semantics": MathMLElement; } +type ElementMatchesNarrowingMap = + ElementType extends Element + ? Element extends ElementType + ? ElementMap + : [ElementMatchesBase] extends [never] + ? {} + : ElementMatchesStrictMap> + : {}; + +type ElementMatchesBase = + HTMLElement extends ElementType + ? HTMLElement + : SVGElement extends ElementType + ? SVGElement + : MathMLElement extends ElementType + ? MathMLElement + : never; + +type ElementMatchesStrictMap = { + [Key in keyof ElementMap as ( + ElementMap[Key] extends BaseElement + ? BaseElement extends ElementMap[Key] + ? never + : Key + : never + )]: ElementMap[Key]; +}; + /** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */ type ElementTagNameMap = HTMLElementTagNameMap & Pick>; diff --git a/baselines/ts5.9/dom.generated.d.ts b/baselines/ts5.9/dom.generated.d.ts index 24a34aa36..222589452 100644 --- a/baselines/ts5.9/dom.generated.d.ts +++ b/baselines/ts5.9/dom.generated.d.ts @@ -14048,9 +14048,12 @@ interface Element extends Node, ARIAMixin, Animatable, ChildNode, NonDocumentTyp * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/matches) */ - matches(selectors: K): this is HTMLElementTagNameMap[K]; - matches(selectors: K): this is SVGElementTagNameMap[K]; - matches(selectors: K): this is MathMLElementTagNameMap[K]; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches>(selectors: K): this is Extract; + matches(selectors: K): boolean; + matches(selectors: K): boolean; + matches(selectors: K): boolean; matches(selectors: string): boolean; /** * The **`releasePointerCapture()`** method of the Element interface releases (stops) pointer capture that was previously set for a specific (PointerEvent) pointer. @@ -43877,6 +43880,34 @@ interface MathMLElementTagNameMap { "semantics": MathMLElement; } +type ElementMatchesNarrowingMap = + ElementType extends Element + ? Element extends ElementType + ? ElementMap + : [ElementMatchesBase] extends [never] + ? {} + : ElementMatchesStrictMap> + : {}; + +type ElementMatchesBase = + HTMLElement extends ElementType + ? HTMLElement + : SVGElement extends ElementType + ? SVGElement + : MathMLElement extends ElementType + ? MathMLElement + : never; + +type ElementMatchesStrictMap = { + [Key in keyof ElementMap as ( + ElementMap[Key] extends BaseElement + ? BaseElement extends ElementMap[Key] + ? never + : Key + : never + )]: ElementMap[Key]; +}; + /** @deprecated Directly use HTMLElementTagNameMap or SVGElementTagNameMap as appropriate, instead. */ type ElementTagNameMap = HTMLElementTagNameMap & Pick>; diff --git a/src/build/emitter.ts b/src/build/emitter.ts index a05c088b3..de674eb4d 100644 --- a/src/build/emitter.ts +++ b/src/build/emitter.ts @@ -645,7 +645,12 @@ export function emitWebIdl( const paramName = m.signature[0].param![0].name; for (const mapName of tagNameMapNames) { printer.printLine( - `matches(${paramName}: K): this is ${mapName}[K];`, + `matches>(${paramName}: K): this is Extract<${mapName}[K], this>;`, + ); + } + for (const mapName of tagNameMapNames) { + printer.printLine( + `matches(${paramName}: K): boolean;`, ); } printer.printLine(`matches(${paramName}: string): boolean;`); @@ -715,6 +720,67 @@ export function emitWebIdl( printer.printLine(""); } + function emitElementMatchesMap() { + printer.printLine( + "type ElementMatchesNarrowingMap =", + ); + printer.increaseIndent(); + printer.printLine("ElementType extends Element"); + printer.increaseIndent(); + printer.printLine("? Element extends ElementType"); + printer.increaseIndent(); + printer.printLine("? ElementMap"); + printer.printLine(": [ElementMatchesBase] extends [never]"); + printer.increaseIndent(); + printer.printLine("? {}"); + printer.printLine( + ": ElementMatchesStrictMap>", + ); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.printLine(": {};"); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.printLine(""); + printer.printLine("type ElementMatchesBase ="); + printer.increaseIndent(); + printer.printLine("HTMLElement extends ElementType"); + printer.increaseIndent(); + printer.printLine("? HTMLElement"); + printer.printLine(": SVGElement extends ElementType"); + printer.increaseIndent(); + printer.printLine("? SVGElement"); + printer.printLine(": MathMLElement extends ElementType"); + printer.increaseIndent(); + printer.printLine("? MathMLElement"); + printer.printLine(": never;"); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.printLine(""); + printer.printLine( + "type ElementMatchesStrictMap = {", + ); + printer.increaseIndent(); + printer.printLine("[Key in keyof ElementMap as ("); + printer.increaseIndent(); + printer.printLine("ElementMap[Key] extends BaseElement"); + printer.increaseIndent(); + printer.printLine("? BaseElement extends ElementMap[Key]"); + printer.increaseIndent(); + printer.printLine("? never"); + printer.printLine(": Key"); + printer.decreaseIndent(); + printer.printLine(": never"); + printer.decreaseIndent(); + printer.decreaseIndent(); + printer.printLine(")]: ElementMap[Key];"); + printer.decreaseIndent(); + printer.printLine("};"); + printer.printLine(""); + } + /// Emit overloads for the createEvent method function emitCreateEventOverloads(m: Browser.Method) { if (matchParamMethodSignature(m, "createEvent", "Event", "string")) { @@ -1664,6 +1730,7 @@ export function emitWebIdl( "MathMLElementTagNameMap", tagNameToEleName.mathMLResult, ); + emitElementMatchesMap(); emitDeprecatedHTMLOrSVGElementTagNameMap(); emitNamedConstructors(); } diff --git a/unittests/files/matches.ts b/unittests/files/matches.ts new file mode 100644 index 000000000..c95e14bc5 --- /dev/null +++ b/unittests/files/matches.ts @@ -0,0 +1,33 @@ +declare const element: Element; +declare const htmlElement: HTMLElement; +declare const htmlDivElement: HTMLDivElement; +declare const htmlTableCellElement: HTMLTableCellElement; + +if (element.matches("dt")) { + const narrowed: HTMLElement = element; +} + +if (!htmlElement.matches("dt")) { + htmlElement.id; +} + +if (htmlElement.matches("td")) { + const narrowed: HTMLTableCellElement = htmlElement; +} + +if (htmlElement.matches("div")) { + const narrowed: HTMLDivElement = htmlElement; +} + +if (!htmlDivElement.matches("div")) { + htmlDivElement.id; +} + +if (htmlDivElement.matches("object")) { + // @ts-expect-error HTMLDivElement should not narrow to HTMLObjectElement. + const narrowed: HTMLObjectElement = htmlDivElement; +} + +if (!htmlTableCellElement.matches("th")) { + htmlTableCellElement.id; +}