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/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. 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; +}