-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
fix(es/parser): enforce UniqueFormalParameters #12092
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
d037fd7
be8a1cd
b6d45d6
f62a934
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| swc_core: patch | ||
| swc_ecma_parser: patch | ||
| --- | ||
|
|
||
| fix(es/parser): enforce UniqueFormalParameters for methods, arrows, and non-simple forms |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2716,6 +2716,8 @@ impl<I: Tokens> Parser<I> { | |
| }; | ||
|
|
||
| let validate_arrow_params = |p: &mut Self, params: &[Pat], is_async: bool| { | ||
| p.ensure_unique_formal_params(params.iter()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For non-async TypeScript generic arrows such as Useful? React with 👍 / 👎. |
||
|
|
||
| for param in params { | ||
| if is_async { | ||
| match param { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,11 @@ | ||
| //! 13.3.3 Destructuring Binding Patterns | ||
|
|
||
| use rustc_hash::FxHashSet; | ||
| use swc_atoms::Atom; | ||
| use swc_common::Spanned; | ||
|
|
||
| use super::*; | ||
| use crate::parser::{expr::AssignTargetOrSpread, Parser}; | ||
| use crate::parser::{expr::AssignTargetOrSpread, util::IsSimpleParameterList, Parser}; | ||
|
|
||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub(crate) enum PatType { | ||
|
|
@@ -77,6 +79,63 @@ impl<I: Tokens> Parser<I> { | |
| } | ||
| } | ||
|
|
||
| /// Enforce UniqueFormalParameters: BoundNames must not contain duplicates. | ||
| /// | ||
| /// Used for methods, setters, arrows, constructors, strict-mode functions, | ||
| /// and any FormalParameters list that is not a simple parameter list. | ||
| pub(crate) fn ensure_unique_formal_params<'a, Iter>(&mut self, pats: Iter) | ||
| where | ||
| Iter: IntoIterator<Item = &'a Pat>, | ||
| { | ||
| let mut names = FxHashSet::default(); | ||
| for pat in pats { | ||
| self.collect_unique_formal_bindings(pat, &mut names); | ||
| } | ||
| } | ||
|
|
||
| fn collect_unique_formal_bindings(&mut self, pat: &Pat, names: &mut FxHashSet<Atom>) { | ||
| match pat { | ||
| Pat::Ident(i) => { | ||
| if !names.insert(i.id.sym.clone()) { | ||
| self.emit_err( | ||
| i.id.span, | ||
| SyntaxError::DuplicateFormalParameter(i.id.sym.clone()), | ||
| ); | ||
| } | ||
| } | ||
| Pat::Array(arr) => { | ||
| for elem in arr.elems.iter().flatten() { | ||
| self.collect_unique_formal_bindings(elem, names); | ||
| } | ||
| } | ||
| Pat::Rest(r) => self.collect_unique_formal_bindings(&r.arg, names), | ||
| Pat::Object(obj) => { | ||
| for prop in &obj.props { | ||
| match prop { | ||
| ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) | ||
| | ObjectPatProp::Rest(RestPat { arg: value, .. }) => { | ||
| self.collect_unique_formal_bindings(value, names); | ||
| } | ||
| ObjectPatProp::Assign(AssignPatProp { key, .. }) => { | ||
| if !names.insert(key.sym.clone()) { | ||
| self.emit_err( | ||
| key.span, | ||
| SyntaxError::DuplicateFormalParameter(key.sym.clone()), | ||
| ); | ||
| } | ||
| } | ||
| #[cfg(swc_ast_unknown)] | ||
| _ => unreachable!(), | ||
| } | ||
| } | ||
| } | ||
| Pat::Assign(a) => self.collect_unique_formal_bindings(&a.left, names), | ||
| Pat::Invalid(_) | Pat::Expr(_) => {} | ||
| #[cfg(swc_ast_unknown)] | ||
| _ => unreachable!(), | ||
| } | ||
| } | ||
|
|
||
| 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 })); | ||
|
|
||
|
|
@@ -767,6 +826,36 @@ impl<I: Tokens> Parser<I> { | |
| } | ||
| } | ||
|
|
||
| // Class constructors always use UniqueFormalParameters (class bodies are | ||
| // strict mode code). | ||
| { | ||
| let mut names = FxHashSet::default(); | ||
| for p in ¶ms { | ||
| match p { | ||
| ParamOrTsParamProp::Param(param) => { | ||
| self.collect_unique_formal_bindings(¶m.pat, &mut names); | ||
| } | ||
| ParamOrTsParamProp::TsParamProp(prop) => match &prop.param { | ||
| TsParamPropParam::Ident(i) => { | ||
| if !names.insert(i.id.sym.clone()) { | ||
| self.emit_err( | ||
| i.id.span, | ||
| SyntaxError::DuplicateFormalParameter(i.id.sym.clone()), | ||
| ); | ||
| } | ||
| } | ||
| TsParamPropParam::Assign(a) => { | ||
| self.collect_unique_formal_bindings(&a.left, &mut names); | ||
| } | ||
| #[cfg(swc_ast_unknown)] | ||
| _ => {} | ||
| }, | ||
| #[cfg(swc_ast_unknown)] | ||
| _ => {} | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(params) | ||
| } | ||
|
|
||
|
|
@@ -867,12 +956,32 @@ impl<I: Tokens> Parser<I> { | |
| } | ||
| } | ||
|
|
||
| // UniqueFormalParameters / FormalParameters early errors: | ||
| // duplicate BoundNames are always illegal in strict mode, and also | ||
| // illegal whenever the parameter list is not simple. | ||
| if self.ctx().contains(Context::Strict) || !params.is_simple_parameter_list() { | ||
| self.ensure_unique_formal_params(params.iter().map(|p| &p.pat)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Please don't make this depend only on the current Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| if self.ctx().contains(Context::Strict) { | ||
| for param in params.iter() { | ||
| self.pat_is_valid_argument_in_strict(¶m.pat) | ||
| } | ||
| } | ||
|
|
||
| Ok(params) | ||
| } | ||
|
|
||
| pub(crate) fn parse_unique_formal_params(&mut self) -> PResult<Vec<Param>> { | ||
| // FIXME: This is wrong | ||
| self.parse_formal_params() | ||
| let params = self.parse_formal_params()?; | ||
| // Methods / object methods always require UniqueFormalParameters, | ||
| // including simple duplicate bindings that sloppy-mode functions allow. | ||
| // `parse_formal_params` already checks when !simple or Strict; cover the | ||
| // remaining simple+non-strict case used by class/object methods. | ||
| if params.is_simple_parameter_list() && !self.ctx().contains(Context::Strict) { | ||
| self.ensure_unique_formal_params(params.iter().map(|p| &p.pat)); | ||
| } | ||
| Ok(params) | ||
| } | ||
|
|
||
| pub(super) fn parse_paren_items_as_params( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| (a, a) => a; | ||
| async (a, a) => a; | ||
| ({a}, a) => a; | ||
| (a, a = 1) => a; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/arrow/input.js:1:1] | ||
| 1 | (a, a) => a; | ||
| : ^ | ||
| 2 | async (a, a) => a; | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/arrow/input.js:2:1] | ||
| 1 | (a, a) => a; | ||
| 2 | async (a, a) => a; | ||
| : ^ | ||
| 3 | ({a}, a) => a; | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/arrow/input.js:3:1] | ||
| 2 | async (a, a) => a; | ||
| 3 | ({a}, a) => a; | ||
| : ^ | ||
| 4 | (a, a = 1) => a; | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/arrow/input.js:4:1] | ||
| 3 | ({a}, a) => a; | ||
| 4 | (a, a = 1) => a; | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ({ async m(a, a) { return a; } }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/async-method/input.js:1:1] | ||
| 1 | ({ async m(a, a) { return a; } }); | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| class C { | ||
| constructor(a, a) {} | ||
| } | ||
| class D { | ||
| constructor([a], a) {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-constructor/input.js:2:1] | ||
| 1 | class C { | ||
| 2 | constructor(a, a) {} | ||
| : ^ | ||
| 3 | } | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-constructor/input.js:5:1] | ||
| 4 | class D { | ||
| 5 | constructor([a], a) {} | ||
| : ^ | ||
| 6 | } | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| class C { | ||
| m(a, a) { return a; } | ||
| static m2(a, a) { return a; } | ||
| async m3(a, a) { return a; } | ||
| *m4(a, a) { return a; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-method/input.js:2:1] | ||
| 1 | class C { | ||
| 2 | m(a, a) { return a; } | ||
| : ^ | ||
| 3 | static m2(a, a) { return a; } | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-method/input.js:3:1] | ||
| 2 | m(a, a) { return a; } | ||
| 3 | static m2(a, a) { return a; } | ||
| : ^ | ||
| 4 | async m3(a, a) { return a; } | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-method/input.js:4:1] | ||
| 3 | static m2(a, a) { return a; } | ||
| 4 | async m3(a, a) { return a; } | ||
| : ^ | ||
| 5 | *m4(a, a) { return a; } | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/class-method/input.js:5:1] | ||
| 4 | async m3(a, a) { return a; } | ||
| 5 | *m4(a, a) { return a; } | ||
| : ^ | ||
| 6 | } | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| ({ *m(a, a) { return a; } }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/generator-method/input.js:1:1] | ||
| 1 | ({ *m(a, a) { return a; } }); | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| function f([a], a) {} | ||
| function g({a}, a) {} | ||
| function h(a, a = 1) {} | ||
| function i(a = 1, a) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/non-simple-fn/input.js:1:1] | ||
| 1 | function f([a], a) {} | ||
| : ^ | ||
| 2 | function g({a}, a) {} | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/non-simple-fn/input.js:2:1] | ||
| 1 | function f([a], a) {} | ||
| 2 | function g({a}, a) {} | ||
| : ^ | ||
| 3 | function h(a, a = 1) {} | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/non-simple-fn/input.js:3:1] | ||
| 2 | function g({a}, a) {} | ||
| 3 | function h(a, a = 1) {} | ||
| : ^ | ||
| 4 | function i(a = 1, a) {} | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/non-simple-fn/input.js:4:1] | ||
| 3 | function h(a, a = 1) {} | ||
| 4 | function i(a = 1, a) {} | ||
| : ^ | ||
| `---- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| ({ m(a, a) { return a; } }); | ||
| ({ m([a], a) { return a; } }); | ||
| ({ m({a}, a) { return a; } }); | ||
| ({ m(a, a = 1) { return a; } }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/object-method/input.js:1:1] | ||
| 1 | ({ m(a, a) { return a; } }); | ||
| : ^ | ||
| 2 | ({ m([a], a) { return a; } }); | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/object-method/input.js:2:1] | ||
| 1 | ({ m(a, a) { return a; } }); | ||
| 2 | ({ m([a], a) { return a; } }); | ||
| : ^ | ||
| 3 | ({ m({a}, a) { return a; } }); | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/object-method/input.js:3:1] | ||
| 2 | ({ m([a], a) { return a; } }); | ||
| 3 | ({ m({a}, a) { return a; } }); | ||
| : ^ | ||
| 4 | ({ m(a, a = 1) { return a; } }); | ||
| `---- | ||
| x Duplicate parameter name not allowed in this context: a | ||
| ,-[$DIR/tests/errors/unique-formal-params/object-method/input.js:4:1] | ||
| 3 | ({ m({a}, a) { return a; } }); | ||
| 4 | ({ m(a, a = 1) { return a; } }); | ||
| : ^ | ||
| `---- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please route the other TypeScript arrow parsers through this new uniqueness check as well. This closure only runs in the main parenthesized-arrow path, but
parse_paren_expr_or_arrow_fnreturns earlier for the conditional-expression typed-arrow slow path, andtry_parse_ts_generic_async_arrow_fnparses generic async arrows separately; in sloppy TS both still accept simple duplicates such ascond ? (a, a): number => a : 0andasync <T>(a, a) => a, so the new UniqueFormalParameters enforcement remains incomplete for arrows.Useful? React with 👍 / 👎.