fix(semantic): classify global references per identifier (#25608)

## Summary
- classify global references from each identifier reference rather than unresolved-name membership
- retain configured disabled-global handling in the linter context
- add semantic and no-promise-executor-return regressions for a shadowed `Promise`

## Before
```js
Promise.resolve(); // unresolved global reference

function f(Promise) {
  new Promise((resolve, reject) => 1);
  //  ^ incorrectly classified as global because another `Promise` is unresolved & inserted in `root_unresolved_references`
}
```

## After
```js
Promise.resolve(); // still an unresolved global reference

function f(Promise) {
  new Promise((resolve, reject) => 1);
  //  ^ correctly classified as the local parameter
}
```
This commit is contained in:
camc314 committed 2026-08-13 16:07:19 +00:00
1 parent de9bf67dee
commit 509931bd27
4 files changed
+32 -13

No files matched your search

+2 -2
View File
@@ -8,7 +8,7 @@ use oxc_allocator::Allocator;
use oxc_ast::ast::IdentifierReference;
use oxc_cfg::ControlFlowGraph;
use oxc_diagnostics::{OxcDiagnostic, Severity};
use oxc_semantic::Semantic;
use oxc_semantic::{IsGlobalReference, Semantic};
use oxc_span::Span;
#[cfg(debug_assertions)]
@@ -168,7 +168,7 @@ impl<'a> LintContext<'a> {
/// Checks if the provided identifier is a reference to a global variable.
pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool {
let name = ident.name.as_str();
self.scoping().root_unresolved_references().contains_key(name)
ident.is_global_reference(self.scoping())
&& !self.globals().get(name).is_some_and(|value| *value == GlobalValue::Off)
}
@@ -261,6 +261,7 @@ fn test() {
// globals are not supported in tests.
// ("/* globals Promise:off */ new Promise(function (resolve, reject) { return 1; });", None)
// ("new Promise((resolve, reject) => { return 1; });", None), // { "globals": { "Promise": "off" } }
("Promise.resolve(); function f(Promise) { new Promise((resolve, reject) => 1); }", None),
("let Promise; new Promise(function (resolve, reject) { return 1; });", None),
("function f() { new Promise((resolve, reject) => { return 1; }); var Promise; }", None),
("function f(Promise) { new Promise((resolve, reject) => 1); }", None),
@@ -32,15 +32,6 @@ source: crates/oxc_linter/src/tester.rs
╰────
help: Remove the `new` operator.
⚠ eslint(no-new-wrappers): Do not use `String` as a constructor
╭─[no_new_wrappers.tsx:5:27]
4 │ const String = CustomString;
5 │ const b = new String('foo');
· ─────────────────
6 │ }
╰────
help: Remove the `new` operator.
⚠ eslint(no-new-wrappers): Do not use `String` as a constructor
╭─[no_new_wrappers.tsx:2:21]
1 │
+29 -2
View File
@@ -239,7 +239,7 @@ impl<'a> Semantic<'a> {
let AstKind::IdentifierReference(id) = reference_node.kind() else {
return false;
};
self.scoping.root_unresolved_references().contains_key(&id.name)
id.is_global_reference(&self.scoping)
}
/// Find which scope a symbol is declared in
@@ -262,7 +262,7 @@ impl<'a> Semantic<'a> {
/// Returns `true` if `ident` resolves to a global (unbound) reference.
pub fn is_reference_to_global_variable(&self, ident: &IdentifierReference) -> bool {
self.scoping.root_unresolved_references().contains_key(&ident.name)
ident.is_global_reference(&self.scoping)
}
/// Get the textual name for a semantic reference.
@@ -382,6 +382,33 @@ mod tests {
}
}
#[test]
fn unresolved_reference_is_tracked_per_identifier() {
let source = "Promise.resolve(); function f(Promise) { Promise.resolve(); }";
let allocator = Allocator::default();
let semantic = get_semantic(&allocator, source, SourceType::default());
let nodes = semantic
.scoping()
.references
.iter()
.map(|reference| semantic.nodes.get_node(reference.node_id()))
.collect::<Vec<_>>();
let [global_promise, local_promise] = nodes.as_slice() else { unreachable!() };
// `Promise.resolve()`
assert!(semantic.is_unresolved_reference(global_promise.id()));
assert!(semantic.is_reference_to_global_variable(
global_promise.kind().as_identifier_reference().unwrap()
));
// `function f(Promise) { Promise.resolve(); }`
assert!(!semantic.is_unresolved_reference(local_promise.id()));
assert!(!semantic.is_reference_to_global_variable(
local_promise.kind().as_identifier_reference().unwrap()
));
}
#[test]
fn type_alias_gets_reference() {
let source = "type A = 1; type B = A";