From 4b7baef5c142b2e975e5a365dfb5576786ee6ae0 Mon Sep 17 00:00:00 2001 From: uhyo Date: Tue, 4 Mar 2025 23:32:05 +0900 Subject: [PATCH 1/2] fix: improve isPartialReplacement logic --- build/logic/generate.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/build/logic/generate.ts b/build/logic/generate.ts index 01cbbf9..5582ad4 100644 --- a/build/logic/generate.ts +++ b/build/logic/generate.ts @@ -335,9 +335,11 @@ function isPartialReplacement( const rhc = replacementDecl.heritageClauses; if ( interfaceDecl.heritageClauses.some((heritageClause, index) => { - return ( - heritageClause.getFullText(originalFile) !== - rhc[index].getFullText(betterFile) + return !heritageClauseEquals( + heritageClause, + rhc[index], + originalFile, + betterFile, ); }) ) { @@ -352,6 +354,29 @@ function isPartialReplacement( return true; } +function heritageClauseEquals( + left: ts.HeritageClause, + right: ts.HeritageClause, + leftSourceFile: ts.SourceFile, + rightSourceFile: ts.SourceFile, +): boolean { + if (left.token !== right.token) { + return false; + } + if (left.types.length !== right.types.length) { + return false; + } + for (let i = 0; i < left.types.length; i++) { + if ( + left.types[i].getFullText(leftSourceFile).trim() !== + right.types[i].getFullText(rightSourceFile).trim() + ) { + return false; + } + } + return true; +} + /** * Print an interface declaration where members may be from * mixed source files. From 9d0ef3a1d1912a062ef66d27d385015b1b03a025 Mon Sep 17 00:00:00 2001 From: uhyo Date: Tue, 4 Mar 2025 23:46:14 +0900 Subject: [PATCH 2/2] fix: correct return type of `document.getElementById` from `HTMLElement | null` to `Element | null` to account for more general situations like `SVGElement` --- .clinerules | 11 +++++++++++ CHANGELOG.md | 4 ++++ docs/diff/dom.generated.d.ts.md | 22 ++++++++++++++++++++++ generated/lib.dom.d.ts | 10 ++++++++-- lib/lib.dom.d.ts | 19 +++++++++++++++++++ tests/src/dom.ts | 13 +++++++++++++ 6 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 .clinerules diff --git a/.clinerules b/.clinerules new file mode 100644 index 0000000..067220c --- /dev/null +++ b/.clinerules @@ -0,0 +1,11 @@ +# Project Structure + +This project, better-ts-lib, is a project that provides an alternative to TypeScript's standard library. The main ingredients of this project are in the `lib/` directory which contains incremental differences to the standard library. + +This project is structured differently from ordinary TypeScript projects. Carefully read CONTRIBUTING.md to understand how to modify the type definitions, how to build the project, and how to test the project. + +# Restrictions + +## Original TypeScript type definitions + +Please do not try to read the original type definitions from TypeScript. Those may be very large and you cannot read it. Instead, ask the user to provide relevant code snippets from the original type definitions. diff --git a/CHANGELOG.md b/CHANGELOG.md index 898ff91..5727b60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- fix: correct return type of `document.getElementById` from `HTMLElement | null` to `Element | null` to account for more general situations like `SVGElement` + ## v2.10.1 - fix: more generic types for `Promise.then` and `Promise.catch` (https://github.com/uhyo/better-typescript-lib/pull/60) diff --git a/docs/diff/dom.generated.d.ts.md b/docs/diff/dom.generated.d.ts.md index d456897..e761a55 100644 --- a/docs/diff/dom.generated.d.ts.md +++ b/docs/diff/dom.generated.d.ts.md @@ -51,6 +51,28 @@ Index: dom.generated.d.ts } declare var CustomStateSet: { +@@ -8375,9 +8380,9 @@ + /** + * Returns a reference to the first object with the specified value of the ID attribute. + * @param elementId String that specifies the ID value. + */ +- getElementById(elementId: string): HTMLElement | null; ++ getElementById(elementId: string): Element | null; + /** + * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. + * + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/getElementsByClassName) +@@ -8563,9 +8568,9 @@ + * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DocumentFragment) + */ + interface DocumentFragment extends Node, NonElementParentNode, ParentNode { + readonly ownerDocument: Document; +- getElementById(elementId: string): HTMLElement | null; ++ getElementById(elementId: string): Element | null; + } + + declare var DocumentFragment: { + prototype: DocumentFragment; @@ -9378,11 +9383,11 @@ }; diff --git a/generated/lib.dom.d.ts b/generated/lib.dom.d.ts index 1da4d44..684742a 100644 --- a/generated/lib.dom.d.ts +++ b/generated/lib.dom.d.ts @@ -8386,7 +8386,7 @@ interface Document * Returns a reference to the first object with the specified value of the ID attribute. * @param elementId String that specifies the ID value. */ - getElementById(elementId: string): HTMLElement | null; + getElementById(elementId: string): Element | null; /** * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. * @@ -8559,6 +8559,11 @@ interface Document options?: boolean | EventListenerOptions, ): void; } +// /** +// * Returns a reference to the first object with the specified value of the ID attribute. +// * @param elementId String that specifies the ID value. +// */ +// getElementById(elementId: string): HTMLElement | null; declare var Document: { prototype: Document; @@ -8574,8 +8579,9 @@ declare var Document: { */ interface DocumentFragment extends Node, NonElementParentNode, ParentNode { readonly ownerDocument: Document; - getElementById(elementId: string): HTMLElement | null; + getElementById(elementId: string): Element | null; } +// getElementById(elementId: string): HTMLElement | null; declare var DocumentFragment: { prototype: DocumentFragment; diff --git a/lib/lib.dom.d.ts b/lib/lib.dom.d.ts index bcee5ef..986bc4a 100644 --- a/lib/lib.dom.d.ts +++ b/lib/lib.dom.d.ts @@ -229,3 +229,22 @@ interface CustomStateSet { thisArg?: This, ): void; } + +interface Document + extends Node, + DocumentOrShadowRoot, + FontFaceSource, + GlobalEventHandlers, + NonElementParentNode, + ParentNode, + XPathEvaluatorBase { + /** + * Returns a reference to the first object with the specified value of the ID attribute. + * @param elementId String that specifies the ID value. + */ + getElementById(elementId: string): Element | null; +} + +interface DocumentFragment extends Node, NonElementParentNode, ParentNode { + getElementById(elementId: string): Element | null; +} diff --git a/tests/src/dom.ts b/tests/src/dom.ts index c0b9617..f1aad73 100644 --- a/tests/src/dom.ts +++ b/tests/src/dom.ts @@ -148,3 +148,16 @@ const test = async (url: string) => { // @ts-expect-error item.append("a"); } + +// document.getElementById +{ + const element = document.getElementById("test"); + expectType(element); + + // Verify that the return type is not specifically HTMLElement + expectNotType(element); + + // Verify that SVGElement is assignable to the return type (without type assertion) + const svgElement: SVGElement = {} as SVGElement; + const elementOrNull: typeof element = svgElement; +}