From 556dc7bcda7fbf87fa5c847beb4f414516e2c950 Mon Sep 17 00:00:00 2001 From: Mikhail Baev Date: Sun, 11 Jan 2026 17:00:29 +0500 Subject: [PATCH 1/5] feat(linter): add fixer for `unicorn/prefer-global-this` rule --- .../src/rules/unicorn/prefer_global_this.rs | 209 +++++++++++++++++- 1 file changed, 206 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs index beb1f738ec47b..3ea658bd97a05 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs @@ -2,6 +2,7 @@ use oxc_ast::{AstKind, ast::Expression}; use oxc_diagnostics::OxcDiagnostic; use oxc_macros::declare_oxc_lint; use oxc_span::{GetSpan, Span}; +use oxc_syntax::operator::UnaryOperator; use crate::{AstNode, context::LintContext, rule::Rule}; @@ -55,7 +56,7 @@ declare_oxc_lint!( PreferGlobalThis, unicorn, style, - pending + suggestion ); impl Rule for PreferGlobalThis { @@ -100,8 +101,30 @@ impl Rule for PreferGlobalThis { } } - ctx.diagnostic(prefer_global_this_diagnostic(ident.span)); + let is_typeof = is_typeof_legacy_global(node, ctx); + let replacement = if is_typeof { + format!("globalThis.{}", ident.name) + } else { + "globalThis".to_string() + }; + + ctx.diagnostic_with_suggestion(prefer_global_this_diagnostic(ident.span), |fixer| { + fixer.replace(ident.span, replacement) + }); + } +} + +fn is_typeof_legacy_global(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool { + if let AstKind::UnaryExpression(unary) = ctx.nodes().parent_kind(node.id()) { + if unary.operator == UnaryOperator::Typeof { + if let Expression::Identifier(arg_ident) = &unary.argument { + if let AstKind::IdentifierReference(node_ident) = node.kind() { + return arg_ident.span == node_ident.span; + } + } + } } + false } /// `window[foo]`, `self[bar]`, etc. are allowed. @@ -367,5 +390,185 @@ fn test() { "self.self_did_not_declare_in_language_options", ]; - Tester::new(PreferGlobalThis::NAME, PreferGlobalThis::PLUGIN, pass, fail).test_and_snapshot(); + let fix = vec![ + ("global", "globalThis"), + ("self", "globalThis"), + ("window", "globalThis"), + ("window.foo", "globalThis.foo"), + // aaaaa + ("window.foo()", "globalThis.foo()"), + ("window > 10", "globalThis > 10"), + ("10 > window", "10 > globalThis"), + ("window ?? 10", "globalThis ?? 10"), + ("10 ?? window", "10 ?? globalThis"), + ("window.foo = 123", "globalThis.foo = 123"), + ("window = 123", "globalThis = 123"), + ("obj.a = window", "obj.a = globalThis"), + ( + "function* gen() { + yield window + }", + "function* gen() { + yield globalThis + }", + ), + ( + "async function gen() { + await window + }", + "async function gen() { + await globalThis + }", + ), + ("window ? foo : bar", "globalThis ? foo : bar"), + ("foo ? window : bar", "foo ? globalThis : bar"), + ("foo ? bar : window", "foo ? bar : globalThis"), + ( + "function foo() { + return window + }", + "function foo() { + return globalThis + }", + ), + ("new window()", "new globalThis()"), + ( + "const obj = { + foo: window.foo, + bar: window.bar, + window: window + }", + "const obj = { + foo: globalThis.foo, + bar: globalThis.bar, + window: globalThis + }", + ), + ( + "function sequenceTest() { + let x, y; + x = (y = 10, y + 5, window); + console.log(x, y); + }", + "function sequenceTest() { + let x, y; + x = (y = 10, y + 5, globalThis); + console.log(x, y); + }", + ), + ("window`Hello ${42} World`", "globalThis`Hello ${42} World`"), + ("tag`Hello ${window.foo} World`", "tag`Hello ${globalThis.foo} World`"), + ("var str = `hello ${window.foo} world!`", "var str = `hello ${globalThis.foo} world!`"), + ("delete window.foo", "delete globalThis.foo"), + ("++window", "++globalThis"), + ("++window.foo", "++globalThis.foo"), + ( + "for (var attr in window) { + }", + "for (var attr in globalThis) { + }", + ), + ( + "for (window.foo = 0; i < 10; window.foo++) { + }", + "for (globalThis.foo = 0; i < 10; globalThis.foo++) { + }", + ), + ( + "for (const item of window.foo) { + }", + "for (const item of globalThis.foo) { + }", + ), + ( + "for (const item of window) { + }", + "for (const item of globalThis) { + }", + ), + ("switch (window) {}", "switch (globalThis) {}"), + ( + "switch (true) { + case window: + break; + }", + "switch (true) { + case globalThis: + break; + }", + ), + ( + "switch (true) { + case window.foo: + break; + }", + "switch (true) { + case globalThis.foo: + break; + }", + ), + ( + "while (window) { + }", + "while (globalThis) { + }", + ), + ("do {} while (window) {}", "do {} while (globalThis) {}"), + ("if (window) {}", "if (globalThis) {}"), + ("throw window", "throw globalThis"), + ("var foo = window", "var foo = globalThis"), + ( + "function foo (name = window) { + }", + "function foo (name = globalThis) { + }", + ), + ("self.innerWidth", "globalThis.innerWidth"), + ("self.innerHeight", "globalThis.innerHeight"), + ("window.crypto", "globalThis.crypto"), + ( + r#"window.addEventListener("play", () => {})"#, + r#"globalThis.addEventListener("play", () => {})"#, + ), + ("window.onplay = function () {}", "globalThis.onplay = function () {}"), + ( + "function greet({ name = window.foo }) {}", + "function greet({ name = globalThis.foo }) {}", + ), + ("({ foo: window.foo } = {})", "({ foo: globalThis.foo } = {})"), + ("[window.foo] = []", "[globalThis.foo] = []"), + ("foo[window]", "foo[globalThis]"), + ("foo[window.foo]", "foo[globalThis.foo]"), + (r#"typeof window !== "undefined""#, r#"typeof globalThis.window !== "undefined""#), + (r#"typeof self !== "undefined""#, r#"typeof globalThis.self !== "undefined""#), + (r#"typeof global !== "undefined""#, r#"typeof globalThis.global !== "undefined""#), + ( + r#"typeof window.something === "function""#, + r#"typeof globalThis.something === "function""#, + ), + ( + r#"typeof self.something === "function""#, + r#"typeof globalThis.something === "function""#, + ), + ( + r#"typeof global.something === "function""#, + r#"typeof globalThis.something === "function""#, + ), + ( + "global.global_did_not_declare_in_language_options", + "globalThis.global_did_not_declare_in_language_options", + ), + ( + "window.window_did_not_declare_in_language_options", + "globalThis.window_did_not_declare_in_language_options", + ), + ( + "self.self_did_not_declare_in_language_options", + "globalThis.self_did_not_declare_in_language_options", + ), + ]; + + Tester::new(PreferGlobalThis::NAME, PreferGlobalThis::PLUGIN, pass, fail) + .expect_fix(fix) + .test_and_snapshot(); } From aff8ef684d0c169fab976125db7cdb92da6feebe Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:06:32 +0000 Subject: [PATCH 2/5] [autofix.ci] apply automated fixes --- crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs index 3ea658bd97a05..2a15a943893cc 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs @@ -102,11 +102,8 @@ impl Rule for PreferGlobalThis { } let is_typeof = is_typeof_legacy_global(node, ctx); - let replacement = if is_typeof { - format!("globalThis.{}", ident.name) - } else { - "globalThis".to_string() - }; + let replacement = + if is_typeof { format!("globalThis.{}", ident.name) } else { "globalThis".to_string() }; ctx.diagnostic_with_suggestion(prefer_global_this_diagnostic(ident.span), |fixer| { fixer.replace(ident.span, replacement) From 82adf776ec80bcc04c7f7bf30daac2f3393eaf0b Mon Sep 17 00:00:00 2001 From: Mikhail Baev Date: Sun, 11 Jan 2026 17:15:44 +0500 Subject: [PATCH 3/5] fix copilot suggestion --- .../src/rules/unicorn/prefer_global_this.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs index 2a15a943893cc..a4d5769875259 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs @@ -112,15 +112,11 @@ impl Rule for PreferGlobalThis { } fn is_typeof_legacy_global(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool { - if let AstKind::UnaryExpression(unary) = ctx.nodes().parent_kind(node.id()) { - if unary.operator == UnaryOperator::Typeof { - if let Expression::Identifier(arg_ident) = &unary.argument { - if let AstKind::IdentifierReference(node_ident) = node.kind() { - return arg_ident.span == node_ident.span; - } + if let AstKind::UnaryExpression(unary) = ctx.nodes().parent_kind(node.id()) + && unary.operator == UnaryOperator::Typeof + && let Expression::Identifier(arg_ident) = &unary.argument { + return arg_ident.span == node.span(); } - } - } false } From 876fe4f9cbd57794aceb2fd12b7bad85ca6581a4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 12:16:54 +0000 Subject: [PATCH 4/5] [autofix.ci] apply automated fixes --- crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs index a4d5769875259..b12d2c4a1cd02 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs @@ -114,9 +114,10 @@ impl Rule for PreferGlobalThis { fn is_typeof_legacy_global(node: &AstNode<'_>, ctx: &LintContext<'_>) -> bool { if let AstKind::UnaryExpression(unary) = ctx.nodes().parent_kind(node.id()) && unary.operator == UnaryOperator::Typeof - && let Expression::Identifier(arg_ident) = &unary.argument { - return arg_ident.span == node.span(); - } + && let Expression::Identifier(arg_ident) = &unary.argument + { + return arg_ident.span == node.span(); + } false } From c339735260d7c11ba885706ef12daf98ae6ab974 Mon Sep 17 00:00:00 2001 From: Cameron Clark Date: Sun, 11 Jan 2026 13:51:33 +0000 Subject: [PATCH 5/5] u --- .../src/rules/unicorn/prefer_global_this.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs index b12d2c4a1cd02..b3ad720fc6f7c 100644 --- a/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs +++ b/crates/oxc_linter/src/rules/unicorn/prefer_global_this.rs @@ -101,11 +101,13 @@ impl Rule for PreferGlobalThis { } } - let is_typeof = is_typeof_legacy_global(node, ctx); - let replacement = - if is_typeof { format!("globalThis.{}", ident.name) } else { "globalThis".to_string() }; - ctx.diagnostic_with_suggestion(prefer_global_this_diagnostic(ident.span), |fixer| { + let is_typeof = is_typeof_legacy_global(node, ctx); + let replacement = if is_typeof { + format!("globalThis.{}", ident.name) + } else { + "globalThis".to_string() + }; fixer.replace(ident.span, replacement) }); } @@ -389,7 +391,6 @@ fn test() { ("self", "globalThis"), ("window", "globalThis"), ("window.foo", "globalThis.foo"), - // aaaaa ("window.foo()", "globalThis.foo()"), ("window > 10", "globalThis > 10"), ("10 > window", "10 > globalThis"),