mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
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:
1 parent
de9bf67dee
commit
509931bd27
4 files changed
+32
-13
No files matched your search
@@ -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 │
|
||||
|
||||
@@ -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";
|
||||
|
||||
Reference in new issue
Block a user