From 7ba28c8972a050e640b61d35af9d2adf0df87747 Mon Sep 17 00:00:00 2001 From: Christian Gonzalez <1581488+christiango@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:52:11 +0000 Subject: [PATCH 1/6] Handle unions in no-postmessage-star-origin --- lib/rules/no-postmessage-star-origin.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 5a76c0e..83d3067 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -9,6 +9,10 @@ const astUtils = require("../ast-utils"); +function isWindowOrAny(type) { + return type === "any" || type === "Window"; +} + module.exports = { meta: { type: "suggestion", @@ -41,7 +45,16 @@ module.exports = { ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); - if (type !== "any" && type !== "Window") { + if (!isWindowOrAny(type)) { + return; + } + + if ( + tsType.isUnion() && + tsType.types + .map((value) => fullTypeChecker.typeToString(value)) + .every((t) => !isWindowOrAny(t)) + ) { return; } } From 3125194866ee5656c14e4620d4b39f1938aa5b9f Mon Sep 17 00:00:00 2001 From: Christian Gonzalez <1581488+christiango@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:12:02 +0000 Subject: [PATCH 2/6] Also add a check for element --- lib/rules/no-inner-html.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/no-inner-html.js b/lib/rules/no-inner-html.js index 24b30f1..4a4330e 100644 --- a/lib/rules/no-inner-html.js +++ b/lib/rules/no-inner-html.js @@ -30,7 +30,7 @@ module.exports = { function mightBeHTMLElement(node) { const type = astUtils.getNodeTypeAsString(fullTypeChecker, node, context); - return type.match(/HTML.*Element/) || type === "any"; + return type.match(/HTML.*Element/) || type === "any" || type == "Element"; } return { From 24f7d0b942a96b68b97f2f9fd33cc588040ff115 Mon Sep 17 00:00:00 2001 From: Christian Gonzalez <1581488+christiango@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:27:49 +0000 Subject: [PATCH 3/6] Fix it properly --- lib/rules/no-postmessage-star-origin.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 83d3067..144c895 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -45,19 +45,15 @@ module.exports = { ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); - if (!isWindowOrAny(type)) { + // Remove some false positives by returning if the type does not contain Union + if (tsType.isUnionOrIntersection()) { + if (tsType.types.every(t=> !isWindowOrAny(fullTypeChecker.typeToString(t)))) { + return; + } + } else if(!isWindowOrAny(type)){ return; } - if ( - tsType.isUnion() && - tsType.types - .map((value) => fullTypeChecker.typeToString(value)) - .every((t) => !isWindowOrAny(t)) - ) { - return; - } - } context.report({ node: node, From a45c29dc6e5e03a807231ded39e1328169e05e7c Mon Sep 17 00:00:00 2001 From: Christian Gonzalez <1581488+christiango@users.noreply.github.com> Date: Mon, 27 Oct 2025 22:32:13 +0000 Subject: [PATCH 4/6] Formatting fix --- lib/rules/no-postmessage-star-origin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 144c895..680e5ae 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -45,15 +45,15 @@ module.exports = { ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); - // Remove some false positives by returning if the type does not contain Union + // Remove some false positives by returning if the type does not contain Window or any if (tsType.isUnionOrIntersection()) { - if (tsType.types.every(t=> !isWindowOrAny(fullTypeChecker.typeToString(t)))) { + if (tsType.types.every((t) => !isWindowOrAny(fullTypeChecker.typeToString(t)))) { return; } - } else if(!isWindowOrAny(type)){ + } else if (!isWindowOrAny(type)) { return; } - + } context.report({ node: node, From 7d2fa04a11aa05bb8fab733029fcf7256097058a Mon Sep 17 00:00:00 2001 From: Christian Gonzalez Date: Wed, 15 Jul 2026 14:28:22 -0400 Subject: [PATCH 5/6] Address Element type review feedback Verify exact Element types originate from lib.dom.d.ts, add regression coverage for local and DOM Element types, and exercise Window unions with strict null checking. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c5c981a3-aec0-4094-8d03-3a49e13df9f2 --- lib/rules/no-inner-html.js | 28 +++++++++++++++++-- lib/rules/no-postmessage-star-origin.js | 2 +- tests/fixtures/ts/tsconfig.json | 3 ++ tests/lib/rules/no-inner-html.js | 21 ++++++++++++++ tests/lib/rules/no-postmessage-star-origin.js | 8 ++++++ 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/lib/rules/no-inner-html.js b/lib/rules/no-inner-html.js index 4a4330e..2a2ed0a 100644 --- a/lib/rules/no-inner-html.js +++ b/lib/rules/no-inner-html.js @@ -8,8 +8,22 @@ "use strict"; +const path = require("path"); const astUtils = require("../ast-utils"); +function isDomElement(type) { + const symbol = type.getSymbol(); + // A local type can also be named Element, so only trust the DOM library declaration. + return ( + symbol && + symbol + .getDeclarations() + ?.some( + (declaration) => path.basename(declaration.getSourceFile().fileName) === "lib.dom.d.ts" + ) + ); +} + module.exports = { meta: { type: "suggestion", @@ -29,8 +43,18 @@ module.exports = { const fullTypeChecker = astUtils.getFullTypeChecker(context); function mightBeHTMLElement(node) { - const type = astUtils.getNodeTypeAsString(fullTypeChecker, node, context); - return type.match(/HTML.*Element/) || type === "any" || type == "Element"; + if (!fullTypeChecker) { + return true; + } + + const tsNode = context.sourceCode.parserServices.esTreeNodeToTSNodeMap.get(node); + const tsType = fullTypeChecker.getTypeAtLocation(tsNode); + const type = fullTypeChecker.typeToString(tsType); + return ( + type.match(/HTML.*Element/) || + type === "any" || + (type === "Element" && isDomElement(tsType)) + ); } return { diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 680e5ae..623267b 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -45,7 +45,7 @@ module.exports = { ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); const type = fullTypeChecker.typeToString(tsType); - // Remove some false positives by returning if the type does not contain Window or any + // Unions such as Window | null must be checked one constituent at a time. if (tsType.isUnionOrIntersection()) { if (tsType.types.every((t) => !isWindowOrAny(fullTypeChecker.typeToString(t)))) { return; diff --git a/tests/fixtures/ts/tsconfig.json b/tests/fixtures/ts/tsconfig.json index ed4dc2f..f33e424 100644 --- a/tests/fixtures/ts/tsconfig.json +++ b/tests/fixtures/ts/tsconfig.json @@ -1,3 +1,6 @@ { + "compilerOptions": { + "strictNullChecks": true + }, "include": ["estree.ts"] } diff --git a/tests/lib/rules/no-inner-html.js b/tests/lib/rules/no-inner-html.js index 34bde71..9bf3b72 100644 --- a/tests/lib/rules/no-inner-html.js +++ b/tests/lib/rules/no-inner-html.js @@ -30,6 +30,18 @@ ruleTester.run(ruleId, rule, { test.innerHTML = test; test.outerHTML = test; ` + }, + { + languageOptions: testUtils.tsLanguageOptions, + code: ` + function main() { + class Element { + innerHTML = ""; + } + const element = new Element(); + element.innerHTML = "test"; + } + ` } ], invalid: [ @@ -48,6 +60,15 @@ ruleTester.run(ruleId, rule, { { messageId: "noInsertAdjacentHTML", line: 5 } ] }, + { + languageOptions: testUtils.tsLanguageOptions, + code: ` + function main(element: Element) { + element.innerHTML = "test"; + } + `, + errors: [{ messageId: "noInnerHtml", line: 3 }] + }, { code: ` element.innerHTML = 'test'; diff --git a/tests/lib/rules/no-postmessage-star-origin.js b/tests/lib/rules/no-postmessage-star-origin.js index 91df84f..951a055 100644 --- a/tests/lib/rules/no-postmessage-star-origin.js +++ b/tests/lib/rules/no-postmessage-star-origin.js @@ -52,6 +52,14 @@ function main() { { messageId: "default", line: 2 }, { messageId: "default", line: 4 } ] + }, + { + languageOptions: testUtils.tsLanguageOptions, + code: ` + declare const target: Window | null; + target.postMessage(message, "*"); + `, + errors: [{ messageId: "default", line: 3 }] } ] }); From eba455eb8baddc02e13a114c22b03e244ad691f5 Mon Sep 17 00:00:00 2001 From: Christian Gonzalez Date: Thu, 16 Jul 2026 15:13:45 -0400 Subject: [PATCH 6/6] Verify Window uses DOM declaration Reject custom types named Window while preserving DOM Window union handling, and share declaration-origin checks between the Window and Element rules. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c5c981a3-aec0-4094-8d03-3a49e13df9f2 --- lib/ast-utils.js | 11 +++++++++++ lib/rules/no-inner-html.js | 17 ++--------------- lib/rules/no-postmessage-star-origin.js | 14 +++++++++----- tests/lib/rules/no-postmessage-star-origin.js | 13 +++++++++++++ 4 files changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/ast-utils.js b/lib/ast-utils.js index 0a97334..51d4bf0 100644 --- a/lib/ast-utils.js +++ b/lib/ast-utils.js @@ -7,6 +7,8 @@ "use strict"; +const path = require("path"); + module.exports = { isTypeScriptParserServices(parserServices) { // Check properties specific to @typescript-eslint/parser @@ -39,6 +41,15 @@ module.exports = { } return "any"; }, + hasDeclarationInFile(type, fileName) { + const symbol = type.getSymbol(); + return Boolean( + symbol && + symbol + .getDeclarations() + ?.some((declaration) => path.basename(declaration.getSourceFile().fileName) === fileName) + ); + }, isDocumentObject(node, context, fullTypeChecker) { if (fullTypeChecker) { const type = this.getNodeTypeAsString(fullTypeChecker, node, context); diff --git a/lib/rules/no-inner-html.js b/lib/rules/no-inner-html.js index 2a2ed0a..36e3fda 100644 --- a/lib/rules/no-inner-html.js +++ b/lib/rules/no-inner-html.js @@ -8,22 +8,8 @@ "use strict"; -const path = require("path"); const astUtils = require("../ast-utils"); -function isDomElement(type) { - const symbol = type.getSymbol(); - // A local type can also be named Element, so only trust the DOM library declaration. - return ( - symbol && - symbol - .getDeclarations() - ?.some( - (declaration) => path.basename(declaration.getSourceFile().fileName) === "lib.dom.d.ts" - ) - ); -} - module.exports = { meta: { type: "suggestion", @@ -53,7 +39,8 @@ module.exports = { return ( type.match(/HTML.*Element/) || type === "any" || - (type === "Element" && isDomElement(tsType)) + // A local type can also be named Element, so only trust the DOM library declaration. + (type === "Element" && astUtils.hasDeclarationInFile(tsType, "lib.dom.d.ts")) ); } diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 623267b..e1ab083 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -9,8 +9,13 @@ const astUtils = require("../ast-utils"); -function isWindowOrAny(type) { - return type === "any" || type === "Window"; +function isWindowOrAny(fullTypeChecker, type) { + const typeName = fullTypeChecker.typeToString(type); + return ( + typeName === "any" || + // A local type can also be named Window, so only trust the DOM library declaration. + (typeName === "Window" && astUtils.hasDeclarationInFile(type, "lib.dom.d.ts")) + ); } module.exports = { @@ -44,13 +49,12 @@ module.exports = { node.callee.object ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); - const type = fullTypeChecker.typeToString(tsType); // Unions such as Window | null must be checked one constituent at a time. if (tsType.isUnionOrIntersection()) { - if (tsType.types.every((t) => !isWindowOrAny(fullTypeChecker.typeToString(t)))) { + if (tsType.types.every((t) => !isWindowOrAny(fullTypeChecker, t))) { return; } - } else if (!isWindowOrAny(type)) { + } else if (!isWindowOrAny(fullTypeChecker, tsType)) { return; } } diff --git a/tests/lib/rules/no-postmessage-star-origin.js b/tests/lib/rules/no-postmessage-star-origin.js index 951a055..a755c35 100644 --- a/tests/lib/rules/no-postmessage-star-origin.js +++ b/tests/lib/rules/no-postmessage-star-origin.js @@ -26,6 +26,19 @@ class WindowLike { function main() { var w: WindowLike = new WindowLike(); w.postMessage('test', '*'); +} + ` + }, + { + languageOptions: testUtils.tsLanguageOptions, + code: ` +function main() { + class Window { + postMessage(): void { + }; + } + const target = new Window(); + target.postMessage('test', '*'); } ` }