fix(es/parser): Throw an error when function body has use strict and paramaters is not simple (#3278)

This commit is contained in:
RiESAEX 2022-01-16 03:21:02 +08:00 committed by GitHub
parent d396c32fe5
commit 6406b49df2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 579 additions and 23 deletions

View File

@ -88,6 +88,7 @@ pub enum SyntaxError {
InvalidIdentInStrict, InvalidIdentInStrict,
/// 'eval' and 'arguments' are invalid identifier in strict mode. /// 'eval' and 'arguments' are invalid identifier in strict mode.
EvalAndArgumentsInStrict, EvalAndArgumentsInStrict,
IllegalLanguageModeDirective,
UnaryInExp { UnaryInExp {
left: String, left: String,
left_span: Span, left_span: Span,
@ -300,6 +301,9 @@ impl SyntaxError {
SyntaxError::EvalAndArgumentsInStrict => "'eval' and 'arguments' cannot be used as a \ SyntaxError::EvalAndArgumentsInStrict => "'eval' and 'arguments' cannot be used as a \
binding identifier in strict mode" binding identifier in strict mode"
.into(), .into(),
SyntaxError::IllegalLanguageModeDirective => {
"Illegal 'use strict' directive in function with non-simple parameter list.".into()
}
SyntaxError::UnaryInExp { .. } => "** cannot be applied to unary expression".into(), SyntaxError::UnaryInExp { .. } => "** cannot be applied to unary expression".into(),
SyntaxError::Hash => "Unexpected token '#'".into(), SyntaxError::Hash => "Unexpected token '#'".into(),
SyntaxError::LineBreakInThrow => "LineBreak cannot follow 'throw'".into(), SyntaxError::LineBreakInThrow => "LineBreak cannot follow 'throw'".into(),

View File

@ -1,5 +1,5 @@
use super::{ident::MaybeOptionalIdentParser, *}; use super::{ident::MaybeOptionalIdentParser, *};
use crate::{error::SyntaxError, lexer::TokenContext, Tokens}; use crate::{error::SyntaxError, lexer::TokenContext, parser::stmt::IsDirective, Tokens};
use either::Either; use either::Either;
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::{Spanned, SyntaxContext}; use swc_common::{Spanned, SyntaxContext};
@ -667,7 +667,8 @@ impl<'a, I: Tokens> Parser<I> {
self.emit_err(type_ann.type_ann.span(), SyntaxError::TS1093); self.emit_err(type_ann.type_ann.span(), SyntaxError::TS1093);
} }
let body: Option<_> = self.parse_fn_body(false, false)?; let body: Option<_> =
self.parse_fn_body(false, false, params.is_simple_parameter_list())?;
if self.syntax().typescript() && body.is_none() { if self.syntax().typescript() && body.is_none() {
// Declare constructors cannot have assignment pattern in parameters // Declare constructors cannot have assignment pattern in parameters
@ -1110,7 +1111,8 @@ impl<'a, I: Tokens> Parser<I> {
None None
}; };
let body: Option<_> = p.parse_fn_body(is_async, is_generator)?; let body: Option<_> =
p.parse_fn_body(is_async, is_generator, params.is_simple_parameter_list())?;
if p.syntax().typescript() && body.is_none() { if p.syntax().typescript() && body.is_none() {
// Declare functions cannot have assignment pattern in parameters // Declare functions cannot have assignment pattern in parameters
@ -1149,7 +1151,12 @@ impl<'a, I: Tokens> Parser<I> {
} }
} }
pub(super) fn parse_fn_body<T>(&mut self, is_async: bool, is_generator: bool) -> PResult<T> pub(super) fn parse_fn_body<T>(
&mut self,
is_async: bool,
is_generator: bool,
is_simple_parameter_list: bool,
) -> PResult<T>
where where
Self: FnBodyParser<T>, Self: FnBodyParser<T>,
{ {
@ -1173,7 +1180,9 @@ impl<'a, I: Tokens> Parser<I> {
labels: vec![], labels: vec![],
..Default::default() ..Default::default()
}; };
self.with_ctx(ctx).with_state(state).parse_fn_body_inner() self.with_ctx(ctx)
.with_state(state)
.parse_fn_body_inner(is_simple_parameter_list)
} }
} }
@ -1373,13 +1382,25 @@ impl OutputType for Decl {
} }
pub(super) trait FnBodyParser<Body> { pub(super) trait FnBodyParser<Body> {
fn parse_fn_body_inner(&mut self) -> PResult<Body>; fn parse_fn_body_inner(&mut self, is_simple_parameter_list: bool) -> PResult<Body>;
}
fn has_use_strict(block: &BlockStmt) -> Option<Span> {
match block.stmts.iter().find(|stmt| stmt.is_use_strict()) {
Some(Stmt::Expr(ExprStmt { span, expr: _ })) => Some(*span),
_ => None,
}
} }
impl<I: Tokens> FnBodyParser<BlockStmtOrExpr> for Parser<I> { impl<I: Tokens> FnBodyParser<BlockStmtOrExpr> for Parser<I> {
fn parse_fn_body_inner(&mut self) -> PResult<BlockStmtOrExpr> { fn parse_fn_body_inner(&mut self, is_simple_parameter_list: bool) -> PResult<BlockStmtOrExpr> {
if is!(self, '{') { if is!(self, '{') {
self.parse_block(false).map(BlockStmtOrExpr::BlockStmt) self.parse_block(false).map(|block_stmt| {
if !self.input.syntax().typescript() && !is_simple_parameter_list {
if let Some(span) = has_use_strict(&block_stmt) {
self.emit_err(span, SyntaxError::IllegalLanguageModeDirective);
}
}
BlockStmtOrExpr::BlockStmt(block_stmt)
})
} else { } else {
self.parse_assignment_expr().map(BlockStmtOrExpr::Expr) self.parse_assignment_expr().map(BlockStmtOrExpr::Expr)
} }
@ -1387,12 +1408,51 @@ impl<I: Tokens> FnBodyParser<BlockStmtOrExpr> for Parser<I> {
} }
impl<I: Tokens> FnBodyParser<Option<BlockStmt>> for Parser<I> { impl<I: Tokens> FnBodyParser<Option<BlockStmt>> for Parser<I> {
fn parse_fn_body_inner(&mut self) -> PResult<Option<BlockStmt>> { fn parse_fn_body_inner(
&mut self,
is_simple_parameter_list: bool,
) -> PResult<Option<BlockStmt>> {
// allow omitting body and allow placing `{` on next line // allow omitting body and allow placing `{` on next line
if self.input.syntax().typescript() && !is!(self, '{') && eat!(self, ';') { if self.input.syntax().typescript() && !is!(self, '{') && eat!(self, ';') {
return Ok(None); return Ok(None);
} }
self.include_in_expr(true).parse_block(true).map(Some) let block = self.include_in_expr(true).parse_block(true);
block.map(|block_stmt| {
if !self.input.syntax().typescript() && !is_simple_parameter_list {
if let Some(span) = has_use_strict(&block_stmt) {
self.emit_err(span, SyntaxError::IllegalLanguageModeDirective);
}
}
Some(block_stmt)
})
}
}
pub(super) trait IsSimpleParameterList {
fn is_simple_parameter_list(&self) -> bool;
}
impl IsSimpleParameterList for Vec<Param> {
fn is_simple_parameter_list(&self) -> bool {
self.iter().all(|param| matches!(param.pat, Pat::Ident(_)))
}
}
impl IsSimpleParameterList for Vec<Pat> {
fn is_simple_parameter_list(&self) -> bool {
self.iter().all(|pat| matches!(pat, Pat::Ident(_)))
}
}
impl IsSimpleParameterList for Vec<ParamOrTsParamProp> {
fn is_simple_parameter_list(&self) -> bool {
self.iter().all(|param| {
matches!(
param,
ParamOrTsParamProp::TsParamProp(..)
| ParamOrTsParamProp::Param(Param {
pat: Pat::Ident(_),
..
})
)
})
} }
} }

View File

@ -1,5 +1,7 @@
use super::{pat::PatType, util::ExprExt, *}; use super::{pat::PatType, util::ExprExt, *};
use crate::{lexer::TokenContext, token::AssignOpToken}; use crate::{
lexer::TokenContext, parser::class_and_fn::IsSimpleParameterList, token::AssignOpToken,
};
use either::Either; use either::Either;
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::{ast_node, util::take::Take, Spanned}; use swc_common::{ast_node, util::take::Take, Spanned};
@ -394,7 +396,7 @@ impl<'a, I: Tokens> Parser<I> {
let arg = Pat::from(ident); let arg = Pat::from(ident);
let params = vec![arg]; let params = vec![arg];
expect!(self, "=>"); expect!(self, "=>");
let body = self.parse_fn_body(true, false)?; let body = self.parse_fn_body(true, false, params.is_simple_parameter_list())?;
return Ok(Box::new(Expr::Arrow(ArrowExpr { return Ok(Box::new(Expr::Arrow(ArrowExpr {
span: span!(self, start), span: span!(self, start),
@ -407,7 +409,7 @@ impl<'a, I: Tokens> Parser<I> {
}))); })));
} else if can_be_arrow && !self.input.had_line_break_before_cur() && eat!(self, "=>") { } else if can_be_arrow && !self.input.had_line_break_before_cur() && eat!(self, "=>") {
let params = vec![id.into()]; let params = vec![id.into()];
let body = self.parse_fn_body(false, false)?; let body = self.parse_fn_body(false, false, params.is_simple_parameter_list())?;
return Ok(Box::new(Expr::Arrow(ArrowExpr { return Ok(Box::new(Expr::Arrow(ArrowExpr {
span: span!(self, start), span: span!(self, start),
@ -681,12 +683,16 @@ impl<'a, I: Tokens> Parser<I> {
expect!(p, "=>"); expect!(p, "=>");
let params = p let params: Vec<Pat> = p
.parse_paren_items_as_params(items_ref.clone())? .parse_paren_items_as_params(items_ref.clone())?
.into_iter() .into_iter()
.collect(); .collect();
let body: BlockStmtOrExpr = p.parse_fn_body(async_span.is_some(), false)?; let body: BlockStmtOrExpr = p.parse_fn_body(
async_span.is_some(),
false,
params.is_simple_parameter_list(),
)?;
if is_direct_child_of_cond { if is_direct_child_of_cond {
if !is_one_of!(p, ':', ';') { if !is_one_of!(p, ':', ';') {
@ -733,12 +739,16 @@ impl<'a, I: Tokens> Parser<I> {
} }
expect!(self, "=>"); expect!(self, "=>");
let params = self let params: Vec<Pat> = self
.parse_paren_items_as_params(paren_items)? .parse_paren_items_as_params(paren_items)?
.into_iter() .into_iter()
.collect(); .collect();
let body: BlockStmtOrExpr = self.parse_fn_body(async_span.is_some(), false)?; let body: BlockStmtOrExpr = self.parse_fn_body(
async_span.is_some(),
false,
params.is_simple_parameter_list(),
)?;
let arrow_expr = ArrowExpr { let arrow_expr = ArrowExpr {
span: span!(self, expr_start), span: span!(self, expr_start),
is_async: async_span.is_some(), is_async: async_span.is_some(),
@ -1633,12 +1643,13 @@ impl<'a, I: Tokens> Parser<I> {
_ => false, _ => false,
} }
} { } {
let params = self let params: Vec<Pat> = self
.parse_paren_items_as_params(items.clone())? .parse_paren_items_as_params(items.clone())?
.into_iter() .into_iter()
.collect(); .collect();
let body: BlockStmtOrExpr = self.parse_fn_body(false, false)?; let body: BlockStmtOrExpr =
self.parse_fn_body(false, false, params.is_simple_parameter_list())?;
let span = span!(self, start); let span = span!(self, start);
items.push(PatOrExprOrSpread::ExprOrSpread(ExprOrSpread { items.push(PatOrExprOrSpread::ExprOrSpread(ExprOrSpread {

View File

@ -301,3 +301,58 @@ fn issue_2853_2() {
Ok(program) Ok(program)
}); });
} }
#[test]
fn illegal_language_mode_directive1() {
test_parser(
r#"function f(a = 0) { "use strict"; }"#,
Default::default(),
|p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(
errors,
vec![Error {
error: Box::new((
Span {
lo: BytePos(20),
hi: BytePos(33),
ctxt: swc_common::SyntaxContext::empty()
},
crate::parser::SyntaxError::IllegalLanguageModeDirective
))
}]
);
Ok(program)
},
);
}
#[test]
fn illegal_language_mode_directive2() {
test_parser(
r#"let f = (a = 0) => { "use strict"; }"#,
Default::default(),
|p| {
let program = p.parse_program()?;
let errors = p.take_errors();
assert_eq!(
errors,
vec![Error {
error: Box::new((
Span {
lo: BytePos(21),
hi: BytePos(34),
ctxt: swc_common::SyntaxContext::empty()
},
crate::parser::SyntaxError::IllegalLanguageModeDirective
))
}]
);
Ok(program)
},
);
}

View File

@ -1,5 +1,5 @@
use super::*; use super::*;
use crate::lexer::TokenContexts; use crate::{lexer::TokenContexts, parser::class_and_fn::IsSimpleParameterList};
use either::Either; use either::Either;
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::{Spanned, SyntaxContext}; use swc_common::{Spanned, SyntaxContext};
@ -2474,7 +2474,7 @@ impl<I: Tokens> Parser<I> {
let type_params = p.parse_ts_type_params()?; let type_params = p.parse_ts_type_params()?;
// Don't use overloaded parseFunctionParams which would look for "<" again. // Don't use overloaded parseFunctionParams which would look for "<" again.
expect!(p, '('); expect!(p, '(');
let params = p let params: Vec<Pat> = p
.parse_formal_params()? .parse_formal_params()?
.into_iter() .into_iter()
.map(|p| p.pat) .map(|p| p.pat)
@ -2502,7 +2502,7 @@ impl<I: Tokens> Parser<I> {
self.with_ctx(ctx).parse_with(|p| { self.with_ctx(ctx).parse_with(|p| {
let is_generator = false; let is_generator = false;
let is_async = true; let is_async = true;
let body = p.parse_fn_body(true, false)?; let body = p.parse_fn_body(true, false, params.is_simple_parameter_list())?;
Ok(Some(ArrowExpr { Ok(Some(ArrowExpr {
span: span!(p, start), span: span!(p, start),
body, body,

View File

@ -0,0 +1,3 @@
let f = (a = 0) => {
"use strict";
}

View File

@ -0,0 +1,169 @@
warning: Module
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:1
|
1 | / let f = (a = 0) => {
2 | | "use strict";
3 | | }
| |_^
warning: ModuleItem
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:1
|
1 | / let f = (a = 0) => {
2 | | "use strict";
3 | | }
| |_^
warning: Stmt
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:1
|
1 | / let f = (a = 0) => {
2 | | "use strict";
3 | | }
| |_^
warning: Decl
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:1
|
1 | / let f = (a = 0) => {
2 | | "use strict";
3 | | }
| |_^
warning: VarDecl
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:1
|
1 | / let f = (a = 0) => {
2 | | "use strict";
3 | | }
| |_^
warning: VarDeclarator
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:5
|
1 | let f = (a = 0) => {
| _____^
2 | | "use strict";
3 | | }
| |_^
warning: Pat
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:5
|
1 | let f = (a = 0) => {
| ^
warning: Ident
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:5
|
1 | let f = (a = 0) => {
| ^
warning: Expr
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:9
|
1 | let f = (a = 0) => {
| _________^
2 | | "use strict";
3 | | }
| |_^
warning: ArrowExpr
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:9
|
1 | let f = (a = 0) => {
| _________^
2 | | "use strict";
3 | | }
| |_^
warning: Pat
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:10
|
1 | let f = (a = 0) => {
| ^^^^^
warning: AssignPat
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:10
|
1 | let f = (a = 0) => {
| ^^^^^
warning: Pat
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:10
|
1 | let f = (a = 0) => {
| ^
warning: Ident
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:10
|
1 | let f = (a = 0) => {
| ^
warning: Expr
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:14
|
1 | let f = (a = 0) => {
| ^
warning: Lit
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:14
|
1 | let f = (a = 0) => {
| ^
warning: Number
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:14
|
1 | let f = (a = 0) => {
| ^
warning: BlockStmtOrExpr
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:20
|
1 | let f = (a = 0) => {
| ____________________^
2 | | "use strict";
3 | | }
| |_^
warning: BlockStmt
--> $DIR/tests/span/js/issue-3236/arrow_function.js:1:20
|
1 | let f = (a = 0) => {
| ____________________^
2 | | "use strict";
3 | | }
| |_^
warning: Stmt
--> $DIR/tests/span/js/issue-3236/arrow_function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^^
warning: ExprStmt
--> $DIR/tests/span/js/issue-3236/arrow_function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^^
warning: Expr
--> $DIR/tests/span/js/issue-3236/arrow_function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^
warning: Lit
--> $DIR/tests/span/js/issue-3236/arrow_function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^
warning: Str
--> $DIR/tests/span/js/issue-3236/arrow_function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^

View File

@ -0,0 +1,3 @@
function f(a = 0) {
"use strict";
}

View File

@ -0,0 +1,141 @@
warning: Module
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: ModuleItem
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: Stmt
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: Decl
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: FnDecl
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: Ident
--> $DIR/tests/span/js/issue-3236/function.js:1:10
|
1 | function f(a = 0) {
| ^
warning: Function
--> $DIR/tests/span/js/issue-3236/function.js:1:1
|
1 | / function f(a = 0) {
2 | | "use strict";
3 | | }
| |_^
warning: Param
--> $DIR/tests/span/js/issue-3236/function.js:1:12
|
1 | function f(a = 0) {
| ^^^^^
warning: Pat
--> $DIR/tests/span/js/issue-3236/function.js:1:12
|
1 | function f(a = 0) {
| ^^^^^
warning: AssignPat
--> $DIR/tests/span/js/issue-3236/function.js:1:12
|
1 | function f(a = 0) {
| ^^^^^
warning: Pat
--> $DIR/tests/span/js/issue-3236/function.js:1:12
|
1 | function f(a = 0) {
| ^
warning: Ident
--> $DIR/tests/span/js/issue-3236/function.js:1:12
|
1 | function f(a = 0) {
| ^
warning: Expr
--> $DIR/tests/span/js/issue-3236/function.js:1:16
|
1 | function f(a = 0) {
| ^
warning: Lit
--> $DIR/tests/span/js/issue-3236/function.js:1:16
|
1 | function f(a = 0) {
| ^
warning: Number
--> $DIR/tests/span/js/issue-3236/function.js:1:16
|
1 | function f(a = 0) {
| ^
warning: BlockStmt
--> $DIR/tests/span/js/issue-3236/function.js:1:19
|
1 | function f(a = 0) {
| ___________________^
2 | | "use strict";
3 | | }
| |_^
warning: Stmt
--> $DIR/tests/span/js/issue-3236/function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^^
warning: ExprStmt
--> $DIR/tests/span/js/issue-3236/function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^^
warning: Expr
--> $DIR/tests/span/js/issue-3236/function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^
warning: Lit
--> $DIR/tests/span/js/issue-3236/function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^
warning: Str
--> $DIR/tests/span/js/issue-3236/function.js:2:5
|
2 | "use strict";
| ^^^^^^^^^^^^

View File

@ -0,0 +1,3 @@
function f(a = 0) {
"use strict";
}

View File

@ -0,0 +1,107 @@
{
"type": "Script",
"span": {
"start": 0,
"end": 39,
"ctxt": 0
},
"body": [
{
"type": "FunctionDeclaration",
"identifier": {
"type": "Identifier",
"span": {
"start": 9,
"end": 10,
"ctxt": 0
},
"value": "f",
"optional": false
},
"declare": false,
"params": [
{
"type": "Parameter",
"span": {
"start": 11,
"end": 16,
"ctxt": 0
},
"decorators": [],
"pat": {
"type": "AssignmentPattern",
"span": {
"start": 11,
"end": 16,
"ctxt": 0
},
"left": {
"type": "Identifier",
"span": {
"start": 11,
"end": 12,
"ctxt": 0
},
"value": "a",
"optional": false,
"typeAnnotation": null
},
"right": {
"type": "NumericLiteral",
"span": {
"start": 15,
"end": 16,
"ctxt": 0
},
"value": 0.0
},
"typeAnnotation": null
}
}
],
"decorators": [],
"span": {
"start": 0,
"end": 39,
"ctxt": 0
},
"body": {
"type": "BlockStatement",
"span": {
"start": 18,
"end": 39,
"ctxt": 0
},
"stmts": [
{
"type": "ExpressionStatement",
"span": {
"start": 24,
"end": 37,
"ctxt": 0
},
"expression": {
"type": "StringLiteral",
"span": {
"start": 24,
"end": 36,
"ctxt": 0
},
"value": "use strict",
"hasEscape": false,
"kind": {
"type": "normal",
"containsQuote": true
}
}
}
]
},
"generator": false,
"async": false,
"typeParameters": null,
"returnType": null
}
],
"interpreter": null
}