mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-15 06:39:17 +00:00
fix(semantic): detect duplicate private class elements (#26361)
The duplicate class element check stored only the last element for each name. This allowed a public element to hide an earlier private declaration, and a setter to hide an earlier getter (or vice versa):
```js
class DuplicateField {
#x;
x;
#x;
}
class DuplicateGetter {
get #x() {}
set #x(value) {}
get #x() {}
}
```
Both classes were accepted despite declaring the same private field or getter twice. The same problem affected setter/getter/setter sequences.
Key a single map by both the element name and whether it is private, so public elements cannot replace private declarations. This keeps the namespaces separate while reserving map capacity only once. For each private name, retain separate slots for setters and other declarations. Look for a previous declaration in the matching slot first, then fall back to the other slot. This catches repeated getters and setters while still allowing one getter/setter pair when both have the same static status.
The existing diagnostic logic continues to reject private names shared by static and instance elements, including the TypeScript-specific diagnostic. Public elements continue through the existing public-element duplicate check.
This commit is contained in:
1 parent
2289d27099
commit
8b35abdfd9
9 files changed
+73
-15
No files matched your search
@@ -7,7 +7,7 @@ use oxc_ecmascript::{BoundNames, IsSimpleParameterList, PropName};
|
||||
use oxc_span::{GetSpan, ModuleKind, Span, best_match};
|
||||
use oxc_str::Ident;
|
||||
use oxc_syntax::{
|
||||
class::ClassId,
|
||||
class::{ClassId, ElementKind},
|
||||
number::NumberBase,
|
||||
operator::UnaryOperator,
|
||||
scope::{ScopeFlags, ScopeId},
|
||||
@@ -88,7 +88,20 @@ pub fn check_duplicate_class_elements(ctx: &SemanticBuilder<'_>) {
|
||||
let mut defined_elements =
|
||||
FxHashMap::with_capacity_and_hasher(elements.len(), FxBuildHasher);
|
||||
for (element_id, element) in elements.iter_enumerated() {
|
||||
if let Some(prev_element_id) = defined_elements.insert(&element.name, element_id) {
|
||||
// Public and private names belong to separate namespaces.
|
||||
let previous =
|
||||
defined_elements.entry((&element.name, element.is_private)).or_insert([None; 2]);
|
||||
let prev_element_id = if element.is_private {
|
||||
// Keep setters separate so a valid getter/setter pair cannot hide a
|
||||
// repeated accessor.
|
||||
let index = usize::from(element.kind.contains(ElementKind::Setter));
|
||||
let prev_element_id = previous[index].or(previous[1 - index]);
|
||||
previous[index] = Some(element_id);
|
||||
prev_element_id
|
||||
} else {
|
||||
previous[0].replace(element_id)
|
||||
};
|
||||
if let Some(prev_element_id) = prev_element_id {
|
||||
let prev_element = &elements[prev_element_id];
|
||||
|
||||
let mut is_duplicate = element.is_private == prev_element.is_private
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class DuplicateField { #x; x; #x; }
|
||||
|
||||
class DuplicateGetter { get #x() {} set #x(value) {} get #x() {} }
|
||||
|
||||
class DuplicateSetter { set #x(value) {} get #x() {} set #x(value) {} }
|
||||
@@ -0,0 +1,12 @@
|
||||
class Instance {
|
||||
#x;
|
||||
x;
|
||||
get #value() {}
|
||||
value;
|
||||
set #value(value) {}
|
||||
}
|
||||
class Static {
|
||||
static set #value(value) {}
|
||||
static value;
|
||||
static get #value() {}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
codegen_misc Summary:
|
||||
AST Parsed : 83/83 (100.00%)
|
||||
Positive Passed: 83/83 (100.00%)
|
||||
AST Parsed : 84/84 (100.00%)
|
||||
Positive Passed: 84/84 (100.00%)
|
||||
@@ -1,3 +1,3 @@
|
||||
formatter_misc Summary:
|
||||
AST Parsed : 83/83 (100.00%)
|
||||
Positive Passed: 83/83 (100.00%)
|
||||
AST Parsed : 84/84 (100.00%)
|
||||
Positive Passed: 84/84 (100.00%)
|
||||
@@ -1,6 +1,6 @@
|
||||
lexer_misc Summary:
|
||||
AST Parsed : 253/253 (100.00%)
|
||||
Positive Passed: 251/253 (99.21%)
|
||||
AST Parsed : 255/255 (100.00%)
|
||||
Positive Passed: 253/255 (99.22%)
|
||||
Tokens: tasks/coverage/misc/pass/oxc-2674.tsx
|
||||
|
||||
Tokens: tasks/coverage/misc/pass/swc-7187.ts
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
parser_misc Summary:
|
||||
AST Parsed : 83/83 (100.00%)
|
||||
Positive Passed: 83/83 (100.00%)
|
||||
Negative Passed: 170/170 (100.00%)
|
||||
AST Parsed : 84/84 (100.00%)
|
||||
Positive Passed: 84/84 (100.00%)
|
||||
Negative Passed: 171/171 (100.00%)
|
||||
|
||||
× Cannot assign to 'arguments' in strict mode
|
||||
╭─[misc/fail/arguments-eval-ambient.ts:2:13]
|
||||
@@ -264,6 +264,34 @@ Negative Passed: 170/170 (100.00%)
|
||||
If you're in the middle of a rebase, the top section is the code being rebased onto and the bottom section is the code coming from the current commit being rebased.
|
||||
If you have nested conflicts, resolve the outermost conflict first.
|
||||
|
||||
× Identifier `#x` has already been declared
|
||||
╭─[misc/fail/duplicate-private-declarations.js:1:24]
|
||||
1 │ class DuplicateField { #x; x; #x; }
|
||||
· ─┬ ─┬
|
||||
· │ ╰── It can not be redeclared here
|
||||
· ╰── `#x` has already been declared here
|
||||
2 │
|
||||
╰────
|
||||
|
||||
× Identifier `#x` has already been declared
|
||||
╭─[misc/fail/duplicate-private-declarations.js:3:29]
|
||||
2 │
|
||||
3 │ class DuplicateGetter { get #x() {} set #x(value) {} get #x() {} }
|
||||
· ─┬ ─┬
|
||||
· │ ╰── It can not be redeclared here
|
||||
· ╰── `#x` has already been declared here
|
||||
4 │
|
||||
╰────
|
||||
|
||||
× Identifier `#x` has already been declared
|
||||
╭─[misc/fail/duplicate-private-declarations.js:5:29]
|
||||
4 │
|
||||
5 │ class DuplicateSetter { set #x(value) {} get #x() {} set #x(value) {} }
|
||||
· ─┬ ─┬
|
||||
· │ ╰── It can not be redeclared here
|
||||
· ╰── `#x` has already been declared here
|
||||
╰────
|
||||
|
||||
× '0'-prefixed octal literals and octal escape sequences are deprecated
|
||||
╭─[misc/fail/escape-00.js:1:25]
|
||||
1 │ export const escape00 = "\00";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
semantic_misc Summary:
|
||||
AST Parsed : 83/83 (100.00%)
|
||||
Positive Passed: 79/83 (95.18%)
|
||||
AST Parsed : 84/84 (100.00%)
|
||||
Positive Passed: 80/84 (95.24%)
|
||||
semantic Error: tasks/coverage/misc/pass/declare-let-private.ts
|
||||
Bindings mismatch:
|
||||
after transform: ScopeId(0): ["private"]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
transformer_misc Summary:
|
||||
AST Parsed : 83/83 (100.00%)
|
||||
Positive Passed: 83/83 (100.00%)
|
||||
AST Parsed : 84/84 (100.00%)
|
||||
Positive Passed: 84/84 (100.00%)
|
||||
Reference in new issue
Block a user