swc_ecma_codegen:
 - escape characters (#204)

swc_ecma_parser:
 - fix parsing of `<!--`

swc_ecma_transforms:
 - promote VarCollector to crate-level utility
 - fix es3::member_expr_lits (#206)
 - fix es2015::duplicate_keys pass (#203)
 - improve fixer (paren for assignment) (#201)
 - improve fixer (arrow expression) (#207)
 - make typescript pass strip out type-only exports (#196)
This commit is contained in:
강동윤 2019-02-14 11:03:06 +09:00 committed by GitHub
parent c1a3cc53c1
commit b27829825e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 758 additions and 73 deletions

View File

@ -338,7 +338,16 @@ impl<'a> Emitter<'a> {
// self.wr.write_str_lit(node.span, &s)?;
// return Ok(());
// }
let value = node.value.replace("\\", "\\\\").replace("\n", "\\n");
let value = node
.value
.replace("\\", "\\\\")
.replace('\u{0008}', "\\b")
.replace('\u{000C}', "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")
.replace('\u{000B}', "\\v")
.replace("\0", "\\0");
// let value = node.value.replace("\n", "\\n");
if !node.value.contains("'") {

View File

@ -86,4 +86,15 @@ mod tests {
"import colors, { color } from 'patterns/colors';",
);
}
#[test]
fn issue_204_01() {
assert_min(r#"'\r\n';"#, r#"'\r\n';"#);
}
#[test]
fn issue_204_02() {
assert_min(r#"const a = fn() + '\r\n';"#, r#"const a=fn()+'\r\n';"#);
}
}

View File

@ -1 +1 @@
(' ');
('\t');

View File

@ -1 +1 @@
a(' ');
a('\v');

View File

@ -59,16 +59,16 @@ impl<'a, I: Input> Parser<'a, I> {
}
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);
let start = cur_pos!();
let shebang = self.parse_shebang()?;
self.parse_block_body(true, true, None).map(|body| Script {
span: span!(start),
body,
@ -77,9 +77,6 @@ impl<'a, I: Input> Parser<'a, I> {
}
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,
@ -89,6 +86,9 @@ impl<'a, I: Input> Parser<'a, I> {
// Module code is always in strict mode
self.set_ctx(ctx);
let start = cur_pos!();
let shebang = self.parse_shebang()?;
self.parse_block_body(true, true, None).map(|body| Module {
span: span!(start),
body,

View File

@ -1,6 +1,6 @@
error: Unexpected eof
--> $DIR/tests/test262-parser/fail/ba2d36d35efa68ec.js:1:6
error: Unexpected token None
--> $DIR/tests/test262-parser/fail/ba2d36d35efa68ec.js:1:5
|
1 | a -->
| ^
| ^

View File

@ -1,6 +1,6 @@
error: Unexpected eof
--> $DIR/tests/test262-parser/fail/d942f64886578d87.module.js:1:7
error: Expected Word(from), got None
--> $DIR/tests/test262-parser/fail/d942f64886578d87.module.js:1:1
|
1 | import
| ^
| ^^^^^^

View File

@ -0,0 +1 @@
export type Link = { key: string; text: string };

View File

@ -0,0 +1,186 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
49
),
ctxt: #0
},
body: [
ModuleDecl(
ExportDecl(
TsTypeAlias(
TsTypeAliasDecl {
span: Span {
lo: BytePos(
7
),
hi: BytePos(
49
),
ctxt: #0
},
declare: false,
id: Ident {
sym: Link,
span: Span {
lo: BytePos(
12
),
hi: BytePos(
16
),
ctxt: #0
},
type_ann: None,
optional: false
},
type_params: None,
type_ann: TsTypeLit(
TsTypeLit {
span: Span {
lo: BytePos(
19
),
hi: BytePos(
48
),
ctxt: #0
},
members: [
TsPropertySignature(
TsPropertySignature {
span: Span {
lo: BytePos(
21
),
hi: BytePos(
33
),
ctxt: #0
},
readonly: false,
key: Ident(
Ident {
sym: key,
span: Span {
lo: BytePos(
21
),
hi: BytePos(
24
),
ctxt: #0
},
type_ann: None,
optional: false
}
),
computed: false,
optional: false,
init: None,
params: [],
type_ann: Some(
TsTypeAnn {
span: Span {
lo: BytePos(
24
),
hi: BytePos(
32
),
ctxt: #0
},
type_ann: TsKeywordType(
TsKeywordType {
span: Span {
lo: BytePos(
26
),
hi: BytePos(
32
),
ctxt: #0
},
kind: TsStringKeyword
}
)
}
),
type_params: None
}
),
TsPropertySignature(
TsPropertySignature {
span: Span {
lo: BytePos(
34
),
hi: BytePos(
46
),
ctxt: #0
},
readonly: false,
key: Ident(
Ident {
sym: text,
span: Span {
lo: BytePos(
34
),
hi: BytePos(
38
),
ctxt: #0
},
type_ann: None,
optional: false
}
),
computed: false,
optional: false,
init: None,
params: [],
type_ann: Some(
TsTypeAnn {
span: Span {
lo: BytePos(
38
),
hi: BytePos(
46
),
ctxt: #0
},
type_ann: TsKeywordType(
TsKeywordType {
span: Span {
lo: BytePos(
40
),
hi: BytePos(
46
),
ctxt: #0
},
kind: TsStringKeyword
}
)
}
),
type_params: None
}
)
]
}
)
}
)
)
)
],
shebang: None
}

View File

@ -0,0 +1,2 @@
type Link = { key: string; text: string };
export { Link };

View File

@ -0,0 +1,232 @@
Module {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
59
),
ctxt: #0
},
body: [
Stmt(
Decl(
TsTypeAlias(
TsTypeAliasDecl {
span: Span {
lo: BytePos(
0
),
hi: BytePos(
42
),
ctxt: #0
},
declare: false,
id: Ident {
sym: Link,
span: Span {
lo: BytePos(
5
),
hi: BytePos(
9
),
ctxt: #0
},
type_ann: None,
optional: false
},
type_params: None,
type_ann: TsTypeLit(
TsTypeLit {
span: Span {
lo: BytePos(
12
),
hi: BytePos(
41
),
ctxt: #0
},
members: [
TsPropertySignature(
TsPropertySignature {
span: Span {
lo: BytePos(
14
),
hi: BytePos(
26
),
ctxt: #0
},
readonly: false,
key: Ident(
Ident {
sym: key,
span: Span {
lo: BytePos(
14
),
hi: BytePos(
17
),
ctxt: #0
},
type_ann: None,
optional: false
}
),
computed: false,
optional: false,
init: None,
params: [],
type_ann: Some(
TsTypeAnn {
span: Span {
lo: BytePos(
17
),
hi: BytePos(
25
),
ctxt: #0
},
type_ann: TsKeywordType(
TsKeywordType {
span: Span {
lo: BytePos(
19
),
hi: BytePos(
25
),
ctxt: #0
},
kind: TsStringKeyword
}
)
}
),
type_params: None
}
),
TsPropertySignature(
TsPropertySignature {
span: Span {
lo: BytePos(
27
),
hi: BytePos(
39
),
ctxt: #0
},
readonly: false,
key: Ident(
Ident {
sym: text,
span: Span {
lo: BytePos(
27
),
hi: BytePos(
31
),
ctxt: #0
},
type_ann: None,
optional: false
}
),
computed: false,
optional: false,
init: None,
params: [],
type_ann: Some(
TsTypeAnn {
span: Span {
lo: BytePos(
31
),
hi: BytePos(
39
),
ctxt: #0
},
type_ann: TsKeywordType(
TsKeywordType {
span: Span {
lo: BytePos(
33
),
hi: BytePos(
39
),
ctxt: #0
},
kind: TsStringKeyword
}
)
}
),
type_params: None
}
)
]
}
)
}
)
)
),
ModuleDecl(
ExportNamed(
NamedExport {
span: Span {
lo: BytePos(
43
),
hi: BytePos(
59
),
ctxt: #0
},
specifiers: [
Named(
NamedExportSpecifier {
span: Span {
lo: BytePos(
52
),
hi: BytePos(
56
),
ctxt: #0
},
orig: Ident {
sym: Link,
span: Span {
lo: BytePos(
52
),
hi: BytePos(
56
),
ctxt: #0
},
type_ann: None,
optional: false
},
exported: None
}
)
],
src: None
}
)
)
],
shebang: None
}

View File

@ -39,6 +39,12 @@ struct PropFolder {
setter_props: FxHashSet<JsWord>,
}
impl Fold<Expr> for PropFolder {
fn fold(&mut self, node: Expr) -> Expr {
node
}
}
impl Fold<Prop> for PropFolder {
fn fold(&mut self, prop: Prop) -> Prop {
match prop {
@ -81,6 +87,11 @@ impl Fold<Prop> for PropFolder {
struct PropNameFolder<'a> {
props: &'a mut FxHashSet<JsWord>,
}
impl<'a> Fold<Expr> for PropNameFolder<'a> {
fn fold(&mut self, node: Expr) -> Expr {
node
}
}
impl<'a> Fold<PropName> for PropNameFolder<'a> {
fn fold(&mut self, name: PropName) -> PropName {

View File

@ -1,5 +1,37 @@
use super::*;
test!(
::swc_ecma_parser::Syntax::default(),
|_| DuplicateKeys,
issue_203,
r#"
const obj = {};
obj.prop = {
alpha: {
charlie: true
},
beta: {
charlie: true,
delta: true
}
};
"#,
r#"
const obj = {};
obj.prop = {
alpha: {
charlie: true
},
beta: {
charlie: true,
delta: true
}
};
"#
);
test!(
ignore,
::swc_ecma_parser::Syntax::default(),

View File

@ -26,17 +26,14 @@ impl Fold<MemberExpr> for MemberExprLit {
fn fold(&mut self, e: MemberExpr) -> MemberExpr {
let mut e = e.fold_children(self);
e.prop = match *e.prop {
Expr::Lit(Lit::Str(Str {
value: sym, span, ..
}))
| Expr::Ident(Ident { sym, span, .. }) => {
if sym.is_reserved_for_es3() || !is_valid_ident(&sym) {
macro_rules! handle {
($sym:expr, $span:expr) => {
if $sym.is_reserved_for_es3() || !is_valid_ident(&$sym) {
return MemberExpr {
computed: true,
prop: box Expr::Lit(Lit::Str(Str {
span,
value: sym,
span: $span,
value: $sym,
has_escape: false,
})),
..e
@ -44,10 +41,21 @@ impl Fold<MemberExpr> for MemberExprLit {
} else {
return MemberExpr {
computed: false,
prop: box Expr::Ident(quote_ident!(span, sym)),
prop: box Expr::Ident(quote_ident!($span, $sym)),
..e
};
}
};
}
e.prop = match *e.prop {
Expr::Lit(Lit::Str(Str { value, span, .. })) => handle!(value, span),
Expr::Ident(i) => {
if e.computed {
box Expr::Ident(i)
} else {
handle!(i.sym, i.span)
}
}
_ => e.prop,
};
@ -74,4 +82,11 @@ obj["const"] = "isKeyword";
obj["var"] = "isKeyword";"#
);
test!(
::swc_ecma_parser::Syntax::default(),
|_| MemberExprLit,
issue_206,
"const number = foo[bar1][baz1]",
"const number = foo[bar1][baz1]"
);
}

View File

@ -77,6 +77,20 @@ impl Fold<VarDeclarator> for Fixer {
}
}
impl Fold<BlockStmtOrExpr> for Fixer {
fn fold(&mut self, body: BlockStmtOrExpr) -> BlockStmtOrExpr {
let body = body.fold_children(self);
match body {
BlockStmtOrExpr::Expr(box expr @ Expr::Object(..)) => {
BlockStmtOrExpr::Expr(box expr.wrap_with_paren())
}
_ => body,
}
}
}
impl Fold<Stmt> for Fixer {
fn fold(&mut self, stmt: Stmt) -> Stmt {
let stmt = match stmt {
@ -189,6 +203,20 @@ macro_rules! array {
array!(ArrayLit);
// array!(ArrayPat);
impl Fold<KeyValueProp> for Fixer {
fn fold(&mut self, prop: KeyValueProp) -> KeyValueProp {
let prop = prop.fold_children(self);
match *prop.value {
Expr::Seq(..) => KeyValueProp {
value: box (*prop.value).wrap_with_paren(),
..prop
},
_ => return prop,
}
}
}
impl Fold<Expr> for Fixer {
fn fold(&mut self, expr: Expr) -> Expr {
fn unwrap_expr(mut e: Expr) -> Expr {
@ -459,4 +487,14 @@ const _ref = {}, { c =( _tmp = {}, d = _extends({}, _tmp), _tmp) } = _ref;"
identical!(issue_192, "a === true && (a = true)");
identical!(issue_199, "(i - 1).toString()");
identical!(
issue_201_01,
"outer = {
inner: (_obj = {}, _defineProperty(_obj, ns.EXPORT1, true), _defineProperty(_obj, ns.EXPORT2, \
true), _obj)
};"
);
identical!(issue_207, "a => ({x: 'xxx', y: {a}});");
}

View File

@ -1,10 +1,10 @@
use super::util::{
define_es_module, define_property, has_use_strict, initialize_to_undefined, local_name_for_src,
make_descriptor, use_strict, Exports, Scope, VarCollector,
make_descriptor, use_strict, Exports, Scope,
};
use crate::{
pass::Pass,
util::{prepend_stmts, DestructuringFinder, ExprFactory, State},
util::{prepend_stmts, var::VarCollector, DestructuringFinder, ExprFactory, State},
};
use ast::*;
use fxhash::FxHashSet;

View File

@ -1,10 +1,10 @@
use super::util::{
define_es_module, define_property, has_use_strict, initialize_to_undefined, make_descriptor,
make_require_call, use_strict, Scope, VarCollector,
make_require_call, use_strict, Scope,
};
use crate::{
pass::Pass,
util::{undefined, DestructuringFinder, ExprFactory, State},
util::{undefined, var::VarCollector, DestructuringFinder, ExprFactory, State},
};
use ast::*;
use fxhash::FxHashSet;

View File

@ -2,11 +2,11 @@ use self::config::BuiltConfig;
pub use self::config::Config;
use super::util::{
define_es_module, define_property, has_use_strict, initialize_to_undefined, local_name_for_src,
make_descriptor, make_require_call, use_strict, Exports, Scope, VarCollector,
make_descriptor, make_require_call, use_strict, Exports, Scope,
};
use crate::{
pass::Pass,
util::{prepend_stmts, DestructuringFinder, ExprFactory, State},
util::{prepend_stmts, var::VarCollector, DestructuringFinder, ExprFactory, State},
};
use ast::*;
use fxhash::FxHashSet;

View File

@ -4,7 +4,7 @@ use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
use inflector::Inflector;
use std::iter;
use swc_atoms::JsWord;
use swc_common::{Mark, Span, SyntaxContext, Visit, VisitWith, DUMMY_SP};
use swc_common::{Mark, Span, SyntaxContext, DUMMY_SP};
#[derive(Clone, Default)]
pub(super) struct Scope {
@ -420,39 +420,6 @@ pub(super) fn make_descriptor(get_expr: Box<Expr>) -> ObjectLit {
}
}
pub(super) struct VarCollector<'a> {
pub to: &'a mut Vec<(JsWord, SyntaxContext)>,
}
impl<'a> Visit<VarDeclarator> for VarCollector<'a> {
fn visit(&mut self, node: &VarDeclarator) {
node.name.visit_with(self);
}
}
impl<'a> Visit<Ident> for VarCollector<'a> {
fn visit(&mut self, i: &Ident) {
self.to.push((i.sym.clone(), i.span.ctxt()))
}
}
macro_rules! var_noop {
($T:path) => {
impl<'a> Visit<$T> for VarCollector<'a> {
fn visit(&mut self, _: &$T) {}
}
};
($T:path, $($rest:tt)*) => {
var_noop!($T);
var_noop!($($rest)*);
};
}
var_noop!(Expr, ArrowExpr, Function, Constructor);
var_noop!(TsType, TsTypeAnn, TsTypeParam);
/// Private `_exports` ident.
pub(super) struct Exports(pub Ident);

View File

@ -1,17 +1,95 @@
use crate::{
pass::Pass,
util::{prepend_stmts, ExprFactory},
util::{prepend_stmts, var::VarCollector, ExprFactory, State},
};
use ast::*;
use swc_common::{util::move_map::MoveMap, Fold, FoldWith, Spanned, DUMMY_SP};
use fxhash::FxHashMap;
use swc_atoms::JsWord;
use swc_common::{
util::move_map::MoveMap, Fold, FoldWith, Spanned, SyntaxContext, VisitWith, DUMMY_SP,
};
/// Strips type annotations out.
pub fn strip() -> impl Pass + Clone + Copy {
Strip
pub fn strip() -> impl Pass + Clone {
Strip::default()
}
#[derive(Clone, Copy)]
struct Strip;
#[derive(Default, Clone)]
struct Strip {
non_top_level: State<bool>,
scope: State<Scope>,
}
impl Strip {
fn handle_decl(&mut self, decl: &Decl) {
// We don't care about stuffs which cannot be exported
if self.non_top_level.value {
return;
}
macro_rules! store {
($sym:expr, $ctxt:expr, $concrete:expr) => {{
let entry = self
.scope
.value
.decls
.entry(($sym.clone(), $ctxt))
.or_default();
if $concrete {
entry.has_concrete = true
} else {
entry.has_type = true;
}
}};
}
match *decl {
Decl::Class(ClassDecl { ref ident, .. }) | Decl::Fn(FnDecl { ref ident, .. }) => {
store!(ident.sym, ident.span.ctxt(), true);
}
Decl::Var(ref var) => {
let mut names = vec![];
var.decls.visit_with(&mut VarCollector { to: &mut names });
for name in names {
store!(name.0, name.1, true);
}
}
Decl::TsEnum(TsEnumDecl { ref id, .. })
| Decl::TsInterface(TsInterfaceDecl { ref id, .. })
| Decl::TsModule(TsModuleDecl {
id: TsModuleName::Ident(ref id),
..
})
| Decl::TsTypeAlias(TsTypeAliasDecl { ref id, .. }) => {
store!(id.sym, id.span.ctxt(), false)
}
Decl::TsModule(TsModuleDecl {
id:
TsModuleName::Str(Str {
ref value, span, ..
}),
..
}) => store!(value, span.ctxt(), false),
}
}
}
#[derive(Default)]
struct Scope {
decls: FxHashMap<(JsWord, SyntaxContext), DeclInfo>,
}
#[derive(Debug, Default)]
struct DeclInfo {
/// interface / type alias
has_type: bool,
/// Var, Fn, Class
has_concrete: bool,
}
impl Fold<Constructor> for Strip {
fn fold(&mut self, c: Constructor) -> Constructor {
@ -65,15 +143,19 @@ impl Fold<Constructor> for Strip {
impl Fold<Vec<ModuleItem>> for Strip {
fn fold(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
let items = items.fold_children(self);
items.move_flat_map(|item| match item {
ModuleItem::Stmt(Stmt::Empty(..))
| ModuleItem::Stmt(Stmt::Decl(Decl::TsEnum(..)))
ModuleItem::Stmt(Stmt::Empty(..)) => None,
ModuleItem::Stmt(Stmt::Decl(Decl::TsEnum(..)))
| ModuleItem::Stmt(Stmt::Decl(Decl::TsInterface(..)))
| ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(..)))
| ModuleItem::Stmt(Stmt::Decl(Decl::TsTypeAlias(..)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(Decl::TsEnum(..)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(Decl::TsInterface(..)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(Decl::TsModule(..)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(Decl::TsTypeAlias(..)))
| ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(
ExportDefaultDecl::TsInterfaceDecl(..),
))
@ -104,12 +186,50 @@ impl Fold<Vec<ModuleItem>> for Strip {
ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => Some(
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export.expr).fold_with(self)),
),
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(mut export)) => {
// if specifier become empty, we remove export statement.
export.specifiers.retain(|s| match *s {
ExportSpecifier::Named(NamedExportSpecifier { ref orig, .. }) => {
if let Some(e) = self
.scope
.value
.decls
.get(&(orig.sym.clone(), orig.span.ctxt()))
{
e.has_concrete
} else {
return true;
}
}
_ => true,
});
if export.specifiers.is_empty() {
return None;
}
Some(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
NamedExport { ..export },
)))
}
_ => Some(item.fold_with(self)),
})
}
}
impl Fold<Decl> for Strip {
fn fold(&mut self, decl: Decl) -> Decl {
self.handle_decl(&decl);
let old = self.non_top_level;
self.non_top_level = true.into();
let decl = decl.fold_children(self);
self.non_top_level = old;
decl
}
}
impl Fold<Stmt> for Strip {
fn fold(&mut self, stmt: Stmt) -> Stmt {
let stmt = stmt.fold_children(self);
@ -215,4 +335,28 @@ mod tests {
to!(export_import, "export import A = B", "export { B as A }");
to!(export_equals, "export = Foo", "export default Foo");
to!(
issue_196_01,
"export type Link = { key: string; text: string };",
""
);
to!(
issue_196_02,
"type Link = { key: string; text: string };
export { Link };",
""
);
to!(
issue_196_03,
"type Link = { key: string; text: string };
const Link = 'Boo';
export { Link };",
"const Link = 'Boo';
export { Link };"
);
// TODO: Test function / variable hoisting
}

View File

@ -25,6 +25,7 @@ pub(crate) mod constructor;
mod factory;
mod state;
mod value;
pub(crate) mod var;
pub(crate) struct ThisVisitor {
found: bool,

View File

@ -0,0 +1,36 @@
use ast::*;
use swc_atoms::JsWord;
use swc_common::{SyntaxContext, Visit, VisitWith};
pub(crate) struct VarCollector<'a> {
pub to: &'a mut Vec<(JsWord, SyntaxContext)>,
}
impl<'a> Visit<VarDeclarator> for VarCollector<'a> {
fn visit(&mut self, node: &VarDeclarator) {
node.name.visit_with(self);
}
}
impl<'a> Visit<Ident> for VarCollector<'a> {
fn visit(&mut self, i: &Ident) {
self.to.push((i.sym.clone(), i.span.ctxt()))
}
}
macro_rules! var_noop {
($T:path) => {
impl<'a> Visit<$T> for VarCollector<'a> {
fn visit(&mut self, _: &$T) {}
}
};
($T:path, $($rest:tt)*) => {
var_noop!($T);
var_noop!($($rest)*);
};
}
var_noop!(Expr, ArrowExpr, Function, Constructor);
var_noop!(TsType, TsTypeAnn, TsTypeParam);