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 24b30f1..36e3fda 100644 --- a/lib/rules/no-inner-html.js +++ b/lib/rules/no-inner-html.js @@ -29,8 +29,19 @@ module.exports = { const fullTypeChecker = astUtils.getFullTypeChecker(context); function mightBeHTMLElement(node) { - const type = astUtils.getNodeTypeAsString(fullTypeChecker, node, context); - return type.match(/HTML.*Element/) || type === "any"; + 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" || + // A local type can also be named Element, so only trust the DOM library declaration. + (type === "Element" && astUtils.hasDeclarationInFile(tsType, "lib.dom.d.ts")) + ); } return { diff --git a/lib/rules/no-postmessage-star-origin.js b/lib/rules/no-postmessage-star-origin.js index 5a76c0e..e1ab083 100644 --- a/lib/rules/no-postmessage-star-origin.js +++ b/lib/rules/no-postmessage-star-origin.js @@ -9,6 +9,15 @@ const astUtils = require("../ast-utils"); +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 = { meta: { type: "suggestion", @@ -40,8 +49,12 @@ module.exports = { node.callee.object ); const tsType = fullTypeChecker.getTypeAtLocation(tsNode); - const type = fullTypeChecker.typeToString(tsType); - if (type !== "any" && type !== "Window") { + // Unions such as Window | null must be checked one constituent at a time. + if (tsType.isUnionOrIntersection()) { + if (tsType.types.every((t) => !isWindowOrAny(fullTypeChecker, t))) { + return; + } + } else if (!isWindowOrAny(fullTypeChecker, tsType)) { 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..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', '*'); } ` } @@ -52,6 +65,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 }] } ] });