diff --git a/ecmascript/ast/src/lib.rs b/ecmascript/ast/src/lib.rs index 54f3bd71d3e..06670d10a41 100644 --- a/ecmascript/ast/src/lib.rs +++ b/ecmascript/ast/src/lib.rs @@ -33,7 +33,7 @@ pub use self::{ JSXOpeningFragment, JSXSpreadChild, JSXText, }, lit::{Bool, Lit, Null, Number, Regex, RegexFlags, Str}, - module::{Module, ModuleItem}, + module::{Module, ModuleItem, Script}, module_decl::{ ExportAll, ExportDefaultDecl, ExportSpecifier, ImportDecl, ImportDefault, ImportSpecific, ImportSpecifier, ImportStarAs, ModuleDecl, NamedExport, NamedExportSpecifier, diff --git a/ecmascript/ast/src/module.rs b/ecmascript/ast/src/module.rs index b7622e6eedf..f751d374d61 100644 --- a/ecmascript/ast/src/module.rs +++ b/ecmascript/ast/src/module.rs @@ -1,10 +1,19 @@ use crate::{module_decl::ModuleDecl, stmt::Stmt}; +use swc_atoms::JsWord; use swc_common::{ast_node, Span}; #[ast_node] pub struct Module { pub span: Span, pub body: Vec, + pub shebang: Option, +} + +#[ast_node] +pub struct Script { + pub span: Span, + pub body: Vec, + pub shebang: Option, } #[ast_node] diff --git a/ecmascript/codegen/src/lib.rs b/ecmascript/codegen/src/lib.rs index 40aac5bb2dd..006672653d5 100644 --- a/ecmascript/codegen/src/lib.rs +++ b/ecmascript/codegen/src/lib.rs @@ -72,7 +72,7 @@ pub struct Emitter<'a> { } impl<'a> Emitter<'a> { - pub fn emit_script(&mut self, stmts: &[Stmt]) -> Result { + pub fn emit_stmts(&mut self, stmts: &[Stmt]) -> Result { let span = if stmts.is_empty() { DUMMY_SP } else { @@ -91,6 +91,21 @@ impl<'a> Emitter<'a> { #[emitter] pub fn emit_module(&mut self, node: &Module) -> Result { + if let Some(ref shebang) = node.shebang { + punct!("#!"); + self.wr.write_lit(DUMMY_SP, &*shebang)?; + } + for stmt in &node.body { + emit!(stmt); + } + } + + #[emitter] + pub fn emit_script(&mut self, node: &Script) -> Result { + if let Some(ref shebang) = node.shebang { + punct!("#!"); + self.wr.write_lit(DUMMY_SP, &*shebang)?; + } for stmt in &node.body { emit!(stmt); } @@ -1462,14 +1477,6 @@ impl<'a> Emitter<'a> { /// Statements impl<'a> Emitter<'a> { - pub fn emit_stmts(&mut self, stmts: &[Stmt]) -> Result { - for stmt in stmts { - self.emit_stmt(stmt)?; - } - - Ok(()) - } - #[emitter] pub fn emit_stmt(&mut self, node: &Stmt) -> Result { match *node { diff --git a/ecmascript/parser/src/lexer/mod.rs b/ecmascript/parser/src/lexer/mod.rs index 81ecce15548..7dd19c1c8f9 100644 --- a/ecmascript/parser/src/lexer/mod.rs +++ b/ecmascript/parser/src/lexer/mod.rs @@ -758,6 +758,16 @@ impl<'a, I: Input> Lexer<'a, I> { )) } + fn read_shebang(&mut self) -> LexResult> { + if self.input.cur() != Some('#') || self.input.peek() != Some('!') { + return Ok(None); + } + self.input.bump(); + self.input.bump(); + let s = self.input.uncons_while(|c| !c.is_line_break()); + Ok(Some(s.into())) + } + fn read_tmpl_token(&mut self, start_of_tpl: BytePos) -> LexResult { let start = self.cur_pos(); diff --git a/ecmascript/parser/src/lexer/state.rs b/ecmascript/parser/src/lexer/state.rs index 6f0b90c1453..2ae301007dc 100644 --- a/ecmascript/parser/src/lexer/state.rs +++ b/ecmascript/parser/src/lexer/state.rs @@ -99,43 +99,41 @@ impl<'a, I: Input> Lexer<'a, I> { impl<'a, I: Input> Iterator for Lexer<'a, I> { type Item = TokenAndSpan; fn next(&mut self) -> Option { - self.state.had_line_break = self.state.is_first; - self.state.is_first = false; - - // skip spaces before getting next character, if we are allowed to. - if self.state.can_skip_space() { - let start = self.cur_pos(); - - match self.skip_space() { - Err(err) => { - return Some(Token::Error(err)).map(|token| { - // Attatch span to token. - TokenAndSpan { - token, - had_line_break: self.had_line_break_before_last(), - span: self.span(start), - } - }); - } - _ => {} - } - }; - - let c = match self.input.cur() { - Some(c) => c, - None => return None, - }; - - // println!( - // "\tContext: ({:?}) {:?}", - // self.input.cur().unwrap(), - // self.state.context.0 - // ); - let start = self.cur_pos(); - self.state.start = start; let res = (|| -> Result, _> { + if self.state.is_first { + if let Some(shebang) = self.read_shebang()? { + return Ok(Some(Token::Shebang(shebang))); + } + } + + self.state.had_line_break = self.state.is_first; + self.state.is_first = false; + + // skip spaces before getting next character, if we are allowed to. + if self.state.can_skip_space() { + match self.skip_space() { + Err(err) => { + return Err(err); + } + _ => {} + } + }; + + let c = match self.input.cur() { + Some(c) => c, + None => return Ok(None), + }; + + // println!( + // "\tContext: ({:?}) {:?}", + // self.input.cur().unwrap(), + // self.state.context.0 + // ); + + self.state.start = start; + if self.syntax.typescript() && self.ctx.in_type { if c == '<' { self.input.bump(); diff --git a/ecmascript/parser/src/lexer/tests.rs b/ecmascript/parser/src/lexer/tests.rs index 6a858415b01..45cfb0fd429 100644 --- a/ecmascript/parser/src/lexer/tests.rs +++ b/ecmascript/parser/src/lexer/tests.rs @@ -896,6 +896,14 @@ fn max_integer() { lex_tokens(::Syntax::default(), "1.7976931348623157e+308"); } +#[test] +fn shebang() { + assert_eq!( + lex_tokens(::Syntax::default(), "#!/usr/bin/env node",), + vec![Token::Shebang("/usr/bin/env node".into())] + ); +} + #[bench] fn lex_colors_js(b: &mut Bencher) { b.bytes = include_str!("../../colors.js").len() as _; diff --git a/ecmascript/parser/src/parser/mod.rs b/ecmascript/parser/src/parser/mod.rs index 42fe0ea2c9c..21d950c88a3 100644 --- a/ecmascript/parser/src/parser/mod.rs +++ b/ecmascript/parser/src/parser/mod.rs @@ -58,17 +58,28 @@ impl<'a, I: Input> Parser<'a, I> { self.input.take_comments() } - pub fn parse_script(&mut self) -> PResult<'a, (Vec)> { + pub fn parse_script(&mut self) -> PResult<'a, Script> { + let start = cur_pos!(); + + let shebang = self.parse_shebang()?; + let ctx = Context { module: false, ..self.ctx() }; self.set_ctx(ctx); - self.parse_block_body(true, true, None) + self.parse_block_body(true, true, None).map(|body| Script { + span: span!(start), + body, + shebang, + }) } pub fn parse_module(&mut self) -> PResult<'a, Module> { + let start = cur_pos!(); + + let shebang = self.parse_shebang()?; //TODO: parse() -> PResult<'a, Program> let ctx = Context { module: true, @@ -78,13 +89,23 @@ impl<'a, I: Input> Parser<'a, I> { // Module code is always in strict mode self.set_ctx(ctx); - let start = cur_pos!(); self.parse_block_body(true, true, None).map(|body| Module { span: span!(start), body, + shebang, }) } + fn parse_shebang(&mut self) -> PResult<'a, Option> { + match *cur!(false)? { + Token::Shebang(..) => match bump!() { + Token::Shebang(v) => Ok(Some(v)), + _ => unreachable!(), + }, + _ => Ok(None), + } + } + fn ctx(&self) -> Context { self.input.get_ctx() } diff --git a/ecmascript/parser/src/parser/stmt/mod.rs b/ecmascript/parser/src/parser/stmt/mod.rs index 187e777d298..2a3dba56139 100644 --- a/ecmascript/parser/src/parser/stmt/mod.rs +++ b/ecmascript/parser/src/parser/stmt/mod.rs @@ -1061,4 +1061,21 @@ export default App"#; ); } + #[test] + fn shebang() { + let src = "#!/usr/bin/env node"; + test_parser( + src, + Syntax::Es(EsConfig { + ..Default::default() + }), + |p| { + p.parse_module().map_err(|mut e| { + e.emit(); + () + }) + }, + ); + } + } diff --git a/ecmascript/parser/src/token/mod.rs b/ecmascript/parser/src/token/mod.rs index fcb7dbb9bce..0b488251de0 100644 --- a/ecmascript/parser/src/token/mod.rs +++ b/ecmascript/parser/src/token/mod.rs @@ -131,6 +131,7 @@ pub(crate) enum Token { JSXTagStart, JSXTagEnd, + Shebang(JsWord), Error(#[cfg_attr(feature = "fold", fold(ignore))] Error), } diff --git a/ecmascript/parser/tests/test262.rs b/ecmascript/parser/tests/test262.rs index 49ac0935152..a0a09ff47bb 100644 --- a/ecmascript/parser/tests/test262.rs +++ b/ecmascript/parser/tests/test262.rs @@ -262,7 +262,7 @@ fn identity_tests(tests: &mut Vec) -> Result<(), io::Error> { Ok(()) } -fn parse_script(file_name: &Path) -> Result, NormalizedOutput> { +fn parse_script(file_name: &Path) -> Result { with_parser(file_name, |p| p.parse_script()) } fn parse_module<'a>(file_name: &Path) -> Result { diff --git a/ecmascript/transforms/src/compat/es2018/object_rest_spread/mod.rs b/ecmascript/transforms/src/compat/es2018/object_rest_spread/mod.rs index fcd8b2b34a8..85567ce6ff3 100644 --- a/ecmascript/transforms/src/compat/es2018/object_rest_spread/mod.rs +++ b/ecmascript/transforms/src/compat/es2018/object_rest_spread/mod.rs @@ -72,8 +72,7 @@ macro_rules! impl_for_for_stmt { left } VarDeclOrPat::Pat(pat) => { - let var_ident = - quote_ident!(DUMMY_SP.apply_mark(Mark::fresh(Mark::root())), "_ref"); + let var_ident = private_ident!("_ref"); let index = self.vars.len(); let pat = self.fold_rest(pat, box Expr::Ident(var_ident.clone()), false, true); @@ -161,7 +160,10 @@ impl Fold> for RestFolder { let var_ident = match decl.init { Some(box Expr::Ident(ref ident)) => ident.clone(), - _ => quote_ident!(DUMMY_SP.apply_mark(Mark::fresh(Mark::root())), "_ref"), + _ => match decl.name { + Pat::Ident(ref i) => i.clone(), + _ => private_ident!("_ref"), + }, }; let has_init = decl.init.is_some(); @@ -410,7 +412,10 @@ impl Fold for RestFolder { f.params, match f.body { BlockStmtOrExpr::BlockStmt(block) => block.stmts, - BlockStmtOrExpr::Expr(expr) => vec![Stmt::Expr(expr)], + BlockStmtOrExpr::Expr(expr) => vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(expr), + })], }, ); ArrowExpr { @@ -480,8 +485,19 @@ impl Fold for RestFolder { } impl RestFolder { - #[inline(always)] fn insert_var_if_not_empty(&mut self, idx: usize, decl: VarDeclarator) { + match decl.init { + Some(box Expr::Ident(ref i1)) => match decl.name { + Pat::Ident(ref i2) => { + if *i1 == *i2 { + return; + } + } + _ => {} + }, + _ => {} + } + match decl.name { Pat::Object(ObjectPat { ref props, .. }) => { if props.len() == 0 { @@ -493,8 +509,19 @@ impl RestFolder { self.vars.insert(idx, decl) } - #[inline(always)] fn push_var_if_not_empty(&mut self, decl: VarDeclarator) { + match decl.init { + Some(box Expr::Ident(ref i1)) => match decl.name { + Pat::Ident(ref i2) => { + if *i1 == *i2 { + return; + } + } + _ => {} + }, + _ => {} + } + match decl.name { Pat::Object(ObjectPat { ref props, .. }) => { if props.len() == 0 { diff --git a/ecmascript/transforms/src/compat/es2018/object_rest_spread/tests.rs b/ecmascript/transforms/src/compat/es2018/object_rest_spread/tests.rs index e9ac529f6b0..de035cc4bfe 100644 --- a/ecmascript/transforms/src/compat/es2018/object_rest_spread/tests.rs +++ b/ecmascript/transforms/src/compat/es2018/object_rest_spread/tests.rs @@ -7,6 +7,21 @@ fn tr() -> impl Fold { object_rest_spread() } +test!( + ::swc_ecma_parser::Syntax::default(), + |_| tr(), + issue_181, + r#" +const fn = ({ a, ...otherProps }) => otherProps; +"#, + r#" +const fn = (_param)=>{ + var { a } = _param, otherProps = _objectWithoutProperties(_param, ['a']); + return otherProps; +}; +"# +); + test!( ::swc_ecma_parser::Syntax::default(), |_| tr(), diff --git a/ecmascript/transforms/src/helpers/mod.rs b/ecmascript/transforms/src/helpers/mod.rs index b36b674a838..4c7c878bedc 100644 --- a/ecmascript/transforms/src/helpers/mod.rs +++ b/ecmascript/transforms/src/helpers/mod.rs @@ -43,7 +43,7 @@ macro_rules! add_to { None, ) .parse_script() - .map(|stmts| stmts.fold_with(&mut DropSpan)) + .map(|script| script.body.fold_with(&mut DropSpan)) .map_err(|mut e| { e.emit(); () diff --git a/ecmascript/transforms/src/hygiene/tests.rs b/ecmascript/transforms/src/hygiene/tests.rs index e95f1e39648..0f29d2f3df2 100644 --- a/ecmascript/transforms/src/hygiene/tests.rs +++ b/ecmascript/transforms/src/hygiene/tests.rs @@ -57,6 +57,7 @@ where Ok(Module { span: DUMMY_SP, body: op(tester)?.into_iter().map(ModuleItem::Stmt).collect(), + shebang: None, }) }, expected, diff --git a/ecmascript/transforms/src/modules/amd/mod.rs b/ecmascript/transforms/src/modules/amd/mod.rs index e21a31625c1..7bf40aa8bbd 100644 --- a/ecmascript/transforms/src/modules/amd/mod.rs +++ b/ecmascript/transforms/src/modules/amd/mod.rs @@ -467,7 +467,6 @@ impl Fold for Amd { // ==================== Module { - span: module.span, body: vec![ModuleItem::Stmt(Stmt::Expr(box Expr::Call(CallExpr { span: DUMMY_SP, callee: quote_ident!("define").as_callee(), @@ -500,6 +499,7 @@ impl Fold for Amd { .collect(), type_args: Default::default(), })))], + ..module } } } diff --git a/ecmascript/transforms/src/modules/util.rs b/ecmascript/transforms/src/modules/util.rs index 7b37cfe85d1..630482b1457 100644 --- a/ecmascript/transforms/src/modules/util.rs +++ b/ecmascript/transforms/src/modules/util.rs @@ -442,12 +442,16 @@ macro_rules! var_noop { fn visit(&mut self, _: &$T) {} } }; + + ($T:path, $($rest:tt)*) => { + var_noop!($T); + var_noop!($($rest)*); + }; } -var_noop!(Expr); -var_noop!(ArrowExpr); -var_noop!(Function); -var_noop!(Constructor); +var_noop!(Expr, ArrowExpr, Function, Constructor); + +var_noop!(TsType, TsTypeAnn, TsTypeParam); /// Private `_exports` ident. pub(super) struct Exports(pub Ident); diff --git a/ecmascript/transforms/src/tests.rs b/ecmascript/transforms/src/tests.rs index 3333b7f844e..c9a0c90d6f4 100644 --- a/ecmascript/transforms/src/tests.rs +++ b/ecmascript/transforms/src/tests.rs @@ -71,10 +71,12 @@ impl<'a> Tester<'a> { pub fn parse_stmts(&mut self, file_name: &str, src: &str) -> Result, ()> { let stmts = self.with_parser(file_name, Syntax::default(), src, |p| { - p.parse_script().map_err(|mut e| { - e.emit(); - () - }) + p.parse_script() + .map_err(|mut e| { + e.emit(); + () + }) + .map(|script| script.body) })?; Ok(stmts)