mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
6 files changed
+68
-155
No files matched your search
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user