diff --git a/docs/rules/prefer-global-this.md b/docs/rules/prefer-global-this.md new file mode 100644 index 0000000000..34d565ff9f --- /dev/null +++ b/docs/rules/prefer-global-this.md @@ -0,0 +1,76 @@ +# Prefer `globalThis` over `window`, `self`, and `global` + +πŸ’Ό This rule is enabled in the βœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +πŸ”§ This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +This rule will enforce the use of `globalThis` over `window`, `self`, and `global`. + +However, there are several exceptions that remain permitted: + +1. Certain window/WebWorker-specific APIs, such as `window.innerHeight` and `self.postMessage` +2. Window-specific events, such as `window.addEventListener('resize')` + +The complete list of permitted APIs can be found in the rule's [source code](../../rules/prefer-global-this.js). + +## Examples + +```js +window; // ❌ +globalThis; // βœ… +``` + +```js +window.foo; // ❌ +globalThis.foo; // βœ… +``` + +```js +window[foo]; // ❌ +globalThis[foo]; // βœ… +``` + +```js +global; // ❌ +globalThis; // βœ… +``` + +```js +global.foo; // ❌ +globalThis.foo; // βœ… +``` + +```js +const {foo} = window; // ❌ +const {foo} = globalThis; // βœ… +``` + +```js +window.location; // ❌ +globalThis.location; // βœ… + +window.innerWidth; // βœ… (Window specific API) +window.innerHeight; // βœ… (Window specific API) +``` + +```js +window.navigator; // ❌ +globalThis.navigator; // βœ… +``` + +```js +self.postMessage('Hello'); // βœ… (Web Worker specific API) +self.onmessage = () => {}; // βœ… (Web Worker specific API) +``` + +```js +window.addEventListener('click', () => {}); // ❌ +globalThis.addEventListener('click', () => {}); // βœ… + +window.addEventListener('resize', () => {}); // βœ… (Window specific event) +window.addEventListener('load', () => {}); // βœ… (Window specific event) +window.addEventListener('unload', () => {}); // βœ… (Window specific event) +``` diff --git a/readme.md b/readme.md index 4d2af71eba..65bdec4d12 100644 --- a/readme.md +++ b/readme.md @@ -190,6 +190,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | βœ… | | πŸ’‘ | | [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | βœ… | | | | [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | βœ… | πŸ”§ | πŸ’‘ | +| [prefer-global-this](docs/rules/prefer-global-this.md) | Prefer `globalThis` over `window`, `self`, and `global`. | βœ… | πŸ”§ | | | [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | βœ… | πŸ”§ | πŸ’‘ | | [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | πŸ”§ | | | [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | βœ… | πŸ”§ | | diff --git a/rules/prefer-global-this.js b/rules/prefer-global-this.js new file mode 100644 index 0000000000..ce1104e3e6 --- /dev/null +++ b/rules/prefer-global-this.js @@ -0,0 +1,210 @@ +'use strict'; + +const MESSAGE_ID_ERROR = 'prefer-global-this/error'; +const messages = { + [MESSAGE_ID_ERROR]: 'Prefer `globalThis` over `{{value}}`.', +}; + +const globalIdentifier = new Set(['window', 'self', 'global']); + +const windowSpecificEvents = new Set([ + 'resize', + 'blur', + 'focus', + 'load', + 'scroll', + 'scrollend', + 'wheel', + 'beforeunload', // Browsers might have specific behaviors on exactly `window.onbeforeunload =` + 'message', + 'messageerror', + 'pagehide', + 'pagereveal', + 'pageshow', + 'pageswap', + 'unload', +]); + +/** +Note: What kind of API should be a windows-specific interface? + +1. It's directly related to window (βœ… window.close()) +2. It does NOT work well as globalThis.x or x (βœ… window.frames, window.top) + +Some constructors are occasionally related to window (like Element !== iframe.contentWindow.Element), but they don't need to mention window anyway. + +Please use these criteria to decide whether an API should be added here. Context: https://github.com/sindresorhus/eslint-plugin-unicorn/pull/2410#discussion_r1695312427 +*/ +const windowSpecificAPIs = new Set([ + // Properties and methods + // https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-window-object + 'name', + 'locationbar', + 'menubar', + 'personalbar', + 'scrollbars', + 'statusbar', + 'toolbar', + 'status', + 'close', + 'closed', + 'stop', + 'focus', + 'blur', + 'frames', + 'length', + 'top', + 'opener', + 'parent', + 'frameElement', + 'open', + 'originAgentCluster', + 'postMessage', + + // Events commonly associated with "window" + ...[...windowSpecificEvents].map(event => `on${event}`), + + // To add/remove/dispatch events that are commonly associated with "window" + // https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow + 'addEventListener', + 'removeEventListener', + 'dispatchEvent', + + // https://dom.spec.whatwg.org/#idl-index + 'event', // Deprecated and quirky, best left untouched + + // https://drafts.csswg.org/cssom-view/#idl-index + 'screen', + 'visualViewport', + 'moveTo', + 'moveBy', + 'resizeTo', + 'resizeBy', + 'innerWidth', + 'innerHeight', + 'scrollX', + 'pageXOffset', + 'scrollY', + 'pageYOffset', + 'scroll', + 'scrollTo', + 'scrollBy', + 'screenX', + 'screenLeft', + 'screenY', + 'screenTop', + 'screenWidth', + 'screenHeight', + 'devicePixelRatio', +]); + +const webWorkerSpecificAPIs = new Set([ + // https://html.spec.whatwg.org/multipage/workers.html#the-workerglobalscope-common-interface + 'addEventListener', + 'removeEventListener', + 'dispatchEvent', + + 'self', + 'location', + 'navigator', + 'onerror', + 'onlanguagechange', + 'onoffline', + 'ononline', + 'onrejectionhandled', + 'onunhandledrejection', + + // https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-dedicatedworkerglobalscope-interface + 'name', + 'postMessage', + 'onconnect', +]); + +/** +Check if the node is a window-specific API. + +@param {import('estree').MemberExpression} node +@returns {boolean} +*/ +const isWindowSpecificAPI = node => { + if (node.type !== 'MemberExpression') { + return false; + } + + if (node.object.name !== 'window' || node.property.type !== 'Identifier') { + return false; + } + + if (windowSpecificAPIs.has(node.property.name)) { + if (['addEventListener', 'removeEventListener', 'dispatchEvent'].includes(node.property.name) && node.parent.type === 'CallExpression' && node.parent.callee === node) { + const argument = node.parent.arguments[0]; + return argument && argument.type === 'Literal' && windowSpecificEvents.has(argument.value); + } + + return true; + } + + return false; +}; + +/** +@param {import('estree').Identifier} identifier +@returns {boolean} +*/ +function isComputedMemberExpressionObject(identifier) { + return identifier.parent.type === 'MemberExpression' && identifier.parent.computed && identifier.parent.object === identifier; +} + +/** +Check if the node is a web worker specific API. + +@param {import('estree').MemberExpression} node +@returns {boolean} +*/ +const isWebWorkerSpecificAPI = node => node.type === 'MemberExpression' && node.object.name === 'self' && node.property.type === 'Identifier' && webWorkerSpecificAPIs.has(node.property.name); + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => ({ + * Program(program) { + const scope = context.sourceCode.getScope(program); + + const references = [ + // Variables declared at globals options + ...scope.variables.flatMap(variable => globalIdentifier.has(variable.name) ? variable.references : []), + // Variables not declared at globals options + ...scope.through.filter(reference => globalIdentifier.has(reference.identifier.name)), + ]; + + for (const {identifier} of references) { + if ( + isComputedMemberExpressionObject(identifier) + || isWindowSpecificAPI(identifier.parent) + || isWebWorkerSpecificAPI(identifier.parent) + ) { + continue; + } + + yield { + node: identifier, + messageId: MESSAGE_ID_ERROR, + data: {value: identifier.name}, + fix: fixer => fixer.replaceText(identifier, 'globalThis'), + }; + } + }, +}); + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer `globalThis` over `window`, `self`, and `global`.', + recommended: true, + }, + fixable: 'code', + hasSuggestions: false, + messages, + }, +}; diff --git a/test/package.mjs b/test/package.mjs index 7e68fe5b62..1a52e00628 100644 --- a/test/package.mjs +++ b/test/package.mjs @@ -32,6 +32,7 @@ const RULES_WITHOUT_PASS_FAIL_SECTIONS = new Set([ 'prefer-modern-math-apis', 'prefer-math-min-max', 'consistent-existence-index-check', + 'prefer-global-this', ]); test('Every rule is defined in index file in alphabetical order', t => { diff --git a/test/prefer-global-this.mjs b/test/prefer-global-this.mjs new file mode 100644 index 0000000000..879ea2cb0f --- /dev/null +++ b/test/prefer-global-this.mjs @@ -0,0 +1,186 @@ +import {getTester} from './utils/test.mjs'; +import outdent from 'outdent'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + 'globalThis', + 'globalThis.foo', + 'globalThis[foo]', + 'globalThis.foo()', + 'const { foo } = globalThis', + 'function foo (window) {}', + 'function foo (global) {}', + 'var foo = function foo (window) {}', + 'var foo = function foo (global) {}', + 'var window = {}', + 'let global = {}', + 'const global = {}', + outdent` + function foo (window) { + window.foo(); + } + `, + outdent` + var window = {}; + function foo () { + window.foo(); + } + `, + 'foo.window', + 'foo.global', + 'import window from "xxx"', + 'import * as window from "xxx"', + 'import window, {foo} from "xxx"', + 'export { window } from "xxx"', + 'export * as window from "xxx";', + outdent` + try { + + } catch (window) {} + `, + + // Use window specific APIs + 'window.name = "foo"', + 'window.addEventListener', + 'window.innerWidth', + 'window.innerHeight', + 'self.location', + 'self.navigator', + 'window.addEventListener("resize", () => {})', + 'window.onresize = function () {}', + outdent` + const {window} = jsdom() + window.jQuery = jQuery; + `, + '({ foo: window.name } = {})', + '[window.name] = []', + 'window[foo]', + 'window[title]', + 'window["foo"]', + ], + invalid: [ + 'global', + 'self', + 'window', + 'window.foo', + 'window.foo()', + 'window > 10', + '10 > window', + 'window ?? 10', + '10 ?? window', + 'window.foo = 123', + 'window = 123', + 'obj.a = window', + outdent` + function* gen() { + yield window + } + `, + outdent` + async function gen() { + await window + } + `, + 'window ? foo : bar', + 'foo ? window : bar', + 'foo ? bar : window', + outdent` + function foo() { + return window + } + `, + 'new window()', + outdent` + const obj = { + foo: window.foo, + bar: window.bar, + window: window + } + `, + outdent` + function sequenceTest() { + let x, y; + x = (y = 10, y + 5, window); + console.log(x, y); + } + `, + 'window`Hello ${42} World`', // eslint-disable-line no-template-curly-in-string + 'tag`Hello ${window.foo} World`', // eslint-disable-line no-template-curly-in-string + 'var str = `hello ${window.foo} world!`', // eslint-disable-line no-template-curly-in-string + 'delete window.foo', + '++window', + '++window.foo', + outdent` + for (var attr in window) { + + } + `, + outdent` + for (window.foo = 0; i < 10; window.foo++) { + + } + `, + outdent` + for (const item of window.foo) { + } + `, + outdent` + for (const item of window) { + } + `, + outdent` + switch (window) {} + `, + outdent` + switch (true) { + case window: + break; + } + `, + outdent` + switch (true) { + case window.foo: + break; + } + `, + outdent` + while (window) { + } + `, + 'do {} while (window) {}', + 'if (window) {}', + 'throw window', + 'var foo = window', + outdent` + function foo (name = window) { + + } + `, + 'self.innerWidth', + 'self.innerHeight', + 'window.crypto', + 'window.addEventListener("play", () => {})', + 'window.onplay = function () {}', + 'function greet({ name = window.foo }) {}', + '({ foo: window.foo } = {})', + '[window.foo] = []', + 'foo[window]', + 'foo[window.foo]', + ], +}); + +test.snapshot({ + testerOptions: { + languageOptions: { + globals: {global: 'off', window: 'off', self: 'off'}, + }, + }, + valid: [], + invalid: [ + 'global.global_did_not_declare_in_language_options', + 'window.window_did_not_declare_in_language_options', + 'self.self_did_not_declare_in_language_options', + ], +}); diff --git a/test/snapshots/prefer-global-this.mjs.md b/test/snapshots/prefer-global-this.mjs.md new file mode 100644 index 0000000000..27876df9b4 --- /dev/null +++ b/test/snapshots/prefer-global-this.mjs.md @@ -0,0 +1,1236 @@ +# Snapshot report for `test/prefer-global-this.mjs` + +The actual snapshot is saved in `prefer-global-this.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): global + +> Input + + `␊ + 1 | global␊ + ` + +> Output + + `␊ + 1 | globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | global␊ + | ^^^^^^ Prefer \`globalThis\` over \`global\`.␊ + ` + +## invalid(2): self + +> Input + + `␊ + 1 | self␊ + ` + +> Output + + `␊ + 1 | globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | self␊ + | ^^^^ Prefer \`globalThis\` over \`self\`.␊ + ` + +## invalid(3): window + +> Input + + `␊ + 1 | window␊ + ` + +> Output + + `␊ + 1 | globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(4): window.foo + +> Input + + `␊ + 1 | window.foo␊ + ` + +> Output + + `␊ + 1 | globalThis.foo␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.foo␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(5): window.foo() + +> Input + + `␊ + 1 | window.foo()␊ + ` + +> Output + + `␊ + 1 | globalThis.foo()␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.foo()␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(6): window > 10 + +> Input + + `␊ + 1 | window > 10␊ + ` + +> Output + + `␊ + 1 | globalThis > 10␊ + ` + +> Error 1/1 + + `␊ + > 1 | window > 10␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(7): 10 > window + +> Input + + `␊ + 1 | 10 > window␊ + ` + +> Output + + `␊ + 1 | 10 > globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | 10 > window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(8): window ?? 10 + +> Input + + `␊ + 1 | window ?? 10␊ + ` + +> Output + + `␊ + 1 | globalThis ?? 10␊ + ` + +> Error 1/1 + + `␊ + > 1 | window ?? 10␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(9): 10 ?? window + +> Input + + `␊ + 1 | 10 ?? window␊ + ` + +> Output + + `␊ + 1 | 10 ?? globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | 10 ?? window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(10): window.foo = 123 + +> Input + + `␊ + 1 | window.foo = 123␊ + ` + +> Output + + `␊ + 1 | globalThis.foo = 123␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.foo = 123␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(11): window = 123 + +> Input + + `␊ + 1 | window = 123␊ + ` + +> Output + + `␊ + 1 | globalThis = 123␊ + ` + +> Error 1/1 + + `␊ + > 1 | window = 123␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(12): obj.a = window + +> Input + + `␊ + 1 | obj.a = window␊ + ` + +> Output + + `␊ + 1 | obj.a = globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | obj.a = window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(13): function* gen() { yield window } + +> Input + + `␊ + 1 | function* gen() {␊ + 2 | yield window␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function* gen() {␊ + 2 | yield globalThis␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function* gen() {␊ + > 2 | yield window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | }␊ + ` + +## invalid(14): async function gen() { await window } + +> Input + + `␊ + 1 | async function gen() {␊ + 2 | await window␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | async function gen() {␊ + 2 | await globalThis␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | async function gen() {␊ + > 2 | await window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | }␊ + ` + +## invalid(15): window ? foo : bar + +> Input + + `␊ + 1 | window ? foo : bar␊ + ` + +> Output + + `␊ + 1 | globalThis ? foo : bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | window ? foo : bar␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(16): foo ? window : bar + +> Input + + `␊ + 1 | foo ? window : bar␊ + ` + +> Output + + `␊ + 1 | foo ? globalThis : bar␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo ? window : bar␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(17): foo ? bar : window + +> Input + + `␊ + 1 | foo ? bar : window␊ + ` + +> Output + + `␊ + 1 | foo ? bar : globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo ? bar : window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(18): function foo() { return window } + +> Input + + `␊ + 1 | function foo() {␊ + 2 | return window␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo() {␊ + 2 | return globalThis␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function foo() {␊ + > 2 | return window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | }␊ + ` + +## invalid(19): new window() + +> Input + + `␊ + 1 | new window()␊ + ` + +> Output + + `␊ + 1 | new globalThis()␊ + ` + +> Error 1/1 + + `␊ + > 1 | new window()␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(20): const obj = { foo: window.foo, bar: window.bar, window: window } + +> Input + + `␊ + 1 | const obj = {␊ + 2 | foo: window.foo,␊ + 3 | bar: window.bar,␊ + 4 | window: window␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | const obj = {␊ + 2 | foo: globalThis.foo,␊ + 3 | bar: globalThis.bar,␊ + 4 | window: globalThis␊ + 5 | }␊ + ` + +> Error 1/3 + + `␊ + 1 | const obj = {␊ + > 2 | foo: window.foo,␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | bar: window.bar,␊ + 4 | window: window␊ + 5 | }␊ + ` + +> Error 2/3 + + `␊ + 1 | const obj = {␊ + 2 | foo: window.foo,␊ + > 3 | bar: window.bar,␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 4 | window: window␊ + 5 | }␊ + ` + +> Error 3/3 + + `␊ + 1 | const obj = {␊ + 2 | foo: window.foo,␊ + 3 | bar: window.bar,␊ + > 4 | window: window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 5 | }␊ + ` + +## invalid(21): function sequenceTest() { let x, y; x = (y = 10, y + 5, window); console.log(x, y); } + +> Input + + `␊ + 1 | function sequenceTest() {␊ + 2 | let x, y;␊ + 3 | x = (y = 10, y + 5, window);␊ + 4 | console.log(x, y);␊ + 5 | }␊ + ` + +> Output + + `␊ + 1 | function sequenceTest() {␊ + 2 | let x, y;␊ + 3 | x = (y = 10, y + 5, globalThis);␊ + 4 | console.log(x, y);␊ + 5 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | function sequenceTest() {␊ + 2 | let x, y;␊ + > 3 | x = (y = 10, y + 5, window);␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 4 | console.log(x, y);␊ + 5 | }␊ + ` + +## invalid(22): window`Hello ${42} World` + +> Input + + `␊ + 1 | window\`Hello ${42} World\`␊ + ` + +> Output + + `␊ + 1 | globalThis\`Hello ${42} World\`␊ + ` + +> Error 1/1 + + `␊ + > 1 | window\`Hello ${42} World\`␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(23): tag`Hello ${window.foo} World` + +> Input + + `␊ + 1 | tag\`Hello ${window.foo} World\`␊ + ` + +> Output + + `␊ + 1 | tag\`Hello ${globalThis.foo} World\`␊ + ` + +> Error 1/1 + + `␊ + > 1 | tag\`Hello ${window.foo} World\`␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(24): var str = `hello ${window.foo} world!` + +> Input + + `␊ + 1 | var str = \`hello ${window.foo} world!\`␊ + ` + +> Output + + `␊ + 1 | var str = \`hello ${globalThis.foo} world!\`␊ + ` + +> Error 1/1 + + `␊ + > 1 | var str = \`hello ${window.foo} world!\`␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(25): delete window.foo + +> Input + + `␊ + 1 | delete window.foo␊ + ` + +> Output + + `␊ + 1 | delete globalThis.foo␊ + ` + +> Error 1/1 + + `␊ + > 1 | delete window.foo␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(26): ++window + +> Input + + `␊ + 1 | ++window␊ + ` + +> Output + + `␊ + 1 | ++globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | ++window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(27): ++window.foo + +> Input + + `␊ + 1 | ++window.foo␊ + ` + +> Output + + `␊ + 1 | ++globalThis.foo␊ + ` + +> Error 1/1 + + `␊ + > 1 | ++window.foo␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(28): for (var attr in window) { } + +> Input + + `␊ + 1 | for (var attr in window) {␊ + 2 |␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | for (var attr in globalThis) {␊ + 2 |␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | for (var attr in window) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 |␊ + 3 | }␊ + ` + +## invalid(29): for (window.foo = 0; i < 10; window.foo++) { } + +> Input + + `␊ + 1 | for (window.foo = 0; i < 10; window.foo++) {␊ + 2 |␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | for (globalThis.foo = 0; i < 10; globalThis.foo++) {␊ + 2 |␊ + 3 | }␊ + ` + +> Error 1/2 + + `␊ + > 1 | for (window.foo = 0; i < 10; window.foo++) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 |␊ + 3 | }␊ + ` + +> Error 2/2 + + `␊ + > 1 | for (window.foo = 0; i < 10; window.foo++) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 |␊ + 3 | }␊ + ` + +## invalid(30): for (const item of window.foo) { } + +> Input + + `␊ + 1 | for (const item of window.foo) {␊ + 2 | }␊ + ` + +> Output + + `␊ + 1 | for (const item of globalThis.foo) {␊ + 2 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | for (const item of window.foo) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 | }␊ + ` + +## invalid(31): for (const item of window) { } + +> Input + + `␊ + 1 | for (const item of window) {␊ + 2 | }␊ + ` + +> Output + + `␊ + 1 | for (const item of globalThis) {␊ + 2 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | for (const item of window) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 | }␊ + ` + +## invalid(32): switch (window) {} + +> Input + + `␊ + 1 | switch (window) {}␊ + ` + +> Output + + `␊ + 1 | switch (globalThis) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | switch (window) {}␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(33): switch (true) { case window: break; } + +> Input + + `␊ + 1 | switch (true) {␊ + 2 | case window:␊ + 3 | break;␊ + 4 | }␊ + ` + +> Output + + `␊ + 1 | switch (true) {␊ + 2 | case globalThis:␊ + 3 | break;␊ + 4 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | switch (true) {␊ + > 2 | case window:␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | break;␊ + 4 | }␊ + ` + +## invalid(34): switch (true) { case window.foo: break; } + +> Input + + `␊ + 1 | switch (true) {␊ + 2 | case window.foo:␊ + 3 | break;␊ + 4 | }␊ + ` + +> Output + + `␊ + 1 | switch (true) {␊ + 2 | case globalThis.foo:␊ + 3 | break;␊ + 4 | }␊ + ` + +> Error 1/1 + + `␊ + 1 | switch (true) {␊ + > 2 | case window.foo:␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 3 | break;␊ + 4 | }␊ + ` + +## invalid(35): while (window) { } + +> Input + + `␊ + 1 | while (window) {␊ + 2 | }␊ + ` + +> Output + + `␊ + 1 | while (globalThis) {␊ + 2 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | while (window) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 | }␊ + ` + +## invalid(36): do {} while (window) {} + +> Input + + `␊ + 1 | do {} while (window) {}␊ + ` + +> Output + + `␊ + 1 | do {} while (globalThis) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | do {} while (window) {}␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(37): if (window) {} + +> Input + + `␊ + 1 | if (window) {}␊ + ` + +> Output + + `␊ + 1 | if (globalThis) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | if (window) {}␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(38): throw window + +> Input + + `␊ + 1 | throw window␊ + ` + +> Output + + `␊ + 1 | throw globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | throw window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(39): var foo = window + +> Input + + `␊ + 1 | var foo = window␊ + ` + +> Output + + `␊ + 1 | var foo = globalThis␊ + ` + +> Error 1/1 + + `␊ + > 1 | var foo = window␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(40): function foo (name = window) { } + +> Input + + `␊ + 1 | function foo (name = window) {␊ + 2 |␊ + 3 | }␊ + ` + +> Output + + `␊ + 1 | function foo (name = globalThis) {␊ + 2 |␊ + 3 | }␊ + ` + +> Error 1/1 + + `␊ + > 1 | function foo (name = window) {␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + 2 |␊ + 3 | }␊ + ` + +## invalid(41): self.innerWidth + +> Input + + `␊ + 1 | self.innerWidth␊ + ` + +> Output + + `␊ + 1 | globalThis.innerWidth␊ + ` + +> Error 1/1 + + `␊ + > 1 | self.innerWidth␊ + | ^^^^ Prefer \`globalThis\` over \`self\`.␊ + ` + +## invalid(42): self.innerHeight + +> Input + + `␊ + 1 | self.innerHeight␊ + ` + +> Output + + `␊ + 1 | globalThis.innerHeight␊ + ` + +> Error 1/1 + + `␊ + > 1 | self.innerHeight␊ + | ^^^^ Prefer \`globalThis\` over \`self\`.␊ + ` + +## invalid(43): window.crypto + +> Input + + `␊ + 1 | window.crypto␊ + ` + +> Output + + `␊ + 1 | globalThis.crypto␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.crypto␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(44): window.addEventListener("play", () => {}) + +> Input + + `␊ + 1 | window.addEventListener("play", () => {})␊ + ` + +> Output + + `␊ + 1 | globalThis.addEventListener("play", () => {})␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.addEventListener("play", () => {})␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(45): window.onplay = function () {} + +> Input + + `␊ + 1 | window.onplay = function () {}␊ + ` + +> Output + + `␊ + 1 | globalThis.onplay = function () {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.onplay = function () {}␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(46): function greet({ name = window.foo }) {} + +> Input + + `␊ + 1 | function greet({ name = window.foo }) {}␊ + ` + +> Output + + `␊ + 1 | function greet({ name = globalThis.foo }) {}␊ + ` + +> Error 1/1 + + `␊ + > 1 | function greet({ name = window.foo }) {}␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(47): ({ foo: window.foo } = {}) + +> Input + + `␊ + 1 | ({ foo: window.foo } = {})␊ + ` + +> Output + + `␊ + 1 | ({ foo: globalThis.foo } = {})␊ + ` + +> Error 1/1 + + `␊ + > 1 | ({ foo: window.foo } = {})␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(48): [window.foo] = [] + +> Input + + `␊ + 1 | [window.foo] = []␊ + ` + +> Output + + `␊ + 1 | [globalThis.foo] = []␊ + ` + +> Error 1/1 + + `␊ + > 1 | [window.foo] = []␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(49): foo[window] + +> Input + + `␊ + 1 | foo[window]␊ + ` + +> Output + + `␊ + 1 | foo[globalThis]␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo[window]␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(50): foo[window.foo] + +> Input + + `␊ + 1 | foo[window.foo]␊ + ` + +> Output + + `␊ + 1 | foo[globalThis.foo]␊ + ` + +> Error 1/1 + + `␊ + > 1 | foo[window.foo]␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(1): global.global_did_not_declare_in_language_options + +> Input + + `␊ + 1 | global.global_did_not_declare_in_language_options␊ + ` + +> Output + + `␊ + 1 | globalThis.global_did_not_declare_in_language_options␊ + ` + +> Error 1/1 + + `␊ + > 1 | global.global_did_not_declare_in_language_options␊ + | ^^^^^^ Prefer \`globalThis\` over \`global\`.␊ + ` + +## invalid(2): window.window_did_not_declare_in_language_options + +> Input + + `␊ + 1 | window.window_did_not_declare_in_language_options␊ + ` + +> Output + + `␊ + 1 | globalThis.window_did_not_declare_in_language_options␊ + ` + +> Error 1/1 + + `␊ + > 1 | window.window_did_not_declare_in_language_options␊ + | ^^^^^^ Prefer \`globalThis\` over \`window\`.␊ + ` + +## invalid(3): self.self_did_not_declare_in_language_options + +> Input + + `␊ + 1 | self.self_did_not_declare_in_language_options␊ + ` + +> Output + + `␊ + 1 | globalThis.self_did_not_declare_in_language_options␊ + ` + +> Error 1/1 + + `␊ + > 1 | self.self_did_not_declare_in_language_options␊ + | ^^^^ Prefer \`globalThis\` over \`self\`.␊ + ` diff --git a/test/snapshots/prefer-global-this.mjs.snap b/test/snapshots/prefer-global-this.mjs.snap new file mode 100644 index 0000000000..bb7dac1957 Binary files /dev/null and b/test/snapshots/prefer-global-this.mjs.snap differ