Skip to content
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

feat(es/parser): Disallow let let #9484

Merged
merged 7 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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/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
@@ -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
Loading