Skip to content

Commit

Permalink
feat(es/parser): Disallow let let (#9484)
Browse files Browse the repository at this point in the history
**Related issue:**

 - Closes #8269
  • Loading branch information
kdy1 authored Aug 22, 2024
1 parent ede1a52 commit 1121bc0
Show file tree
Hide file tree
Showing 20 changed files with 86 additions and 49 deletions.
6 changes: 6 additions & 0 deletions .changeset/cyan-rules-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
swc_core: patch
swc_ecma_parser: patch
---

feat(es/parser): Disallow `let let`
2 changes: 1 addition & 1 deletion crates/swc/tests/tsc-references/for-of51.1.normal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// [for-of51.ts]
//! x `let` cannot be used as an identifier in strict mode
//! x Unexpected token `let`. Expected let is reserved in const, let, class declaration
//! ,----
//! 1 | for (let let of []) {}
//! : ^^^
Expand Down
2 changes: 1 addition & 1 deletion crates/swc/tests/tsc-references/for-of51.2.minified.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//// [for-of51.ts]
//! x `let` cannot be used as an identifier in strict mode
//! x Unexpected token `let`. Expected let is reserved in const, let, class declaration
//! ,----
//! 1 | for (let let of []) {}
//! : ^^^
Expand Down
19 changes: 13 additions & 6 deletions crates/swc_ecma_parser/src/parser/class_and_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<I: Tokens> Parser<I> {
expect!(p, "class");

let ident = p
.parse_maybe_opt_binding_ident(is_ident_required)?
.parse_maybe_opt_binding_ident(is_ident_required, true)?
.map(Ident::from);
if p.input.syntax().typescript() {
if let Some(span) = ident.invalid_class_name() {
Expand Down Expand Up @@ -1194,15 +1194,15 @@ impl<I: Tokens> Parser<I> {
in_class_field: false,
..self.ctx()
})
.parse_maybe_opt_binding_ident(is_ident_required)?
.parse_maybe_opt_binding_ident(is_ident_required, false)?
} else {
// function declaration does not change context for `BindingIdentifier`.
self.with_ctx(Context {
allow_direct_super: false,
in_class_field: false,
..self.ctx()
})
.parse_maybe_opt_binding_ident(is_ident_required)?
.parse_maybe_opt_binding_ident(is_ident_required, false)?
}
.map(Ident::from);

Expand Down Expand Up @@ -1254,11 +1254,18 @@ impl<I: Tokens> Parser<I> {
}

/// If `required` is `true`, this never returns `None`.
fn parse_maybe_opt_binding_ident(&mut self, required: bool) -> PResult<Option<Ident>> {
fn parse_maybe_opt_binding_ident(
&mut self,
required: bool,
disallow_let: bool,
) -> PResult<Option<Ident>> {
if required {
self.parse_binding_ident().map(|v| v.id).map(Some)
self.parse_binding_ident(disallow_let)
.map(|v| v.id)
.map(Some)
} else {
Ok(self.parse_opt_binding_ident()?.map(|v| v.id))
self.parse_opt_binding_ident(disallow_let)
.map(|v| v.map(|v| v.id))
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_parser/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ impl<I: Tokens> Parser<I> {
return Ok(id.into());
}

let ident = self.parse_binding_ident()?;
let ident = self.parse_binding_ident(false)?;
if self.input.syntax().typescript() && ident.sym == "as" && !is!(self, "=>") {
// async as type
let type_ann = self.in_type().parse_with(|p| p.parse_ts_type())?;
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_parser/src/parser/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ impl<I: Tokens> ParseObject<Pat> for Parser<I> {
// spread element
let dot3_token = span!(self, start);

let arg = Box::new(self.parse_binding_pat_or_ident()?);
let arg = Box::new(self.parse_binding_pat_or_ident(false)?);

return Ok(ObjectPatProp::Rest(RestPat {
span: span!(self, start),
Expand Down
35 changes: 23 additions & 12 deletions crates/swc_ecma_parser/src/parser/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ use swc_common::Spanned;
use super::{util::ExprExt, *};
use crate::{
parser::{class_and_fn::is_not_this, expr::AssignTargetOrSpread},
token::IdentLike,
token::{IdentLike, Keyword},
};

impl<I: Tokens> Parser<I> {
pub fn parse_pat(&mut self) -> PResult<Pat> {
self.parse_binding_pat_or_ident()
self.parse_binding_pat_or_ident(false)
}

pub(super) fn parse_opt_binding_ident(&mut self) -> PResult<Option<BindingIdent>> {
pub(super) fn parse_opt_binding_ident(
&mut self,
disallow_let: bool,
) -> PResult<Option<BindingIdent>> {
trace_cur!(self, parse_opt_binding_ident);

if is!(self, BindingIdent) || (self.input.syntax().typescript() && is!(self, "this")) {
self.parse_binding_ident().map(Some)
self.parse_binding_ident(disallow_let).map(Some)
} else {
Ok(None)
}
Expand All @@ -27,9 +30,15 @@ impl<I: Tokens> Parser<I> {
/// babel: `parseBindingIdentifier`
///
/// spec: `BindingIdentifier`
pub(super) fn parse_binding_ident(&mut self) -> PResult<BindingIdent> {
pub(super) fn parse_binding_ident(&mut self, disallow_let: bool) -> PResult<BindingIdent> {
trace_cur!(self, parse_binding_ident);

if disallow_let {
if let Some(Token::Word(Word::Keyword(Keyword::Let))) = self.input.cur() {
unexpected!(self, "let is reserved in const, let, class declaration")
}
}

// "yield" and "await" is **lexically** accepted.
let ident = self.parse_ident(true, true)?;
if ident.is_reserved_in_strict_bind() {
Expand All @@ -45,11 +54,11 @@ impl<I: Tokens> Parser<I> {
Ok(ident.into())
}

pub(super) fn parse_binding_pat_or_ident(&mut self) -> PResult<Pat> {
pub(super) fn parse_binding_pat_or_ident(&mut self, disallow_let: bool) -> PResult<Pat> {
trace_cur!(self, parse_binding_pat_or_ident);

match *cur!(self, true) {
tok!("yield") | Word(..) => self.parse_binding_ident().map(Pat::from),
tok!("yield") | Word(..) => self.parse_binding_ident(disallow_let).map(Pat::from),
tok!('[') => self.parse_array_binding_pat(),
tok!('{') => self.parse_object(),
// tok!('(') => {
Expand All @@ -67,7 +76,7 @@ impl<I: Tokens> Parser<I> {
trace_cur!(self, parse_binding_element);

let start = cur_pos!(self);
let left = self.parse_binding_pat_or_ident()?;
let left = self.parse_binding_pat_or_ident(false)?;

if eat!(self, '=') {
let right = self.include_in_expr(true).parse_assignment_expr()?;
Expand Down Expand Up @@ -116,7 +125,7 @@ impl<I: Tokens> Parser<I> {
is_rest = true;
let dot3_token = span!(self, start);

let pat = self.parse_binding_pat_or_ident()?;
let pat = self.parse_binding_pat_or_ident(false)?;
rest_span = span!(self, start);
let pat = RestPat {
span: rest_span,
Expand Down Expand Up @@ -299,7 +308,7 @@ impl<I: Tokens> Parser<I> {
is_rest = true;
let dot3_token = span!(self, pat_start);

let pat = self.parse_binding_pat_or_ident()?;
let pat = self.parse_binding_pat_or_ident(false)?;
let type_ann = if self.input.syntax().typescript() && is!(self, ':') {
let cur_pos = cur_pos!(self);
Some(self.parse_ts_type_ann(/* eat_colon */ true, cur_pos)?)
Expand Down Expand Up @@ -412,7 +421,7 @@ impl<I: Tokens> Parser<I> {
let pat = if eat!(self, "...") {
let dot3_token = span!(self, pat_start);

let mut pat = self.parse_binding_pat_or_ident()?;
let mut pat = self.parse_binding_pat_or_ident(false)?;

if eat!(self, '=') {
let right = self.parse_assignment_expr()?;
Expand Down Expand Up @@ -940,7 +949,9 @@ mod tests {
}

fn object_pat(s: &'static str) -> Pat {
test_parser(s, Syntax::default(), |p| p.parse_binding_pat_or_ident())
test_parser(s, Syntax::default(), |p| {
p.parse_binding_pat_or_ident(false)
})
}

fn ident(s: &str) -> Ident {
Expand Down
8 changes: 5 additions & 3 deletions crates/swc_ecma_parser/src/parser/stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,7 +753,7 @@ impl<'a, I: Tokens> Parser<I> {
/// It's optional since es2019
fn parse_catch_param(&mut self) -> PResult<Option<Pat>> {
if eat!(self, '(') {
let mut pat = self.parse_binding_pat_or_ident()?;
let mut pat = self.parse_binding_pat_or_ident(false)?;

let type_ann_start = cur_pos!(self);

Expand Down Expand Up @@ -967,7 +967,9 @@ impl<'a, I: Tokens> Parser<I> {
) -> PResult<VarDeclarator> {
let start = cur_pos!(self);

let mut name = self.parse_binding_pat_or_ident()?;
let is_let_or_const = matches!(kind, VarDeclKind::Let | VarDeclKind::Const);

let mut name = self.parse_binding_pat_or_ident(is_let_or_const)?;

let definite = if self.input.syntax().typescript() {
match name {
Expand Down Expand Up @@ -1332,7 +1334,7 @@ impl<'a, I: Tokens> Parser<I> {
}

if is_using_decl {
let name = self.parse_binding_ident()?;
let name = self.parse_binding_ident(false)?;
let decl = VarDeclarator {
name: name.into(),
span: span!(self, start),
Expand Down
10 changes: 5 additions & 5 deletions crates/swc_ecma_parser/src/parser/stmt/module_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,12 @@ impl<I: Tokens> Parser<I> {
}));
}

let maybe_as: Ident = self.parse_binding_ident()?.into();
let maybe_as: Ident = self.parse_binding_ident(false)?.into();
if maybe_as.sym == "as" {
if is!(self, IdentName) {
// `import { type as as as } from 'mod'`
// `import { type as as foo } from 'mod'`
let local: Ident = self.parse_binding_ident()?.into();
let local: Ident = self.parse_binding_ident(false)?.into();

if type_only {
self.emit_err(orig_name.span, SyntaxError::TS2206);
Expand Down Expand Up @@ -289,7 +289,7 @@ impl<I: Tokens> Parser<I> {
}

if eat!(self, "as") {
let local: Ident = self.parse_binding_ident()?.into();
let local: Ident = self.parse_binding_ident(false)?.into();
return Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, local.span.hi()),
local,
Expand All @@ -316,7 +316,7 @@ impl<I: Tokens> Parser<I> {
}
ModuleExportName::Str(orig_str) => {
if eat!(self, "as") {
let local: Ident = self.parse_binding_ident()?.into();
let local: Ident = self.parse_binding_ident(false)?.into();
Ok(ImportSpecifier::Named(ImportNamedSpecifier {
span: Span::new(start, local.span.hi()),
local,
Expand Down Expand Up @@ -344,7 +344,7 @@ impl<I: Tokens> Parser<I> {
in_generator: false,
..self.ctx()
};
Ok(self.with_ctx(ctx).parse_binding_ident()?.into())
Ok(self.with_ctx(ctx).parse_binding_ident(false)?.into())
}

fn parse_export(&mut self, mut decorators: Vec<Decorator>) -> PResult<ModuleDecl> {
Expand Down
2 changes: 1 addition & 1 deletion crates/swc_ecma_parser/src/parser/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1247,7 +1247,7 @@ impl<I: Tokens> Parser<I> {
return Ok(true);
}

if (is!(self, '{') || is!(self, '[')) && self.parse_binding_pat_or_ident().is_ok() {
if (is!(self, '{') || is!(self, '[')) && self.parse_binding_pat_or_ident(false).is_ok() {
return Ok(true);
}

Expand Down
9 changes: 9 additions & 0 deletions crates/swc_ecma_parser/tests/errors/issue-8269/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
let let
}
{
const let = 0
}
{
class let { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/errors/issue-8269/input.js:2:1]
1 | {
2 | let let
: ^^^
3 | }
`----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
x Expression expected
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/test262-parser/fail/0abefbc80bf651fa.js:1:1]
1 | for (let let;;;) {}
: ^
: ^^^
`----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
x Expression expected
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/test262-parser/fail/39551fb86dcd3b29.js:1:1]
1 | for (const let = 1;;;) {}
: ^
: ^^^
`----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
x Expression expected
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/test262-parser/fail/49624f905645b7d0.js:1:1]
1 | for (let x, y, z, let;;;) {}
: ^
: ^^^
`----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
x Expression expected
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/test262-parser/fail/ac4ee5fb095faad0.js:1:1]
1 | for (let x, y, z, let = 1;;;) {}
: ^
: ^^^
`----
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
x Expression expected
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/test262-parser/fail/b02b296bd115b9b9.js:1:1]
1 | for (const x = 1, y = 2, z = 3, let = 0;;;) {}
: ^
: ^^^
`----
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
x `let` cannot be used as an identifier in strict mode
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/typescript-errors/issue-1391/case2/input.ts:2:1]
1 | let a = 0,
2 | let b = 1;
: ^^^
`----
x Expected a semicolon
,-[$DIR/tests/typescript-errors/issue-1391/case2/input.ts:2:1]
1 | let a = 0,
2 | let b = 1;
: ^
`----
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
x `let` cannot be used as an identifier in strict mode
x Unexpected token `let`. Expected let is reserved in const, let, class declaration
,-[$DIR/tests/typescript-errors/let/index.ts:3:1]
2 |
3 | let let;
Expand Down
1 change: 1 addition & 0 deletions crates/swc_ecma_parser/tests/typescript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn spec(file: PathBuf) {
#[testing::fixture(
"tests/tsc/**/*.ts",
exclude(
"for-of51.ts",
"parserArrowFunctionExpression11",
"esDecorators-decoratorExpression.1"
)
Expand Down

0 comments on commit 1121bc0

Please sign in to comment.