fix(semantic): allow new.target in class field initializers (#18349)

## Summary

- Allow `new.target` in class field initializers per the ES2022 spec
- `new.target` evaluates to `undefined` in this context, same as in static blocks
- This aligns Oxc with Babel's behavior

## Background

TypeScript incorrectly disallows `new.target` in class field initializers. Their validation was implemented in 2016 ([PR #12783](https://github.com/Microsoft/TypeScript/pull/12783)) before class fields were standardized, and was never updated when ES2022 landed.

The ES spec explicitly allows this - see the [TC39 class-fields proposal](https://github.com/tc39/proposal-class-fields) and [Babel issue #6243](https://github.com/babel/babel/issues/6243).

## Test case

```javascript
class X {
  static a = new.target;              // ✓ allowed (evaluates to undefined)
  static b = () => new.target;        // ✓ allowed (arrow inherits from field context)
  e = new.target;                     // ✓ allowed
  f = () => new.target;               // ✓ allowed
}
```

## Test plan

- [x] Babel conformance test `es2022/class-properties/new-target` now passes
- [x] `cargo test -p oxc_semantic --lib` passes
- [x] `just check lint test` passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Boshen committed 2026-01-21 15:05:55 +00:00
1 parent 776ba18628
commit 60379958cd
6 files changed
+68 -155

No files matched your search

+42 -11
View File
@@ -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;
}
if flags.intersects(ScopeFlags::Function | ScopeFlags::ClassStaticBlock) {
in_function_scope = true;
break;
// 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 !in_function_scope {
// 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_valid_context {
ctx.error(diagnostics::new_target(prop.span));
}
}
+3 -1
View File
@@ -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)
}
+7 -81
View File
@@ -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]
+3 -3
View File
@@ -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]
+12 -48
View File
@@ -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]
+1 -11
View File
@@ -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