Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/ts2371-declare-object-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
swc_core: patch
swc_ecma_parser: patch
---

fix(es/parser): emit TS2371 for object-pattern defaults in declare signatures
34 changes: 10 additions & 24 deletions crates/swc_ecma_parser/src/parser/class_and_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,16 +355,7 @@ impl<I: Tokens> Parser<I> {
if p.syntax().typescript() && body.is_none() {
// Declare functions cannot have assignment pattern in parameters
for param in &params {
// TODO: Search deeply for assignment pattern using a Visitor

let span = match &param.pat {
Pat::Assign(ref p) => Some(p.span()),
_ => None,
};

if let Some(span) = span {
p.emit_err(span, SyntaxError::TS2371)
}
p.emit_ts2371_for_param_initializers(&param.pat);
}
}

Expand Down Expand Up @@ -1249,22 +1240,17 @@ impl<I: Tokens> Parser<I> {
if self.syntax().typescript() && body.is_none() {
// Declare constructors cannot have assignment pattern in parameters
for param in &params {
// TODO: Search deeply for assignment pattern using a Visitor

let span = match *param {
ParamOrTsParamProp::Param(ref param) => match param.pat {
Pat::Assign(ref p) => Some(p.span()),
_ => None,
},
match param {
ParamOrTsParamProp::Param(param) => {
self.emit_ts2371_for_param_initializers(&param.pat);
}
ParamOrTsParamProp::TsParamProp(TsParamProp {
param: TsParamPropParam::Assign(ref p),
param: TsParamPropParam::Assign(p),
..
}) => Some(p.span()),
_ => None,
};

if let Some(span) = span {
self.emit_err(span, SyntaxError::TS2371)
}) => {
self.emit_err(p.span(), SyntaxError::TS2371);
}
_ => {}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_parser/src/parser/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ impl<I: Tokens> Parser<I> {
};

let value = if self.input_mut().eat(Token::Eq) {
// Do not emit TS2371 here: `Context::InDeclare` also covers ambient
// variable bindings such as `declare const { a = 1 }`, which TypeScript
// accepts. Declare function/constructor parameters are rejected by
// `emit_ts2371_for_param_initializers` after the signature is parsed.
self.allow_in_expr(Self::parse_assignment_expr).map(Some)?
} else {
let ctx = self.ctx();
Expand Down
55 changes: 49 additions & 6 deletions crates/swc_ecma_parser/src/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,49 @@ impl<I: Tokens> Parser<I> {
}
}

/// Emit TS2371 for any parameter initializer nested in a binding pattern.
///
/// Used for ambient / declare function and constructor signatures where
/// top-level AssignPat checks alone miss object shorthand defaults and
/// nested defaults.
pub(crate) fn emit_ts2371_for_param_initializers(&mut self, pat: &Pat) {
match pat {
Pat::Assign(a) => {
self.emit_err(a.span(), SyntaxError::TS2371);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid re-emitting TS2371 for parsed declare defaults

For declare function/method signatures parsed under Context::InParameters, nested Pat::Assign defaults have already reported TS2371 from parse_binding_element before this post-parse walk runs. This line reports the same span a second time for cases such as declare function f([x = 1]): void; or declare function f({ a: b = 1 }): void;, so consumers and the newly added stderr fixtures will see duplicate TS2371 diagnostics unless the walker skips already-checked assign patterns in that context or the eager check is centralized here.

Useful? React with 👍 / 👎.

self.emit_ts2371_for_param_initializers(&a.left);
}
Pat::Array(arr) => {
for elem in arr.elems.iter().flatten() {
self.emit_ts2371_for_param_initializers(elem);
}
}
Pat::Object(obj) => {
for prop in &obj.props {
match prop {
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. })
| ObjectPatProp::Rest(RestPat { arg: value, .. }) => {
self.emit_ts2371_for_param_initializers(value);
}
ObjectPatProp::Assign(AssignPatProp {
span,
value: Some(_),
..
}) => {
self.emit_err(*span, SyntaxError::TS2371);
}
ObjectPatProp::Assign(AssignPatProp { value: None, .. }) => {}
#[cfg(swc_ast_unknown)]
_ => {}
}
}
}
Pat::Rest(r) => self.emit_ts2371_for_param_initializers(&r.arg),
Pat::Ident(_) | Pat::Invalid(_) | Pat::Expr(_) => {}
#[cfg(swc_ast_unknown)]
_ => {}
}
}

fn assign_pat_type_ann(&mut self, pat: &mut Pat, span: Span, type_ann: Box<TsType>) {
let type_ann = Some(Box::new(TsTypeAnn { span, type_ann }));

Expand Down Expand Up @@ -463,9 +506,10 @@ impl<I: Tokens> Parser<I> {
self.emit_err(right.span(), SyntaxError::AwaitParamInAsync);
}

if self.ctx().contains(Context::InDeclare) {
self.emit_err(self.span(start), SyntaxError::TS2371);
}
// Do not emit TS2371 here. Ambient destructuring such as
// `declare const { a: b = 1 }` is valid, and declare / type-signature
// parameters are checked by `emit_ts2371_for_param_initializers`
// after the parameter list is parsed (avoids duplicate diagnostics).

return Ok(AssignPat {
span: self.span(start),
Expand Down Expand Up @@ -654,9 +698,8 @@ impl<I: Tokens> Parser<I> {
{
self.emit_err(right.span(), SyntaxError::AwaitParamInAsync);
}
if self.ctx().contains(Context::InDeclare) {
self.emit_err(self.span(start), SyntaxError::TS2371);
}
// TS2371 for declare / signature parameters is emitted by
// `emit_ts2371_for_param_initializers` after the list is parsed.

AssignPat {
span: self.span(start),
Expand Down
19 changes: 19 additions & 0 deletions crates/swc_ecma_parser/src/parser/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ fn assert_module_error(src: &'static str) -> Module {
})
}

#[test]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move duplicate TS2371 coverage into fixtures

This new #[test] adds parser coverage outside the fixture suites even though this commit already adds tests/typescript-errors/ts2371/mixed, whose stderr snapshot would catch duplicate TS2371 diagnostics. Keeping the ad-hoc inline test bypasses the parser fixture workflow and violates the repo's fixture-test convention; please move the duplicate-diagnostic assertion into the fixture suite or rely on the existing stderr fixture.

AGENTS.md reference: AGENTS.md:L52-L55

Useful? React with 👍 / 👎.

fn ts2371_declare_params_are_not_duplicated() {
// Eager parse-time TS2371 plus the post-list walker must not double-report.
test_parser(
"declare function top(a = 1): void; declare function obj({ a = 1 }): void;",
Syntax::Typescript(Default::default()),
|p| {
let _ = p.parse_typescript_module()?;
let errors = p.take_errors();
let n = errors
.iter()
.filter(|e| matches!(e.kind(), crate::error::SyntaxError::TS2371))
.count();
assert_eq!(n, 2, "{errors:?}");
Ok(())
},
);
}

#[test]
fn parse_program_module_01() {
module("import 'foo';");
Expand Down
51 changes: 51 additions & 0 deletions crates/swc_ecma_parser/src/parser/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2629,6 +2629,12 @@ impl<I: Tokens> Parser<I> {
Pat::Array(pat) => TsFnParam::Array(pat),
Pat::Object(pat) => TsFnParam::Object(pat),
Pat::Rest(pat) => TsFnParam::Rest(pat),
// Parameter initializers are illegal in type / call signatures
// (TS2371). Recover by keeping the binding and reporting once.
Pat::Assign(a) => {
p.emit_err(a.span(), SyntaxError::TS2371);
return pat_to_ts_fn_param(p, *a.left);
}
_ => unexpected!(
p,
"an identifier, [ for an array pattern, { for an object patter or ... for a \
Expand Down Expand Up @@ -2718,6 +2724,9 @@ impl<I: Tokens> Parser<I> {
}

expect!(self, Token::RParen);
for param in &list {
self.emit_ts2371_for_ts_fn_param(param);
}
return Ok(list);
}

Expand All @@ -2728,9 +2737,51 @@ impl<I: Tokens> Parser<I> {
list.push(pat_to_ts_fn_param(self, param.pat)?);
}
expect!(self, Token::RParen);
// Nested object/array defaults in type positions never go through
// `parse_fn_args_body`'s post-walk; check them here.
for param in &list {
self.emit_ts2371_for_ts_fn_param(param);
}
Ok(list)
}

/// Emit TS2371 for initializers nested in a type/call/method signature
/// parameter. Top-level `AssignPat` is already reported while converting to
/// [`TsFnParam`]; this covers object shorthand / nested defaults.
fn emit_ts2371_for_ts_fn_param(&mut self, param: &TsFnParam) {
match param {
TsFnParam::Ident(_) => {}
TsFnParam::Array(arr) => {
for elem in arr.elems.iter().flatten() {
self.emit_ts2371_for_param_initializers(elem);
}
}
TsFnParam::Object(obj) => {
for prop in &obj.props {
match prop {
ObjectPatProp::KeyValue(KeyValuePatProp { value, .. })
| ObjectPatProp::Rest(RestPat { arg: value, .. }) => {
self.emit_ts2371_for_param_initializers(value);
}
ObjectPatProp::Assign(AssignPatProp {
span,
value: Some(_),
..
}) => {
self.emit_err(*span, SyntaxError::TS2371);
}
ObjectPatProp::Assign(AssignPatProp { value: None, .. }) => {}
#[cfg(swc_ast_unknown)]
_ => {}
}
}
}
TsFnParam::Rest(r) => self.emit_ts2371_for_param_initializers(&r.arg),
#[cfg(swc_ast_unknown)]
_ => {}
}
}

/// `tsIsStartOfMappedType`
fn is_ts_start_of_mapped_type(&mut self) -> bool {
debug_assert!(self.input().syntax().typescript());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare function bar([x = 1]: number[]): void;
declare function baz([x, y = 2]: number[]): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/array-still-errors/input.ts:1:1]
1 | declare function bar([x = 1]: number[]): void;
: ^^^^^
2 | declare function baz([x, y = 2]: number[]): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/array-still-errors/input.ts:2:1]
1 | declare function bar([x = 1]: number[]): void;
2 | declare function baz([x, y = 2]: number[]): void;
: ^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare class C {
method({ a = 1 }: { a?: number }): void;
constructor({ a = 1 }: { a?: number });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/declare-method/input.ts:2:1]
1 | declare class C {
2 | method({ a = 1 }: { a?: number }): void;
: ^^^^^
3 | constructor({ a = 1 }: { a?: number });
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/declare-method/input.ts:3:1]
2 | method({ a = 1 }: { a?: number }): void;
3 | constructor({ a = 1 }: { a?: number });
: ^^^^^
4 | }
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare function foo({ a: b = 1 }: { a?: number }): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/key-value-default/input.ts:1:1]
1 | declare function foo({ a: b = 1 }: { a?: number }): void;
: ^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare function top(a = 1): void;
declare function obj({ a = 1 }): void;
declare function arr([a = 1]): void;
declare function nested({ a: { b = 1 } }): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/mixed/input.ts:1:1]
1 | declare function top(a = 1): void;
: ^^^^^
2 | declare function obj({ a = 1 }): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/mixed/input.ts:2:1]
1 | declare function top(a = 1): void;
2 | declare function obj({ a = 1 }): void;
: ^^^^^
3 | declare function arr([a = 1]): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/mixed/input.ts:3:1]
2 | declare function obj({ a = 1 }): void;
3 | declare function arr([a = 1]): void;
: ^^^^^
4 | declare function nested({ a: { b = 1 } }): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/mixed/input.ts:4:1]
3 | declare function arr([a = 1]): void;
4 | declare function nested({ a: { b = 1 } }): void;
: ^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare function foo({ a: { b = 1 } }: { a: { b?: number } }): void;
declare function bar({ a: [x = 1] }: { a: number[] }): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/nested-object/input.ts:1:1]
1 | declare function foo({ a: { b = 1 } }: { a: { b?: number } }): void;
: ^^^^^
2 | declare function bar({ a: [x = 1] }: { a: number[] }): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/nested-object/input.ts:2:1]
1 | declare function foo({ a: { b = 1 } }: { a: { b?: number } }): void;
2 | declare function bar({ a: [x = 1] }: { a: number[] }): void;
: ^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declare function foo({ a = 1 }: { a?: number }): void;
declare function bar({ a = 1, b = 2 }: { a?: number; b?: number }): void;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/object-shorthand/input.ts:1:1]
1 | declare function foo({ a = 1 }: { a?: number }): void;
: ^^^^^
2 | declare function bar({ a = 1, b = 2 }: { a?: number; b?: number }): void;
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/object-shorthand/input.ts:2:1]
1 | declare function foo({ a = 1 }: { a?: number }): void;
2 | declare function bar({ a = 1, b = 2 }: { a?: number; b?: number }): void;
: ^^^^^
`----
x A parameter initializer is only allowed in a function or constructor implementation
,-[$DIR/tests/typescript-errors/ts2371/object-shorthand/input.ts:2:1]
1 | declare function foo({ a = 1 }: { a?: number }): void;
2 | declare function bar({ a = 1, b = 2 }: { a?: number; b?: number }): void;
: ^^^^^
`----
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type T1 = (a = 1) => void;
type T2 = ({ a = 1 }: { a?: number }) => void;
type T3 = ({ a: b = 1 }: { a?: number }) => void;
interface I {
(x = 1): void;
({ a = 1 }: { a?: number }): void;
method({ a = 1 }: { a?: number }): void;
}
Loading
Loading