diff --git a/crates/oxc_semantic/src/checker/javascript.rs b/crates/oxc_semantic/src/checker/javascript.rs index 16ff39d6cf38e..668bdf1649a72 100644 --- a/crates/oxc_semantic/src/checker/javascript.rs +++ b/crates/oxc_semantic/src/checker/javascript.rs @@ -512,19 +512,50 @@ pub fn check_meta_property(prop: &MetaProperty, ctx: &SemanticBuilder<'_>) { if ctx.source_type.is_commonjs() { return; } - let mut in_function_scope = false; - for scope_id in ctx.scoping.scope_ancestors(ctx.current_scope_id) { - let flags = ctx.scoping.scope_flags(scope_id); - // In arrow functions, new.target is inherited from the surrounding scope. - if flags.contains(ScopeFlags::Arrow) { - continue; + + // Check if we're in a valid context for new.target: + // 1. Inside a function (including constructor) + // 2. Inside a class static block + // 3. Inside a class field initializer (new.target evaluates to undefined) + // + // Arrow functions inherit new.target from their surrounding scope, + // so we skip them and continue checking the enclosing context. + + let mut in_valid_context = false; + + // First, check AST ancestors for class field initializers. + // We need to do this because class fields don't have their own scope. + for node_kind in ctx.nodes.ancestor_kinds(ctx.current_node_id) { + match node_kind { + // Regular functions have their own new.target binding. + // Use scope-based check from here. + AstKind::Function(_) => break, + // Class field initializers allow new.target (evaluates to undefined). + // This includes arrow functions nested inside the initializer. + AstKind::PropertyDefinition(_) | AstKind::AccessorProperty(_) => { + in_valid_context = true; + break; + } + _ => {} } - if flags.intersects(ScopeFlags::Function | ScopeFlags::ClassStaticBlock) { - in_function_scope = true; - break; + } + + // If not in a class field, fall back to scope-based check + if !in_valid_context { + for scope_id in ctx.scoping.scope_ancestors(ctx.current_scope_id) { + let flags = ctx.scoping.scope_flags(scope_id); + // In arrow functions, new.target is inherited from the surrounding scope. + if flags.contains(ScopeFlags::Arrow) { + continue; + } + if flags.intersects(ScopeFlags::Function | ScopeFlags::ClassStaticBlock) { + in_valid_context = true; + break; + } } } - if !in_function_scope { + + if !in_valid_context { ctx.error(diagnostics::new_target(prop.span)); } } diff --git a/crates/oxc_semantic/src/diagnostics.rs b/crates/oxc_semantic/src/diagnostics.rs index 0c41e6e552d12..2589bc4d61359 100644 --- a/crates/oxc_semantic/src/diagnostics.rs +++ b/crates/oxc_semantic/src/diagnostics.rs @@ -121,7 +121,9 @@ pub fn module_code(x0: &str, span1: Span) -> OxcDiagnostic { #[cold] pub fn new_target(span: Span) -> OxcDiagnostic { OxcDiagnostic::error("Unexpected new.target expression") - .with_help("new.target is only allowed in constructors and functions invoked using the `new` operator") + .with_help( + "new.target is only allowed in constructors, functions, and class field initializers", + ) .with_label(span) } diff --git a/tasks/coverage/snapshots/parser_babel.snap b/tasks/coverage/snapshots/parser_babel.snap index 052092104946a..5e56000bbfd50 100644 --- a/tasks/coverage/snapshots/parser_babel.snap +++ b/tasks/coverage/snapshots/parser_babel.snap @@ -2,7 +2,7 @@ commit: fc58af40 parser_babel Summary: AST Parsed : 2221/2227 (99.73%) -Positive Passed: 2205/2227 (99.01%) +Positive Passed: 2206/2227 (99.06%) Negative Passed: 1649/1689 (97.63%) Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2026/explicit-resource-management/invalid-for-using-of-no-initializer/input.js @@ -84,80 +84,6 @@ Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/ty Expect Syntax Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/typescript/types/invalid-import-type-options-with-spread-element/input.ts -Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:2:14] - 1 │ class X { - 2 │ static a = new.target; - · ────────── - 3 │ static b = (foo = 1 + bar(new.target)); - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:3:29] - 2 │ static a = new.target; - 3 │ static b = (foo = 1 + bar(new.target)); - · ────────── - 4 │ static c = () => new.target; - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:4:20] - 3 │ static b = (foo = 1 + bar(new.target)); - 4 │ static c = () => new.target; - · ────────── - 5 │ static d = (foo = new.target) => {}; - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:5:21] - 4 │ static c = () => new.target; - 5 │ static d = (foo = new.target) => {}; - · ────────── - 6 │ e = new.target; - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:6:7] - 5 │ static d = (foo = new.target) => {}; - 6 │ e = new.target; - · ────────── - 7 │ f = (foo = 1 + bar(new.target)); - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:7:22] - 6 │ e = new.target; - 7 │ f = (foo = 1 + bar(new.target)); - · ────────── - 8 │ g = () => new.target; - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:8:13] - 7 │ f = (foo = 1 + bar(new.target)); - 8 │ g = () => new.target; - · ────────── - 9 │ h = (foo = new.target) => {}; - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js:9:14] - 8 │ g = () => new.target; - 9 │ h = (foo = new.target) => {}; - · ────────── - 10 │ } - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/class-static-block/duplicate-function-var-name/input.js × Identifier `x` has already been declared @@ -956,14 +882,14 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 1 │ const x = new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[babel/packages/babel-parser/test/fixtures/core/opts/allowNewTargetOutsideFunction-false-2/input.js:1:17] 1 │ const y = () => new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × TS(1108): A 'return' statement can only be used within a function body. ╭─[babel/packages/babel-parser/test/fixtures/core/opts/allowReturnOutsideFunction-true-invalid-in-static-block/input.js:3:5] @@ -3132,7 +3058,7 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc · ────────── 3 │ } ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × The only valid meta property for new is new.target ╭─[babel/packages/babel-parser/test/fixtures/es2015/meta-properties/new-invalid-prop/input.js:2:3] @@ -3147,7 +3073,7 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 1 │ new.target · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Keywords cannot contain escape characters ╭─[babel/packages/babel-parser/test/fixtures/es2015/meta-properties/new-target-invalid-escaped-new/input.js:1:16] @@ -8250,7 +8176,7 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 1 │ var x = new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Expected `(` but found `}` ╭─[babel/packages/babel-parser/test/fixtures/es2022/class-properties/no-ctor/input.js:3:1] @@ -9953,7 +9879,7 @@ Expect to Parse: tasks/coverage/babel/packages/babel-parser/test/fixtures/typesc 1 │ var x = new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × The only valid meta property for new is new.target ╭─[babel/packages/babel-parser/test/fixtures/esprima/es2015-meta-property/unknown-property/input.js:1:22] diff --git a/tasks/coverage/snapshots/parser_test262.snap b/tasks/coverage/snapshots/parser_test262.snap index 487ffd47b8c8c..a7c6cd763995a 100644 --- a/tasks/coverage/snapshots/parser_test262.snap +++ b/tasks/coverage/snapshots/parser_test262.snap @@ -20523,7 +20523,7 @@ Negative Passed: 4581/4581 (100.00%) · ────────── 38 │ }; ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[test262/test/language/global-code/new.target.js:21:1] @@ -20531,7 +20531,7 @@ Negative Passed: 4581/4581 (100.00%) 21 │ new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × TS(1108): A 'return' statement can only be used within a function body. ╭─[test262/test/language/global-code/return.js:23:1] @@ -24730,7 +24730,7 @@ Negative Passed: 4581/4581 (100.00%) 16 │ new.target; · ────────── ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × The keyword 'public' is reserved ╭─[test262/test/language/module-code/early-strict-mode.js:15:5] diff --git a/tasks/coverage/snapshots/parser_typescript.snap b/tasks/coverage/snapshots/parser_typescript.snap index 4a80cbffc4e31..ce2544f62dd8e 100644 --- a/tasks/coverage/snapshots/parser_typescript.snap +++ b/tasks/coverage/snapshots/parser_typescript.snap @@ -16683,7 +16683,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 2 │ const b = () => new.target; ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:2:17] @@ -16692,7 +16692,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 3 │ ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:5:6] @@ -16701,16 +16701,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 6 │ c() { return new.target; } ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:9:15] - 8 │ set e(_) { _ = new.target; } - 9 │ f = () => new.target; - · ────────── - 10 │ - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:11:13] @@ -16719,16 +16710,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 12 │ static g() { return new.target; } ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:15:22] - 14 │ static set i(_) { _ = new.target; } - 15 │ static j = () => new.target; - · ────────── - 16 │ } - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:19:6] @@ -16737,7 +16719,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 20 │ k() { return new.target; }, ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es5.ts:23:8] @@ -16746,7 +16728,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 24 │ }; ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:1:11] @@ -16754,7 +16736,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 2 │ const b = () => new.target; ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:2:17] @@ -16763,7 +16745,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 3 │ ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:5:6] @@ -16772,16 +16754,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 6 │ c() { return new.target; } ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:9:15] - 8 │ set e(_) { _ = new.target; } - 9 │ f = () => new.target; - · ────────── - 10 │ - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:11:13] @@ -16790,16 +16763,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 12 │ static g() { return new.target; } ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator - - × Unexpected new.target expression - ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:15:22] - 14 │ static set i(_) { _ = new.target; } - 15 │ static j = () => new.target; - · ────────── - 16 │ } - ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:19:6] @@ -16808,7 +16772,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 20 │ k() { return new.target; }, ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Unexpected new.target expression ╭─[typescript/tests/cases/conformance/es6/newTarget/invalidNewTarget.es6.ts:23:8] @@ -16817,7 +16781,7 @@ Expect to Parse: tasks/coverage/typescript/tests/cases/conformance/statements/Va · ────────── 24 │ }; ╰──── - help: new.target is only allowed in constructors and functions invoked using the `new` operator + help: new.target is only allowed in constructors, functions, and class field initializers × Expected `:` but found `,` ╭─[typescript/tests/cases/conformance/es6/shorthandPropertyAssignment/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.ts:3:20] diff --git a/tasks/coverage/snapshots/semantic_babel.snap b/tasks/coverage/snapshots/semantic_babel.snap index 02279d6164a94..bf8e3e0c2e6e0 100644 --- a/tasks/coverage/snapshots/semantic_babel.snap +++ b/tasks/coverage/snapshots/semantic_babel.snap @@ -2,7 +2,7 @@ commit: fc58af40 semantic_babel Summary: AST Parsed : 2227/2227 (100.00%) -Positive Passed: 1939/2227 (87.07%) +Positive Passed: 1940/2227 (87.11%) semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/comments/decorators/decorators-after-export/input.js Symbol span mismatch for "C": after transform: SymbolId(0): Span { start: 65, end: 66 } @@ -76,16 +76,6 @@ Symbol scope ID mismatch for "_await": after transform: SymbolId(3): ScopeId(2) rebuilt : SymbolId(2): ScopeId(0) -semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/class-properties/new-target/input.js -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression -Unexpected new.target expression - semantic Error: tasks/coverage/babel/packages/babel-parser/test/fixtures/es2022/class-static-block/duplicate-function-var-name/input.js Identifier `x` has already been declared