diff --git a/src/helpers.ts b/src/helpers.ts index ad451acc..62c1b3f4 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,6 @@ /* eslint no-nested-ternary: off */ import browserslist from "browserslist"; +import globals from "globals"; import { AstNodeTypes, TargetNameMappings } from "./constants"; import { AstMetadataApiWithTargetsResolver, @@ -165,6 +166,8 @@ function protoChainFromMemberExpression(node: ESLintNode): string[] { return [...protoChain, node.property!.name]; } +const browserGlobals = new Set(Object.keys(globals.browser)); + export function lintMemberExpression( context: Context, handleFailingRule: HandleFailingRule, @@ -200,11 +203,20 @@ export function lintMemberExpression( } else { const objectName = node.object.name; const propertyName = node.property.name; - const failingRule = rules.find( - (rule) => - rule.object.toLowerCase() === objectName.toLowerCase() && + const isBrowserGlobal = browserGlobals.has(objectName); + const objectNameLower = objectName.toLowerCase(); + + const failingRule = rules.find((rule) => { + // Match case-insensitively IF the objectName was case-sentively found in browserGlobals + const objectNameMatches = isBrowserGlobal + ? rule.object.toLowerCase() === objectNameLower + : rule.object === objectName; + return ( + objectNameMatches && (rule.property == null || rule.property === propertyName) - ); + ); + }); + if (failingRule) checkNotInsideIfStatementAndReport( context, diff --git a/test/e2e.spec.ts b/test/e2e.spec.ts index 3eb4b6d1..b4c9a2f1 100644 --- a/test/e2e.spec.ts +++ b/test/e2e.spec.ts @@ -93,6 +93,51 @@ ruleTester.run("compat", rule, { code: "document.fonts()", settings: { browsers: ["edge 79"] }, }, + { + code: ` + import * as serviceWorker from './serviceWorker'; + serviceWorker.register(false); + `, + settings: { browsers: ["chrome 52", "android 145"] }, + }, + { + code: ` + navigator.permissions + .query({ name: 'local-network-access' }) + .then((permissionStatus) => { + permissionStatus.addEventListener('change', () => {}); + }); + `, + settings: { browsers: ["chrome 52", "android 145"] }, + }, + { + code: ` + const abortController = new AbortController(); + abortController.abort(); + `, + settings: { browsers: ["chrome 70"] }, + }, + { + code: ` + const mutationObserver = new MutationObserver(() => {}); + mutationObserver.observe(document.body, { childList: true }); + `, + settings: { browsers: ["chrome 52"] }, + }, + { + code: ` + const intersectionObserver = new IntersectionObserver(() => {}); + intersectionObserver.observe(document.body); + `, + settings: { browsers: ["chrome 70"] }, + }, + { + code: ` + const IntersectionObserver = "test"; + IntersectionObserver.trim(); + `, + settings: { browsers: ["chrome 30"] }, + }, // Import cases { code: ` @@ -730,5 +775,119 @@ ruleTester.run("compat", rule, { }, ], }, + { + code: "[].includes()", + settings: { browsers: ["ie 11"] }, + errors: [ + { + message: "Array.includes() is not supported in IE 11", + }, + ], + }, + { + code: "'strsd'.includes()", + settings: { browsers: ["ie 11"] }, + errors: [ + { + message: "String.includes() is not supported in IE 11", + }, + ], + }, + { + code: "[1, 2, [3, 4]].flat()", + settings: { browsers: ["ie 11"] }, + errors: [ + { + message: "Array.flat() is not supported in IE 11", + }, + ], + }, + { + code: "[1,2,3].flatMap(x => [x, x])", + settings: { browsers: ["chrome 68"] }, + errors: [ + { + message: "Array.flatMap() is not supported in Chrome 68", + }, + ], + }, + { + code: "Object.fromEntries([])", + settings: { browsers: ["chrome 72"] }, + errors: [ + { + message: "Object.fromEntries() is not supported in Chrome 72", + }, + ], + }, + { + code: "'text'.replaceAll('x', 's')", + settings: { browsers: ["chrome 84"] }, + errors: [ + { + message: "String.replaceAll() is not supported in Chrome 84", + }, + ], + }, + { + code: `navigator.serviceWorker.register("/service_worker.js");`, + settings: { browsers: ["chrome 39"] }, + errors: [ + { + message: "navigator.serviceWorker() is not supported in Chrome 39", + }, + ], + }, + { + code: ` + const abortController = new AbortController(); + abortController.abort(); + `, + settings: { browsers: ["chrome 65"] }, + errors: [ + { + message: "AbortController is not supported in Chrome 65", + }, + ], + }, + { + code: ` + const mutationObserver = new MutationObserver(() => {}); + mutationObserver.observe(document.body, { childList: true }); + `, + settings: { browsers: ["chrome 25"] }, + errors: [ + { + message: "MutationObserver is not supported in Chrome 25", + }, + ], + }, + { + code: ` + const intersectionObserver = new IntersectionObserver(() => {}); + intersectionObserver.observe(document.body); + `, + settings: { browsers: ["chrome 50"] }, + errors: [ + { + message: "IntersectionObserver is not supported in Chrome 50", + }, + ], + }, + { + code: ` + navigator.permissions + .query({ name: 'local-network-access' }) + .then((permissionStatus) => { + permissionStatus.addEventListener('change', () => {}); + }); + `, + settings: { browsers: ["chrome 41"] }, + errors: [ + { + message: "navigator.permissions() is not supported in Chrome 41", + }, + ], + }, ], });