diff --git a/crates/oxc_semantic/src/binder.rs b/crates/oxc_semantic/src/binder.rs new file mode 100644 index 000000000..5d19c81b0 --- /dev/null +++ b/crates/oxc_semantic/src/binder.rs @@ -0,0 +1,25 @@ +//! Declare symbol for `BindingIdentifier`s + +#[allow(clippy::wildcard_imports)] +use oxc_ast::ast::*; + +use crate::{symbol::SymbolFlags, SemanticBuilder}; + +pub trait Binder { + fn bind(&self, _builder: &mut SemanticBuilder) {} +} + +impl<'a> Binder for Class<'a> { + fn bind(&self, builder: &mut SemanticBuilder) { + if let Some(ident) = self.id.as_ref() + && self.r#type == ClassType::ClassDeclaration && !self.modifiers.contains(ModifierKind::Declare) { + builder.declare_symbol( + &ident.name, + ident.span, + builder.scope.current_scope_id, + SymbolFlags::Class , + SymbolFlags::ClassExcludes, + ); + } + } +} diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs index 6386db0b8..37d18dfd6 100644 --- a/crates/oxc_semantic/src/builder.rs +++ b/crates/oxc_semantic/src/builder.rs @@ -9,6 +9,7 @@ use oxc_ast::{ast::*, visit::Visit, AstKind, Atom, GetSpan, SourceType, Span, Tr use oxc_diagnostics::{Error, Redeclaration}; use crate::{ + binder::Binder, node::{AstNodeId, AstNodes, NodeFlags, SemanticNode}, scope::{ScopeBuilder, ScopeId}, symbol::{Reference, ReferenceFlag, SymbolFlags, SymbolId, SymbolTable}, @@ -16,19 +17,19 @@ use crate::{ }; pub struct SemanticBuilder<'a> { - source_type: SourceType, + pub source_type: SourceType, /// Semantic early errors such as redeclaration errors. errors: Vec, // states - current_node_id: AstNodeId, - current_node_flags: NodeFlags, + pub current_node_id: AstNodeId, + pub current_node_flags: NodeFlags, // builders - nodes: AstNodes<'a>, - scope: ScopeBuilder, - symbols: SymbolTable, + pub nodes: AstNodes<'a>, + pub scope: ScopeBuilder, + pub symbols: SymbolTable, } pub struct ScopeBuilderReturn<'a> { @@ -172,19 +173,18 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> { } impl<'a> SemanticBuilder<'a> { - #[allow(clippy::single_match)] fn enter_kind(&mut self, kind: AstKind<'a>) { match kind { - AstKind::BindingIdentifier(ident) => { - self.enter_binding_identifier(ident); - } AstKind::IdentifierReference(ident) => { - self.enter_identifier_reference(ident); + self.reference_identifier(ident); } AstKind::JSXElementName(elem) => { - self.enter_jsx_element_name(elem); + self.reference_jsx_element_name(elem); + } + AstKind::Class(class) => { + self.current_node_flags |= NodeFlags::Class; + class.bind(self); } - AstKind::Class(class) => self.enter_class(class), _ => {} } } @@ -192,22 +192,14 @@ impl<'a> SemanticBuilder<'a> { #[allow(clippy::single_match)] fn leave_kind(&mut self, kind: AstKind<'a>) { match kind { - AstKind::Class(class) => self.leave_class(class), + AstKind::Class(_) => { + self.current_node_flags -= NodeFlags::Class; + } _ => {} } } - fn enter_binding_identifier(&mut self, ident: &BindingIdentifier) { - self.declare_symbol( - &ident.name, - ident.span, - self.scope.current_scope_id, - SymbolFlags::empty(), - SymbolFlags::empty(), - ); - } - - fn enter_identifier_reference(&mut self, ident: &IdentifierReference) { + fn reference_identifier(&mut self, ident: &IdentifierReference) { let flag = if matches!( self.parent_kind(), AstKind::SimpleAssignmentTarget(_) | AstKind::AssignmentTarget(_) @@ -220,7 +212,7 @@ impl<'a> SemanticBuilder<'a> { self.scope.reference_identifier(&ident.name, reference); } - fn enter_jsx_element_name(&mut self, elem: &JSXElementName) { + fn reference_jsx_element_name(&mut self, elem: &JSXElementName) { if matches!(self.parent_kind(), AstKind::JSXOpeningElement(_)) { if let Some(ident) = match elem { JSXElementName::Identifier(ident) @@ -237,12 +229,4 @@ impl<'a> SemanticBuilder<'a> { } } } - - fn enter_class(&mut self, _: &Class<'a>) { - self.current_node_flags |= NodeFlags::Class; - } - - fn leave_class(&mut self, _: &Class<'a>) { - self.current_node_flags -= NodeFlags::Class; - } } diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index c538b0668..81907b6ec 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -1,5 +1,7 @@ #![feature(is_some_and)] +#![feature(let_chains)] +mod binder; mod builder; mod node; mod scope; diff --git a/crates/oxc_semantic/src/symbol/mod.rs b/crates/oxc_semantic/src/symbol/mod.rs index bb7154135..9e79f6775 100644 --- a/crates/oxc_semantic/src/symbol/mod.rs +++ b/crates/oxc_semantic/src/symbol/mod.rs @@ -31,8 +31,13 @@ fn symbol_size() { bitflags! { #[derive(Default)] - pub struct SymbolFlags: u32 { + pub struct SymbolFlags: u16 { const None = 0; + const Class = 1 << 5; + + const Value = Self::Class.bits; + + const ClassExcludes = Self::Value.bits; } } diff --git a/tasks/coverage/babel.snap b/tasks/coverage/babel.snap index be4e44245..c5574987d 100644 --- a/tasks/coverage/babel.snap +++ b/tasks/coverage/babel.snap @@ -1,7 +1,7 @@ Babel Summary: AST Parsed : 2056/2069 (99.37%) Positive Passed: 2056/2069 (99.37%) -Negative Passed: 936/1502 (62.32%) +Negative Passed: 938/1502 (62.45%) Expect Syntax Error: "annex-b/disabled/1.1-html-comments-close/input.js" Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-if-body/input.js" Expect Syntax Error: "annex-b/disabled/3.1-sloppy-labeled-functions-multiple-labels/input.js" @@ -37,7 +37,6 @@ Expect Syntax Error: "core/scope/dupl-bind-catch-let/input.js" Expect Syntax Error: "core/scope/dupl-bind-catch-obj-destr/input.js" Expect Syntax Error: "core/scope/dupl-bind-catch-var-arr-destr/input.js" Expect Syntax Error: "core/scope/dupl-bind-catch-var-obj-destr/input.js" -Expect Syntax Error: "core/scope/dupl-bind-class-class/input.js" Expect Syntax Error: "core/scope/dupl-bind-class-const/input.js" Expect Syntax Error: "core/scope/dupl-bind-class-func/input.js" Expect Syntax Error: "core/scope/dupl-bind-class-let/input.js" @@ -510,7 +509,6 @@ Expect Syntax Error: "typescript/interface/invalid-modifiers-method/input.ts" Expect Syntax Error: "typescript/interface/invalid-modifiers-property/input.ts" Expect Syntax Error: "typescript/module-namespace/top-level-await/input.ts" Expect Syntax Error: "typescript/regression/keyword-qualified-type-disallowed/input.ts" -Expect Syntax Error: "typescript/scope/redeclaration-class-class/input.ts" Expect Syntax Error: "typescript/scope/redeclaration-class-enum/input.ts" Expect Syntax Error: "typescript/scope/redeclaration-class-type/input.ts" Expect Syntax Error: "typescript/scope/redeclaration-constenum-enum/input.ts" @@ -898,6 +896,16 @@ Expect to Parse: "typescript/types/const-type-parameters/input.ts" ╰──── help: Try insert a semicolon here + × Identifier `"foo"` has already been declared + ╭─[core/scope/dupl-bind-class-class/input.js:1:1] + 1 │ class foo {}; + · ─┬─ + · ╰── `foo` has already been declared here + 2 │ class foo {}; + · ─┬─ + · ╰── It can not be redeclared here + ╰──── + × A 'get' accessor must not have any formal parameters. ╭─[core/uncategorised/.542/input.js:1:1] 1 │ ({ get prop(x) {} }) @@ -7695,6 +7703,16 @@ Expect to Parse: "typescript/types/const-type-parameters/input.ts" ╰──── help: Try insert a semicolon here + × Identifier `"A"` has already been declared + ╭─[typescript/scope/redeclaration-class-class/input.ts:1:1] + 1 │ class A {} + · ┬ + · ╰── `A` has already been declared here + 2 │ class A {} + · ┬ + · ╰── It can not be redeclared here + ╰──── + × Automatic Semicolon Insertion ╭─[typescript/static-blocks/invalid-static-block-with-accessibility-private-02/input.ts:1:1] 1 │ class Foo { diff --git a/tasks/coverage/test262.snap b/tasks/coverage/test262.snap index b8338c768..c8cca3012 100644 --- a/tasks/coverage/test262.snap +++ b/tasks/coverage/test262.snap @@ -1,7 +1,7 @@ Test262 Summary: AST Parsed : 44022/44034 (99.97%) Positive Passed: 44022/44034 (99.97%) -Negative Passed: 2626/3917 (67.04%) +Negative Passed: 2630/3917 (67.14%) Expect Syntax Error: "annexB/language/statements/for-in/const-initializer.js" Expect Syntax Error: "annexB/language/statements/for-in/let-initializer.js" Expect Syntax Error: "annexB/language/statements/for-in/strict-initializer.js" @@ -35,7 +35,6 @@ Expect Syntax Error: "language/block-scope/syntax/redeclaration/async-generator- Expect Syntax Error: "language/block-scope/syntax/redeclaration/async-generator-name-redeclaration-attempt-with-var.js" Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-async-function.js" Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-async-generator.js" -Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-class.js" Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-const.js" Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-function.js" Expect Syntax Error: "language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-generator.js" @@ -985,8 +984,6 @@ Expect Syntax Error: "language/statements/class/static-init-invalid-lex-dup.js" Expect Syntax Error: "language/statements/class/static-init-invalid-lex-var.js" Expect Syntax Error: "language/statements/class/static-init-invalid-return.js" Expect Syntax Error: "language/statements/class/static-init-invalid-super-call.js" -Expect Syntax Error: "language/statements/class/syntax/early-errors/class-definition-evaluation-block-duplicate-binding.js" -Expect Syntax Error: "language/statements/class/syntax/early-errors/class-definition-evaluation-scriptbody-duplicate-binding.js" Expect Syntax Error: "language/statements/const/dstr/ary-ptrn-rest-init-ary.js" Expect Syntax Error: "language/statements/const/dstr/ary-ptrn-rest-init-id.js" Expect Syntax Error: "language/statements/const/dstr/ary-ptrn-rest-init-obj.js" @@ -1196,7 +1193,6 @@ Expect Syntax Error: "language/statements/switch/syntax/redeclaration/async-gene Expect Syntax Error: "language/statements/switch/syntax/redeclaration/async-generator-name-redeclaration-attempt-with-var.js" Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-async-function.js" Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-async-generator.js" -Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-class.js" Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-const.js" Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-function.js" Expect Syntax Error: "language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-generator.js" @@ -1722,6 +1718,15 @@ Expect to Parse: "language/statements/class/decorator/syntax/valid/decorator-par 17 │ while (false) ╰──── + × Identifier `"f"` has already been declared + ╭─[language/block-scope/syntax/redeclaration/class-name-redeclaration-attempt-with-class.js:21:1] + 21 │ + 22 │ { class f {} class f {} } + · ┬ ┬ + · │ ╰── It can not be redeclared here + · ╰── `f` has already been declared here + ╰──── + × Unterminated multiline comment ╭─[language/comments/S7.4_A2_T2.js:14:1] 14 │ @@ -20662,6 +20667,29 @@ Expect to Parse: "language/statements/class/decorator/syntax/valid/decorator-par 15 │ ╰──── + × Identifier `"A"` has already been declared + ╭─[language/statements/class/syntax/early-errors/class-definition-evaluation-block-duplicate-binding.js:15:1] + 15 │ { + 16 │ class A {} + · ┬ + · ╰── `A` has already been declared here + 17 │ class A {} + · ┬ + · ╰── It can not be redeclared here + 18 │ } + ╰──── + + × Identifier `"A"` has already been declared + ╭─[language/statements/class/syntax/early-errors/class-definition-evaluation-scriptbody-duplicate-binding.js:14:1] + 14 │ $DONOTEVALUATE(); + 15 │ class A {} + · ┬ + · ╰── `A` has already been declared here + 16 │ class A {} + · ┬ + · ╰── It can not be redeclared here + ╰──── + × Keywords cannot contain escape characters ╭─[language/statements/class/syntax/escaped-static.js:23:1] 23 │ class C { @@ -23662,6 +23690,15 @@ Expect to Parse: "language/statements/class/decorator/syntax/valid/decorator-par 20 │ case 0: ╰──── + × Identifier `"f"` has already been declared + ╭─[language/statements/switch/syntax/redeclaration/class-name-redeclaration-attempt-with-class.js:21:1] + 21 │ + 22 │ switch (0) { case 1: class f {} default: class f {} } + · ┬ ┬ + · │ ╰── It can not be redeclared here + · ╰── `f` has already been declared here + ╰──── + × Expect token ╭─[language/statements/try/S12.14_A16_T1.js:17:1] 17 │ // CHECK#1 diff --git a/tasks/coverage/typescript.snap b/tasks/coverage/typescript.snap index 3b246e5a8..08e1eee58 100644 --- a/tasks/coverage/typescript.snap +++ b/tasks/coverage/typescript.snap @@ -1,7 +1,7 @@ TypeScript Summary: AST Parsed : 2308/2338 (98.72%) Positive Passed: 2308/2338 (98.72%) -Negative Passed: 578/2532 (22.83%) +Negative Passed: 580/2532 (22.91%) Expect Syntax Error: "Symbols/ES5SymbolProperty2.ts" Expect Syntax Error: "Symbols/ES5SymbolProperty6.ts" Expect Syntax Error: "additionalChecks/noPropertyAccessFromIndexSignature1.ts" @@ -80,7 +80,6 @@ Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstra Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractInheritance.ts" Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations1.ts" Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractInstantiations2.ts" -Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractMergedDeclaration.ts" Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractMethodInNonAbstractClass.ts" Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractMethodWithImplementation.ts" Expect Syntax Error: "classes/classDeclarations/classAbstractKeyword/classAbstractOverloads.ts" @@ -588,7 +587,6 @@ Expect Syntax Error: "es6/functionPropertyAssignments/FunctionPropertyAssignment Expect Syntax Error: "es6/memberFunctionDeclarations/MemberFunctionDeclaration3_es6.ts" Expect Syntax Error: "es6/modules/defaultExportsCannotMerge04.ts" Expect Syntax Error: "es6/modules/importEmptyFromModuleNotExisted.ts" -Expect Syntax Error: "es6/modules/multipleDefaultExports03.ts" Expect Syntax Error: "es6/modules/multipleDefaultExports04.ts" Expect Syntax Error: "es6/modules/multipleDefaultExports05.ts" Expect Syntax Error: "es6/newTarget/invalidNewTarget.es5.ts" @@ -2499,6 +2497,30 @@ Expect to Parse: "salsa/privateIdentifierExpando.ts" 4 │ import abstract class D {} ╰──── + × Identifier `"CC1"` has already been declared + ╭─[classes/classDeclarations/classAbstractKeyword/classAbstractMergedDeclaration.ts:12:1] + 12 │ + 13 │ abstract class CC1 {} + · ─┬─ + · ╰── `CC1` has already been declared here + 14 │ class CC1 {} + · ─┬─ + · ╰── It can not be redeclared here + 15 │ + ╰──── + + × Identifier `"CC2"` has already been declared + ╭─[classes/classDeclarations/classAbstractKeyword/classAbstractMergedDeclaration.ts:15:1] + 15 │ + 16 │ class CC2 {} + · ─┬─ + · ╰── `CC2` has already been declared here + 17 │ abstract class CC2 {} + · ─┬─ + · ╰── It can not be redeclared here + 18 │ + ╰──── + × Expect token ╭─[classes/classDeclarations/classAbstractKeyword/classAbstractMixedWithModifiers.ts:15:1] 15 │ abstract async foo_f(); @@ -4718,6 +4740,20 @@ Expect to Parse: "salsa/privateIdentifierExpando.ts" 6 │ return bar; ╰──── + × Identifier `"C"` has already been declared + ╭─[es6/modules/multipleDefaultExports03.ts:3:1] + 3 │ + 4 │ export default class C { + · ┬ + · ╰── `C` has already been declared here + 5 │ } + 6 │ + 7 │ export default class C { + · ┬ + · ╰── It can not be redeclared here + 8 │ } + ╰──── + × Unexpected token ╭─[es6/shorthandPropertyAssignment/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.ts:2:1] 2 │ var y = {