Compiler baseline

- Fix binary
 - Add base for transformers

This will be last large squash.
This commit is contained in:
강동윤 2018-11-03 16:56:43 +09:00 committed by GitHub
parent f9be576e02
commit e12dcf0452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 4421 additions and 138 deletions

View File

@ -66,6 +66,19 @@ fn main() {
"private", "private",
"protected", "protected",
"public", "public",
//
"Object",
"length",
"Infinity",
"undefined",
"NaN",
"RegExp",
// helpers
"apply",
"call",
"concat",
"_extends",
"_toConsumableArray"
], ],
); );
} }

View File

@ -3,9 +3,9 @@
//! ----- //! -----
//! //!
//! This module use [`::rustc_errors`][] internally. //! This module use [`::rustc_errors`][] internally.
//!
pub use self::{diagnostic::*, diagnostic_builder::DiagnosticBuilder, handler::*}; pub use self::{diagnostic::*, diagnostic_builder::DiagnosticBuilder, handler::*};
#[doc(inline)]
pub use rustc_errors::{ pub use rustc_errors::{
ColorConfig, ColorConfig,
Level::{self, *}, Level::{self, *},

View File

@ -1,4 +1,5 @@
use super::*; use super::*;
use crate::{FileLoader, FilePathMapping, SourceMap};
use std::{ use std::{
io, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -34,9 +35,9 @@ function foo() {
#[test] #[test]
fn test() { fn test() {
let cm = CodeMap::with_file_loader(box MyFileLoader, FilePathMapping::empty()); let cm = SourceMap::with_file_loader(box MyFileLoader, FilePathMapping::empty());
let file_map = cm let file_map = cm
.load_file_and_lines("tmp.js".as_ref()) .load_file("tmp.js".into())
.expect("failed to load tmp.js"); .expect("failed to load tmp.js");
println!( println!(
"File (start={},end={})", "File (start={},end={})",
@ -60,7 +61,7 @@ fn test() {
DiagnosticBuilder::new_with_code( DiagnosticBuilder::new_with_code(
&handler, &handler,
Warning, super::Warning,
Some(DiagnosticId::Lint("WITH_STMT".into())), Some(DiagnosticId::Lint("WITH_STMT".into())),
"Lint: With statement", "Lint: With statement",
) )

View File

@ -22,7 +22,9 @@ pub use self::{
pos::*, pos::*,
}; };
pub use syntax::source_map::{FilePathMapping, SourceMap, SpanSnippetError}; pub use syntax::source_map::{
FileLines, FileLoader, FileName, FilePathMapping, SourceMap, SpanSnippetError,
};
mod ast_node; mod ast_node;
pub mod errors; pub mod errors;
mod fold; mod fold;

View File

@ -7,5 +7,6 @@ authors = ["강동윤 <kdy1@outlook.kr>"]
swc_ecma_ast = { path = "./ast" } swc_ecma_ast = { path = "./ast" }
swc_ecma_codegen = { path = "./codegen" } swc_ecma_codegen = { path = "./codegen" }
swc_ecma_parser = { path = "./parser" } swc_ecma_parser = { path = "./parser" }
swc_ecma_transforms = { path = "./transforms" }
[dev-dependencies] [dev-dependencies]

View File

@ -143,12 +143,12 @@ pub struct MemberExpr {
pub prop: Box<Expr>, pub prop: Box<Expr>,
pub computed: bool, pub computed: bool,
} }
#[ast_node] #[ast_node]
pub struct CondExpr { pub struct CondExpr {
#[span(lo)] pub span: Span,
pub test: Box<Expr>, pub test: Box<Expr>,
pub cons: Box<Expr>, pub cons: Box<Expr>,
#[span(hi)]
pub alt: Box<Expr>, pub alt: Box<Expr>,
} }

View File

@ -86,4 +86,34 @@ impl Ident {
pub fn new(sym: JsWord, span: Span) -> Self { pub fn new(sym: JsWord, span: Span) -> Self {
Ident { span, sym } Ident { span, sym }
} }
pub fn is_reserved_only_for_es3(&self) -> bool {
[
"abstract",
"boolean",
"byte",
"char",
"double",
"enum",
"final",
"float",
"goto",
"implements",
"int",
"interface",
"long",
"native",
"package",
"private",
"protected",
"public",
"short",
"static",
"synchronized",
"throws",
"transient",
"volatile",
]
.contains(&&*self.sym)
}
} }

View File

@ -65,6 +65,9 @@ impl<'a> Emitter<'a> {
break; break;
} }
if ext_sp.lo() < BytePos(3) {
return Ok(());
}
// include `//` // include `//`
ext_sp = ext_sp.with_lo(ext_sp.lo() - BytePos(3)); ext_sp = ext_sp.with_lo(ext_sp.lo() - BytePos(3));

View File

@ -1435,7 +1435,11 @@ fn get_text_of_node<T: Spanned>(
node: &T, node: &T,
_include_travia: bool, _include_travia: bool,
) -> Option<String> { ) -> Option<String> {
Some(cm.span_to_snippet(node.span()).unwrap()) let s = cm.span_to_snippet(node.span()).unwrap();
if s == "" {
return None;
}
Some(s)
// let span = node.span(); // let span = node.span();
// let src = match file.src { // let src = match file.src {

View File

@ -4,7 +4,6 @@
#![feature(specialization)] #![feature(specialization)]
#![feature(never_type)] #![feature(never_type)]
// #![feature(nll)] // #![feature(nll)]
#![feature(proc_macro)]
#![feature(try_from)] #![feature(try_from)]
#![feature(try_trait)] #![feature(try_trait)]
#![deny(unreachable_patterns)] #![deny(unreachable_patterns)]

View File

@ -96,7 +96,12 @@ impl<'a, I: Input> Parser<'a, I> {
expect!(':'); expect!(':');
let alt = self.parse_assignment_expr()?; let alt = self.parse_assignment_expr()?;
Ok(box Expr::Cond(CondExpr { test, cons, alt })) Ok(box Expr::Cond(CondExpr {
test,
cons,
alt,
span: span!(start),
}))
} else { } else {
return Ok(test); return Ok(test);
} }

View File

@ -187,3 +187,32 @@ fn new_new_no_paren() {
}) })
); );
} }
#[test]
fn array_lit() {
assert_eq_ignore_span!(
expr("[a,,,,, ...d,, e]"),
box Expr::Array(ArrayLit {
span,
elems: vec![
Some(ExprOrSpread {
spread: None,
expr: box Expr::Ident(Ident::new("a".into(), span))
}),
None,
None,
None,
None,
Some(ExprOrSpread {
spread: Some(span),
expr: box Expr::Ident(Ident::new("d".into(), span))
}),
None,
Some(ExprOrSpread {
spread: None,
expr: box Expr::Ident(Ident::new("e".into(), span))
}),
]
})
);
}

View File

@ -244,7 +244,7 @@ macro_rules! span {
start.0, end.0 start.0, end.0
) )
} }
::swc_common::Span::new(start, end, Default::default()) ::swc_common::Span::new(start, end, ::swc_common::SyntaxContext::empty())
}}; }};
} }

View File

@ -2,5 +2,5 @@ error: Unterminated block comment
--> $DIR/tests/test262-parser/fail/100c329e6dd70e5a.js:1:1 --> $DIR/tests/test262-parser/fail/100c329e6dd70e5a.js:1:1
| |
1 | /* 1 | /*
| ^^^^^ | ^^^

View File

@ -1,6 +1,6 @@
error: Expected Comma error: Unexpected token
--> $DIR/tests/test262-parser/fail/1a5b0dfa9fde985d.js:1:24 --> $DIR/tests/test262-parser/fail/1a5b0dfa9fde985d.js:1:25
| |
1 | function* f() { [yield {a = 0}]; } 1 | function* f() { [yield {a = 0}]; }
| ^ | ^^^^^

View File

@ -1,7 +1,6 @@
error: Unterminated block comment error: Unterminated block comment
--> $DIR/tests/test262-parser/fail/1c6ba8177a9624f0.js:1:1 --> $DIR/tests/test262-parser/fail/1c6ba8177a9624f0.js:1:1
| |
1 | / /* 1 | /*
2 | | | ^^^
| |_^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/3118eaa619345896.js:3:3 --> $DIR/tests/test262-parser/fail/3118eaa619345896.js:2:3
| |
3 | */] 2 | */]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: LineBreak cannot follow 'throw' error: LineBreak cannot follow 'throw'
--> $DIR/tests/test262-parser/fail/379c49fbf3259511.js:2:5 --> $DIR/tests/test262-parser/fail/379c49fbf3259511.js:1:15
| |
2 | */ e 1 | throw /* */ e
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/42cb3f2a38cb2930.js:3:3 --> $DIR/tests/test262-parser/fail/42cb3f2a38cb2930.js:2:3
| |
3 | */] 2 | */]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/62d72a3c3d14d150.js:3:1 --> $DIR/tests/test262-parser/fail/62d72a3c3d14d150.js:2:2
| |
3 | ] 2 | ]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/67419010fc81184a.js:3:3 --> $DIR/tests/test262-parser/fail/67419010fc81184a.js:2:4
| |
3 | */] 2 | */]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/85bb33accf520f1d.js:3:1 --> $DIR/tests/test262-parser/fail/85bb33accf520f1d.js:2:1
| |
3 | ] 2 | ]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/8f7c7b7f4d70f975.js:3:1 --> $DIR/tests/test262-parser/fail/8f7c7b7f4d70f975.js:2:1
| |
3 | ] 2 | ]
| ^ | ^

View File

@ -2,5 +2,5 @@ error: Unterminated block comment
--> $DIR/tests/test262-parser/fail/ac1ee2739ad30d66.js:1:1 --> $DIR/tests/test262-parser/fail/ac1ee2739ad30d66.js:1:1
| |
1 | /* 1 | /*
| ^^^ | ^^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/b7ae8c17f892abf6.js:3:1 --> $DIR/tests/test262-parser/fail/b7ae8c17f892abf6.js:2:2
| |
3 | ] 2 | ]
| ^ | ^

View File

@ -1,8 +1,8 @@
error: Expected ';', '}' or <eof> error: Expected ';', '}' or <eof>
--> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:5 --> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:3
| |
1 | __ 1 | __
| ^ | ^
| |
note: This is the expression part of an expression statement note: This is the expression part of an expression statement
--> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:1 --> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:1

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/c49106fce7ac6b09.js:2:1 --> $DIR/tests/test262-parser/fail/c49106fce7ac6b09.js:1:2
| |
2 | ] 1 | ]
| ^ | ^

View File

@ -1,6 +1,6 @@
error: Unexpected token error: Unexpected token
--> $DIR/tests/test262-parser/fail/c7023e867afea771.js:3:1 --> $DIR/tests/test262-parser/fail/c7023e867afea771.js:2:1
| |
3 | ] 2 | ]
| ^ | ^

View File

@ -2,5 +2,5 @@ error: Unterminated block comment
--> $DIR/tests/test262-parser/fail/d056300d8658429f.js:1:1 --> $DIR/tests/test262-parser/fail/d056300d8658429f.js:1:1
| |
1 | /* 1 | /*
| ^^^^^ | ^^^

View File

@ -1,6 +1,6 @@
error: LineBreak cannot follow 'throw' error: LineBreak cannot follow 'throw'
--> $DIR/tests/test262-parser/fail/f1ee13ed9e476d25.js:2:5 --> $DIR/tests/test262-parser/fail/f1ee13ed9e476d25.js:1:15
| |
2 | */ e 1 | throw /* */ e
| ^ | ^

View File

@ -14,7 +14,7 @@ use std::{
path::Path, path::Path,
}; };
use swc_common::{FileName, Fold, FoldWith, Span}; use swc_common::{FileName, Fold, FoldWith, Span};
use swc_ecma_parser::{ast::*, FileMapInput, PResult, Parser, Session}; use swc_ecma_parser::{ast::*, PResult, Parser, Session, SourceFileInput};
use test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName}; use test::{test_main, Options, ShouldPanic::No, TestDesc, TestDescAndFn, TestFn, TestName};
use testing::NormalizedOutput; use testing::NormalizedOutput;
@ -87,8 +87,12 @@ fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
const IGNORED_ERROR_TESTS: &[&str] = &[ const IGNORED_ERROR_TESTS: &[&str] = &[
// Wrong tests // Wrong tests
"0d5e450f1da8a92a.js", "0d5e450f1da8a92a.js",
"346316bef54d805a.js",
"976b6247ca78ab51.js",
"ae0a7ac275bc9f5c.js",
"748656edbfb2d0bb.js", "748656edbfb2d0bb.js",
"79f882da06f88c9f.js", "79f882da06f88c9f.js",
"d28e80d99f819136.js",
"92b6af54adef3624.js", "92b6af54adef3624.js",
"ef2d369cccc5386c.js", "ef2d369cccc5386c.js",
// Temporarily ignore tests for using octal escape before use strict // Temporarily ignore tests for using octal escape before use strict
@ -261,10 +265,10 @@ fn parse_module<'a>(file_name: &Path, s: &str) -> Result<Module, NormalizedOutpu
fn with_parser<F, Ret>(file_name: &Path, src: &str, f: F) -> Result<Ret, NormalizedOutput> fn with_parser<F, Ret>(file_name: &Path, src: &str, f: F) -> Result<Ret, NormalizedOutput>
where where
F: for<'a> FnOnce(&mut Parser<'a, FileMapInput>) -> PResult<'a, Ret>, F: for<'a> FnOnce(&mut Parser<'a, SourceFileInput>) -> PResult<'a, Ret>,
{ {
let output = ::testing::run_test(|logger, cm, handler| { let output = ::testing::run_test(|logger, cm, handler| {
let fm = cm.new_filemap(FileName::Real(file_name.into()), src.into()); let fm = cm.new_source_file(FileName::Real(file_name.into()), src.into());
let res = f(&mut Parser::new( let res = f(&mut Parser::new(
Session { Session {
@ -291,87 +295,6 @@ where
} }
} }
fn normalize<T>(t: T) -> T
where
Normalizer: Fold<T>,
{
let mut n = Normalizer;
n.fold(t)
}
struct Normalizer;
impl Fold<Span> for Normalizer {
fn fold(&mut self, _: Span) -> Span {
Span::default()
}
}
impl Fold<Str> for Normalizer {
fn fold(&mut self, s: Str) -> Str {
Str {
span: Default::default(),
has_escape: false,
..s
}
}
}
impl Fold<Expr> for Normalizer {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Paren(ParenExpr { expr, .. }) => *expr,
Expr::New(NewExpr {
callee,
args: None,
span,
}) => Expr::New(NewExpr {
span,
callee,
args: Some(vec![]),
}),
// Flatten comma expressions.
Expr::Seq(SeqExpr { mut exprs, span }) => {
let need_work = exprs.iter().any(|n| match **n {
Expr::Seq(..) => true,
_ => false,
});
if need_work {
exprs = exprs.into_iter().fold(vec![], |mut v, e| {
match *e {
Expr::Seq(SeqExpr { exprs, .. }) => v.extend(exprs),
_ => v.push(e),
}
v
});
}
Expr::Seq(SeqExpr { exprs, span })
}
_ => e,
}
}
}
impl Fold<PropName> for Normalizer {
fn fold(&mut self, n: PropName) -> PropName {
let n = n.fold_children(self);
match n {
PropName::Ident(Ident { sym, .. }) => PropName::Str(Str {
span: Default::default(),
value: sym,
has_escape: false,
}),
PropName::Num(num) => PropName::Str(Str {
span: Default::default(),
value: num.to_string().into(),
has_escape: false,
}),
_ => n,
}
}
}
#[test] #[test]
fn identity() { fn identity() {
let args: Vec<_> = env::args().collect(); let args: Vec<_> = env::args().collect();

View File

@ -1,2 +1,4 @@
pub extern crate swc_ecma_ast as ast; pub extern crate swc_ecma_ast as ast;
pub extern crate swc_ecma_codegen as codegen;
pub extern crate swc_ecma_parser as parser; pub extern crate swc_ecma_parser as parser;
pub extern crate swc_ecma_transforms as transforms;

View File

@ -0,0 +1,17 @@
[package]
name = "swc_ecma_transforms"
version = "0.1.0"
authors = ["강동윤 <kdy1@outlook.kr>"]
[dependencies]
swc_atoms = { path = "../../atoms" }
swc_common = { path = "../../common" }
swc_ecma_ast = { path = "../ast" }
[dev-dependencies]
testing = { path = "../../testing" }
swc_ecma_codegen = { path = "../codegen" }
swc_ecma_parser = { path = "../parser" }
pretty_assertions = "0.4"
slog = "2"
sourcemap = "2.2"

View File

@ -0,0 +1,57 @@
# Babel packages to port
- [ ] babel-helper-bindify-decorators
- [ ] babel-helper-builder-binary-assignment-operator-visitor
- [ ] babel-helper-call-delegate
- [ ] babel-helper-define-map
- [ ] babel-helper-explode-assignable-expression
- [ ] babel-helper-explode-class
- [ ] babel-helper-fixtures
- [ ] babel-helper-function-name
- [ ] babel-helper-get-function-arity
- [ ] babel-helper-hoist-variables
- [ ] babel-helper-module-imports
- [ ] babel-helper-module-transforms
- [ ] babel-helper-optimise-call-expression
- [ ] babel-helper-plugin-test-runner
- [ ] babel-helper-regex
- [ ] babel-helper-remap-async-to-generator
- [ ] babel-helper-replace-supers
- [ ] babel-helper-simple-access
- [ ] babel-helper-wrap-function
- [ ] babel-helpers
- [ ] babel-plugin-check-constants
- [ ] babel-plugin-external-helpers
- [ ] babel-plugin-transform-arrow-functions
- [ ] babel-plugin-transform-async-to-generator
- [ ] babel-plugin-transform-block-scoped-functions
- [ ] babel-plugin-transform-block-scoping
- [ ] babel-plugin-transform-classes
- [ ] babel-plugin-transform-computed-properties
- [ ] babel-plugin-transform-destructuring
- [ ] babel-plugin-transform-dotall-regex
- [ ] babel-plugin-transform-duplicate-keys
- [ ] babel-plugin-transform-for-of
- [ ] babel-plugin-transform-function-name
- [ ] babel-plugin-transform-instanceof
- [ ] babel-plugin-transform-jscript
- [ ] babel-plugin-transform-member-expression-literals
- [ ] babel-plugin-transform-modules-amd
- [ ] babel-plugin-transform-modules-commonjs
- [ ] babel-plugin-transform-modules-systemjs
- [ ] babel-plugin-transform-modules-umd
- [ ] babel-plugin-transform-new-target
- [ ] babel-plugin-transform-object-assign
- [ ] babel-plugin-transform-object-set-prototype-of-to-assign
- [ ] babel-plugin-transform-object-super
- [ ] babel-plugin-transform-parameters
- [ ] babel-plugin-transform-property-literals
- [ ] babel-plugin-transform-property-mutators
- [ ] babel-plugin-transform-proto-to-assign
- [ ] babel-plugin-transform-regenerator
- [ ] babel-plugin-transform-reserved-words
- [ ] babel-plugin-transform-strict-mode
- [ ] babel-plugin-transform-template-literals
- [ ] babel-plugin-transform-typeof-symbol
- [ ] babel-plugin-transform-unicode-regex
- [ ] babel-template

View File

@ -0,0 +1,70 @@
use swc_common::{FoldWith, Folder};
use swc_ecma_ast::*;
///Compile ES2015 arrow functions to ES5
///
///# Example
///
///## In
/// ```js
/// var a = () => {};
/// var a = (b) => b;
///
/// const double = [1,2,3].map((num) => num * 2);
/// console.log(double); // [2,4,6]
///
/// var bob = {
/// _name: "Bob",
/// _friends: ["Sally", "Tom"],
/// printFriends() {
/// this._friends.forEach(f =>
/// console.log(this._name + " knows " + f));
/// }
/// };
/// console.log(bob.printFriends());
/// ```
///
///## Out
///```js
/// var a = function () {};
/// var a = function (b) {
/// return b;
/// };
///
/// const double = [1, 2, 3].map(function (num) {
/// return num * 2;
/// });
/// console.log(double); // [2,4,6]
///
/// var bob = {
/// _name: "Bob",
/// _friends: ["Sally", "Tom"],
/// printFriends() {
/// var _this = this;
///
/// this._friends.forEach(function (f) {
/// return console.log(_this._name + " knows " + f);
/// });
/// }
/// };
/// console.log(bob.printFriends());
/// ```
#[derive(Debug, Clone, Copy)]
pub struct Arrow;
impl Folder<Expr> for Arrow {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Arrow(ArrowExpr { .. }) => unimplemented!("Arrow function"),
_ => e,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
}

View File

@ -0,0 +1,30 @@
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
/// babel: `transform-member-expression-literals`
///
/// # Input
/// ```js
/// obj["foo"] = "isValid";
///
/// obj.const = "isKeyword";
/// obj["var"] = "isKeyword";
/// ```
///
/// # Output
/// ```js
/// obj.foo = "isValid";
///
/// obj["const"] = "isKeyword";
/// obj["var"] = "isKeyword";
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct MemberExprLit;
impl Fold<MemberExpr> for MemberExprLit {
fn fold(&mut self, e: MemberExpr) -> MemberExpr {
let e = e.fold_children(self);
e
}
}

View File

@ -0,0 +1,7 @@
pub use self::{
member_expr_lits::MemberExprLit, prop_lits::PropertyLiteral, reserved_word::ReservedWord,
};
mod member_expr_lits;
mod prop_lits;
mod reserved_word;

View File

@ -0,0 +1,82 @@
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
/// babel: `transform-property-literals`
///
/// # Input
/// ```js
/// var foo = {
/// // changed
/// "bar": function () {},
/// "1": function () {},
///
/// // not changed
/// "default": 1,
/// [a]: 2,
/// foo: 1
/// };
/// ```
///
/// # Output
/// ```js
/// var foo = {
/// bar: function () {},
/// 1: function () {},
///
/// "default": 1,
/// [a]: 2,
/// foo: 1
/// };
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct PropertyLiteral;
impl Fold<PropName> for PropertyLiteral {
fn fold(&mut self, n: PropName) -> PropName {
let n = n.fold_children(self);
match n {
PropName::Ident(ident) => {
if ident.is_reserved_only_for_es3() {
return PropName::Str(Str {
value: ident.sym,
span: ident.span,
has_escape: false,
});
} else {
PropName::Ident(ident)
}
}
_ => n,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
test!(
PropertyLiteral,
babel_basic,
r#"var foo = {
// changed
"bar": function () {},
"1": function () {},
// not changed
"default": 1,
[a]: 2,
foo: 1
};"#,
r#"var foo = {
bar: function () {},
1: function () {},
"default": 1,
[a]: 2,
foo: 1
};"#
);
}

View File

@ -0,0 +1,30 @@
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
/// babel: `@babel/plugin-transform-reserved-words`
///
/// Some words were reserved in ES3 as potential future keywords but were not
/// reserved in ES5 and later. This plugin, to be used when targeting ES3
/// environments, renames variables from that set of words.
///
/// # Input
/// ```js
/// var abstract = 1;
/// var x = abstract + 1;
/// ```
///
/// # Output
/// ```js
/// var _abstract = 1;
/// var x = _abstract + 1;
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct ReservedWord;
impl Fold<Expr> for ReservedWord {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
e
}
}

View File

@ -0,0 +1,109 @@
use swc_common::{FoldWith, Folder};
use swc_ecma_ast::*;
#[derive(Debug, Clone, Copy)]
pub struct Exponentation;
impl Folder<Expr> for Exponentation {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Bin(BinExpr {
left,
op: op!("**"),
right,
span,
}) => Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(box Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(
box Ident {
span,
sym: "Math".into(),
}
.into(),
),
prop: box Ident {
span,
sym: "pow".into(),
}
.into(),
computed: false,
})),
args: vec![
ExprOrSpread {
expr: left,
spread: None,
},
ExprOrSpread {
expr: right,
spread: None,
},
],
}),
_ => e,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
test!(Exponentation, babel_binary, "2 ** 2", "Math.pow(2, 2)");
test_exec!(
Exponentation,
babel_comprehensive,
r#"assert.equal(8, 2 ** 3);
assert.equal(24, 3 * 2 ** 3);
var x = 2;
assert.equal(8, 2 ** ++x);
assert.equal(1, 2 ** -1 * 2);
var calls = 0;
var q = {q: 3};
var o = {
get p() {
calls++;
return q;
}
};
o.p.q **= 2;
assert.equal(1, calls);
assert.equal(9, o.p.q);
assert.equal(512, 2 ** (3 ** 2));
assert.equal(512, 2 ** 3 ** 2);"#
);
test_exec!(
Exponentation,
babel_memoize_object,
r#"var counters = 0;
Object.defineProperty(global, "reader", {
get: function () {
counters += 1;
return { x: 2 };
},
configurable: true
});
reader.x **= 2;
assert.ok(counters === 1);"#
);
// test!(
// Exponentation,
// babel_4403,
// "var a, b;
// a[`${b++}`] **= 1;",
// "var _ref;
// var a, b;
// _ref = `${b++}`, a[_ref] = Math.pow(a[_ref], 1);"
// );
}

View File

@ -0,0 +1,16 @@
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug, Default)]
pub struct Helpers {
/// `_extends({}, b)`
pub(crate) extends: AtomicBool,
pub(crate) to_consumable_array: AtomicBool,
}
impl Helpers {
pub fn merge(mut self, other: &Self) -> Self {
*self.extends.get_mut() |= other.extends.load(Ordering::SeqCst);
self
}
}

View File

@ -0,0 +1,13 @@
pub use self::{
arrow::Arrow, es3::*, exponentation::Exponentation, shorthand::Shorthand,
spread::SpreadElement, sticky_regex::StickyRegex, template_literal::TemplateLiteral,
};
mod arrow;
mod es3;
mod exponentation;
mod helpers;
mod shorthand;
mod spread;
mod sticky_regex;
mod template_literal;

View File

@ -0,0 +1,125 @@
use swc_common::{FoldWith, Folder};
use swc_ecma_ast::*;
///Compile ES2015 shorthand properties to ES5
///
///# Example
///
///## In
///
/// ```js
/// var o = { a, b, c };
/// ```
///
///## Out
///
/// ```js
/// var o = { a: a, b: b, c: c };
/// ```
///
///## In
///
/// ```js
/// var cat = {
/// getName() {
/// return name;
/// }
/// };
/// ```
///
///## Out
///```js
/// var cat = {
/// getName: function () {
/// return name;
/// }
/// };
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct Shorthand;
impl Folder<Prop> for Shorthand {
fn fold(&mut self, prop: Prop) -> Prop {
let prop = prop.fold_children(self);
match prop {
Prop::Shorthand(i) => Prop::KeyValue(KeyValueProp {
key: PropName::Ident(i.clone()),
value: box i.into(),
}),
Prop::Method(MethodProp { key, function }) => Prop::KeyValue(KeyValueProp {
key,
value: box Expr::Fn(FnExpr {
ident: None,
function,
}),
}),
_ => prop,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
test!(
Shorthand,
babel_method_plain,
"var obj = {
method() {
return 5 + 5;
}
};",
"var obj = {
method: function () {
return 5 + 5;
}
};"
);
test!(
Shorthand,
babel_comments,
"var A = 'a';
var o = {
A // comment
};",
"var A = 'a';
var o = {
A: A // comment
};"
);
test!(
Shorthand,
babel_mixed,
"var coords = { x, y, foo: 'bar' };",
"var coords = {
x: x,
y: y,
foo: 'bar'
};"
);
test!(
Shorthand,
babel_multiple,
"var coords = { x, y };",
"var coords = {
x: x,
y: y
};"
);
test!(
Shorthand,
babel_single,
"var coords = { x };",
"var coords = {
x: x
};"
);
}

View File

@ -0,0 +1,202 @@
use super::helpers::Helpers;
use std::{
mem,
sync::{atomic::Ordering, Arc},
};
use swc_common::{Fold, FoldWith, Span};
use swc_ecma_ast::*;
use util::ExprFactory;
/// es2015 - `SpreadElement`
#[derive(Debug, Clone, Default)]
pub struct SpreadElement {
helpers: Arc<Helpers>,
}
impl Fold<Expr> for SpreadElement {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Array(ArrayLit { .. }) => unimplemented!(),
Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
span,
}) => {
let has_spread = args
.iter()
.any(|ExprOrSpread { spread, .. }| spread.is_some());
if !has_spread {
return Expr::Call(CallExpr {
callee: ExprOrSuper::Expr(callee),
args,
span,
});
}
let args_array = concat_args(&self.helpers, span, args);
//
// f.apply(undefined, args)
//
callee.apply(span, expr!(span, undefined), vec![args_array.as_arg()])
}
Expr::New(NewExpr {
callee,
args: Some(args),
span,
}) => {
let has_spread = args
.iter()
.any(|ExprOrSpread { spread, .. }| spread.is_some());
if !has_spread {
return Expr::New(NewExpr {
callee,
args: Some(args),
span,
});
}
let args = concat_args(
&self.helpers,
span,
vec![expr!(span, null).as_arg()]
.into_iter()
.chain(args)
.collect(),
);
//
// f.apply(undefined, args)
//
Expr::New(NewExpr {
span,
callee: box member_expr!(span, Function.prototype.bind)
.apply(span, callee, vec![args.as_arg()])
.wrap_with_paren(),
args: Some(vec![]),
})
}
_ => e,
}
}
}
fn concat_args(helpers: &Helpers, span: Span, args: Vec<ExprOrSpread>) -> Expr {
//
// []
//
let mut first_arr = None;
let mut tmp_arr = vec![];
let mut buf = vec![];
macro_rules! make_arr {
() => {
let elems = mem::replace(&mut tmp_arr, vec![]);
match first_arr {
Some(_) => {
if !elems.is_empty() {
buf.push(Expr::Array(ArrayLit { span, elems }).as_arg());
}
}
None => {
first_arr = Some(Expr::Array(ArrayLit { span, elems }));
}
}
};
}
for arg in args {
let ExprOrSpread { expr, spread } = arg;
match spread {
// ...b -> toConsumableArray(b)
Some(span) => {
//
make_arr!();
helpers.to_consumable_array.store(true, Ordering::SeqCst);
buf.push(
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(
box Ident::new(js_word!("_toConsumableArray"), span).into(),
),
args: vec![ExprOrSpread { expr, spread: None }],
})
.as_arg(),
);
}
None => tmp_arr.push(Some(ExprOrSpread { expr, spread: None })),
}
}
make_arr!();
Expr::Call(CallExpr {
// TODO
span,
callee: ExprOrSuper::Expr(box Expr::Member(MemberExpr {
// TODO: Mark
span,
prop: box Expr::Ident(Ident::new(js_word!("concat"), span)),
obj: ExprOrSuper::Expr(box first_arr.take().unwrap_or_else(|| {
// No arg
// assert!(args.is_empty());
Expr::Array(ArrayLit {
span,
elems: vec![],
})
})),
computed: false,
})),
args: buf,
})
}
#[cfg(test)]
mod tests {
use super::*;
test!(
SpreadElement::default(),
call,
"ca(a, b, c, ...d, e)",
"ca.apply(undefined, [a, b, c].concat(_toConsumableArray(d), [e]));"
);
test!(
SpreadElement::default(),
call_multi_spread,
"ca(a, b, ...d, e, f, ...h)",
"ca.apply(undefined, [a, b].concat(_toConsumableArray(d), [e, f], _toConsumableArray(h)));"
);
test!(
SpreadElement::default(),
call_noop,
"ca(a, b, c, d, e)",
"ca(a, b, c, d, e);"
);
test!(
SpreadElement::default(),
new,
"new C(a, b, c, ...d, e)",
"new (Function.prototype.bind.apply(C, [null, a, b, c].concat(_toConsumableArray(d), \
[e])))();"
);
test!(
SpreadElement::default(),
new_noop,
"new C(a, b, c, c, d, e)",
"new C(a, b, c, c, d, e);"
);
}

View File

@ -0,0 +1,80 @@
use swc_common::{FoldWith, Folder, DUMMY_SP};
use swc_ecma_ast::*;
/// Compile ES2015 sticky regex to an ES5 RegExp constructor
///
///# Example
///## In
///
/// ```js
/// /o+/y;
/// ```
///
///## Out
///
/// ```js
/// new RegExp("o+", "y")
/// ```
#[derive(Debug, Clone, Copy)]
pub struct StickyRegex;
impl Folder<Expr> for StickyRegex {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Lit(Lit::Regex(Regex { exp, flags, span })) => {
if flags
.as_ref()
.map(|s| s.value.contains("y"))
.unwrap_or(false)
{
let str_lit = |s: Str| box Expr::Lit(Lit::Str(s));
return Expr::New(NewExpr {
callee: box Ident {
span: DUMMY_SP,
sym: js_word!("RegExp"),
}
.into(),
args: Some(
vec![ExprOrSpread {
expr: str_lit(exp),
spread: None,
}]
.into_iter()
.chain(flags.map(|flags| ExprOrSpread {
expr: str_lit(flags),
spread: None,
}))
.collect(),
),
span,
});
} else {
return Expr::Lit(Lit::Regex(Regex { exp, flags, span }));
}
}
_ => e,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
test!(
StickyRegex,
babel_basic,
"var re = /o+/y;",
"var re = new RegExp('o+', 'y');"
);
test!(
StickyRegex,
babel_ignore_non_sticky,
"var re = /o+/;",
"var re = /o+/;"
);
}

View File

@ -0,0 +1,19 @@
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
#[derive(Debug, Clone, Copy, Default)]
pub struct TemplateLiteral;
impl Fold<Expr> for TemplateLiteral {
fn fold(&mut self, e: Expr) -> Expr {
let e = e.fold_children(self);
match e {
Expr::Tpl(TplLit { .. }) => {
// TODO
unimplemented!()
}
_ => e,
}
}
}

View File

@ -0,0 +1,32 @@
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(try_trait)]
#![feature(specialization)]
#![feature(trace_macros)]
#[cfg(test)]
#[macro_use]
extern crate slog;
#[macro_use]
pub extern crate swc_atoms;
pub extern crate swc_common;
#[macro_use]
pub extern crate swc_ecma_ast;
#[cfg(test)]
extern crate sourcemap;
#[cfg(test)]
pub extern crate swc_ecma_codegen;
#[cfg(test)]
pub extern crate swc_ecma_parser;
#[cfg(test)]
#[macro_use]
extern crate testing;
#[macro_use]
mod macros;
#[macro_use]
mod quote;
pub mod compat;
pub mod scope;
pub mod simplify;
pub mod util;

View File

@ -0,0 +1,131 @@
#![cfg(test)]
use sourcemap::SourceMapBuilder;
use std::{
io::{self, Write},
rc::Rc,
sync::{Arc, RwLock},
};
use swc_common::{BytePos, FileName, Fold};
use swc_ecma_ast::*;
use swc_ecma_codegen::Emitter;
use swc_ecma_parser::{Parser, Session, SourceFileInput};
pub fn fold<F>(module: ::swc_ecma_ast::Module, f: &mut F) -> ::swc_ecma_ast::Module
where
F: ::swc_common::Folder<::swc_ecma_ast::Module>,
{
let module = f.fold(module);
// normalize (remove span)
::testing::drop_span(module)
}
struct MyHandlers;
impl swc_ecma_codegen::Handlers for MyHandlers {}
pub(crate) struct Noop;
pub fn apply_transform<T: Fold<Module>>(
mut tr: T,
name: &'static str,
src: &'static str,
) -> String {
let out = ::testing::run_test(|logger, cm, handler| {
let fm = cm.new_source_file(FileName::Real(name.into()), src.into());
let module = {
let handler = ::swc_common::errors::Handler::with_tty_emitter(
::swc_common::errors::ColorConfig::Auto,
true,
false,
Some(cm.clone()),
);
let logger = ::testing::logger().new(o!("src" => src));
let sess = Session {
handler: &handler,
logger: &logger,
cfg: Default::default(),
};
let module = {
let mut p = Parser::new(sess, SourceFileInput::from(&*fm));
p.parse_module().unwrap_or_else(|err| {
err.emit();
panic!("failed to parse")
})
};
println!("parsed {} as a module", src);
module
};
let module = fold(module, &mut tr);
let handlers = box MyHandlers;
let mut wr = Buf(Arc::new(RwLock::new(vec![])));
{
let mut emitter = Emitter {
cfg: swc_ecma_codegen::config::Config::default(),
cm,
file: fm.clone(),
enable_comments: true,
srcmap: SourceMapBuilder::new(Some(&src)),
wr: box swc_ecma_codegen::text_writer::WriterWrapper::new("\n", &mut wr),
handlers,
pos_of_leading_comments: Default::default(),
};
emitter.emit_module(&module).unwrap();
}
let r = wr.0.read().unwrap();
let s = String::from_utf8_lossy(&*r);
s.to_string()
});
out.result
}
/// Test transformation.
#[cfg(test)]
macro_rules! test {
($tr:expr, $test_name:ident, $input:expr, $expected:expr) => {
#[test]
fn $test_name() {
let actual = $crate::macros::apply_transform($tr, stringify!($test_name), $input);
let expected = $crate::macros::apply_transform(
$crate::macros::Noop,
stringify!($test_name),
$expected,
);
assert_eq_ignore_span!(actual, expected);
}
};
}
/// Test transformation.
#[cfg(test)]
macro_rules! test_exec {
($tr:expr, $test_name:ident, $input:expr) => {
#[test]
#[ignore]
fn $test_name() {
let _transformed = $crate::macros::apply_transform($tr, stringify!($test_name), $input);
}
};
}
#[derive(Debug, Clone)]
struct Buf(Arc<RwLock<Vec<u8>>>);
impl Write for Buf {
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
self.0.write().unwrap().write(data)
}
fn flush(&mut self) -> io::Result<()> {
self.0.write().unwrap().flush()
}
}

View File

@ -0,0 +1,79 @@
#[macro_export]
macro_rules! quote_ident {
($span:expr, $s:expr) => {{
::swc_ecma_ast::Ident::new($s.into(), $span)
}};
}
#[macro_export]
macro_rules! expr {
($span:expr, null) => {{
use swc_ecma_ast::*;
Expr::Lit(Lit::Null(Null { span: $span }))
}};
($span:expr, undefined) => {{
box Expr::Ident(Ident::new(js_word!("undefined"), $span))
}};
}
/// Returns Box<Expr>
macro_rules! member_expr {
($span:expr, $first:ident) => {{
use swc_ecma_ast::*;
box Expr::Ident(Ident::new(stringify!($first).into(), $span))
}};
($span:expr, $first:ident . $($rest:tt)+) => {{
let obj = member_expr!($span, $first);
member_expr!(@EXT, $span, obj, $($rest)* )
}};
(@EXT, $span:expr, $obj:expr, $first:ident . $($rest:tt)* ) => {{
let prop = member_expr!($span, $first);
member_expr!(@EXT, $span, box Expr::Member(MemberExpr{
span: $span,
obj: ExprOrSuper::Expr($obj),
computed: false,
prop,
}), $($rest)*)
}};
(@EXT, $span:expr, $obj:expr, $first:ident) => {{
use swc_ecma_ast::*;
let prop = member_expr!($span, $first);
box Expr::Member(MemberExpr{
span: $span,
obj: ExprOrSuper::Expr($obj),
computed: false,
prop,
})
}};
}
#[cfg(test)]
mod tests {
use super::*;
use swc_common::DUMMY_SP as span;
use swc_ecma_ast::*;
#[test]
fn quote_member_expr() {
assert_eq_ignore_span!(
member_expr!(span, Function.prototype.bind),
box Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(box Expr::Member(MemberExpr {
span,
obj: ExprOrSuper::Expr(member_expr!(span, Function)),
computed: false,
prop: member_expr!(span, prototype),
})),
computed: false,
prop: member_expr!(span, bind),
})
);
}
}

View File

@ -0,0 +1,50 @@
use swc_common::{FoldWith, Folder};
use swc_ecma_ast::*;
pub trait FoldScope<T> {
/// `scope`: Scope which contains `node`.
fn fold_scope(&mut self, scope: &mut Scope, node: T) -> T;
}
#[derive(Debug)]
pub struct ScopeFolder<'a, F> {
pub folder: F,
pub cur_scope: &'a mut Scope<'a>,
}
impl<'a, F, T> Folder<T> for ScopeFolder<'a, F>
where
F: FoldScope<T>,
T: FoldWith<F> + FoldWith<ScopeFolder<'a, F>>,
{
default fn fold(&mut self, node: T) -> T {
self.folder.fold_scope(self.cur_scope, node)
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Scope<'a> {
pub parent: Option<&'a Scope<'a>>,
pub kind: ScopeKind,
pub children: Vec<Scope<'a>>,
pub bindings: Vec<&'a str>,
pub refs: Vec<&'a str>,
}
impl<'a> Scope<'a> {
pub fn from_fn(parent: Option<&'a Scope<'a>>, f: &'a Function) -> Self {
Scope {
parent,
kind: ScopeKind::Fn,
children: vec![],
bindings: vec![],
refs: vec![],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeKind {
Fn,
Block,
}

View File

@ -0,0 +1,710 @@
use super::Simplify;
use std::iter;
use swc_common::{FoldWith, Folder, Span, Spanned, DUMMY_SP};
use swc_ecma_ast::{Ident, Lit, *};
use util::*;
impl Folder<Expr> for Simplify {
fn fold(&mut self, expr: Expr) -> Expr {
// fold children nodes.
let expr = expr.fold_children(self);
match expr {
// Do nothing.
Expr::Lit(_) | Expr::This(..) => expr,
// Remove parenthesis. This may break ast, but it will be fixed up later.
Expr::Paren(ParenExpr { expr, .. }) => *expr,
Expr::Unary(expr) => fold_unary(expr),
Expr::Bin(expr) => fold_bin(expr),
Expr::Member(e) => fold_member_expr(e),
Expr::Cond(CondExpr {
span,
test,
cons,
alt,
}) => match test.as_bool() {
(p, Known(val)) => {
let expr_value = if val { cons } else { alt };
if p.is_pure() {
*expr_value
} else {
Expr::Seq(SeqExpr {
span,
exprs: vec![test, expr_value],
})
}
}
_ => Expr::Cond(CondExpr {
span,
test,
cons,
alt,
}),
},
// Simplify sequence expression.
Expr::Seq(SeqExpr { span, exprs }) => {
if exprs.len() == 1 {
//TODO: Respan
*exprs.into_iter().next().unwrap()
} else {
assert!(!exprs.is_empty(), "sequence expression should not be empty");
//TODO: remove unused
return Expr::Seq(SeqExpr { span, exprs });
}
}
// be conservative.
_ => expr,
}
}
}
fn fold_member_expr(e: MemberExpr) -> Expr {
#[derive(Copy, Clone, PartialEq, Eq)]
enum KnownOp {
/// Length
Len,
Index(u32),
}
let op = match *e.prop {
Expr::Ident(Ident {
sym: js_word!("length"),
..
}) => KnownOp::Len,
// Lit(Lit::Num(Number(f)))=>{
// if f==0{
// }else{
// }
// // TODO: Report error
// KnownOp::Index(f)},
_ => return Expr::Member(e),
};
let o = match e.obj {
ExprOrSuper::Super(_) => return Expr::Member(e),
ExprOrSuper::Expr(box o) => o,
};
match o {
Expr::Lit(Lit::Str(Str {
ref value, span, ..
}))
if op == KnownOp::Len =>
{
Expr::Lit(Lit::Num(Number {
value: value.len() as _,
span,
}))
}
Expr::Array(ArrayLit { ref elems, span })
if op == KnownOp::Len && !o.may_have_side_effects() =>
{
Expr::Lit(Lit::Num(Number {
value: elems.len() as _,
span,
}))
}
_ => {
return Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box o),
..e
})
}
}
}
fn fold_bin(
BinExpr {
left,
op,
right,
span,
}: BinExpr,
) -> Expr {
macro_rules! try_replace {
($v:expr) => {{
match $v {
Known(v) => {
// TODO: Optimize
return make_bool_expr(span, v, { iter::once(left).chain(iter::once(right)) });
}
_ => (left, right),
}
}};
(number, $v:expr) => {{
match $v {
Known(v) => {
return preserve_effects(
span,
Expr::Lit(Lit::Num(Number {
value: v,
span: DUMMY_SP,
})),
{ iter::once(left).chain(iter::once(right)) },
);
}
_ => (left, right),
}
}};
}
let (left, right) = match op {
op!(bin, "+") => {
// It's string concatenation if either left or right is string.
let mut bin = Expr::Bin(BinExpr {
span,
left,
op: op!(bin, "+"),
right,
});
match bin.get_type() {
Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => {
bin = match bin {
Expr::Bin(BinExpr {
left,
op: _,
right,
span,
}) => match perform_arithmetic_op(op!(bin, "+"), &left, &right) {
Known(v) => {
return preserve_effects(
span,
Expr::Lit(Lit::Num(Number { value: v, span })),
{ iter::once(left).chain(iter::once(right)) },
);
}
_ => Expr::Bin(BinExpr {
span,
left,
op: op!(bin, "+"),
right,
}),
},
_ => unreachable!(),
};
}
Known(StringType) => {}
_ => {}
}
//TODO: try string concat
return bin;
}
op!("&&") | op!("||") => match left.as_bool() {
(_, Known(val)) => {
let node = if op == op!("&&") {
if val {
// 1 && $right
right
} else {
// 0 && $right
return *left;
}
} else {
if val {
// 1 || $right
return *left;
} else {
// 0 || $right
right
}
};
return if !left.may_have_side_effects() {
*node
} else {
Expr::Seq(SeqExpr {
span,
exprs: vec![left, node],
})
};
}
_ => (left, right),
},
op!("instanceof") => {
fn is_non_obj(e: &Expr) -> bool {
match *e {
// Non-object types are never instances.
Expr::Lit(Lit::Str { .. })
| Expr::Lit(Lit::Num(..))
| Expr::Lit(Lit::Null(..))
| Expr::Lit(Lit::Bool(..))
| Expr::Ident(Ident {
sym: js_word!("undefined"),
..
})
| Expr::Ident(Ident {
sym: js_word!("Infinity"),
..
})
| Expr::Ident(Ident {
sym: js_word!("NaN"),
..
}) => true,
Expr::Unary(UnaryExpr {
op: op!("!"),
ref arg,
..
})
| Expr::Unary(UnaryExpr {
op: op!(unary, "-"),
ref arg,
..
})
| Expr::Unary(UnaryExpr {
op: op!("void"),
ref arg,
..
}) => is_non_obj(&arg),
_ => false,
}
}
// Non-object types are never instances.
if is_non_obj(&left) {
return make_bool_expr(span, false, iter::once(right));
}
if right.is_ident_ref_to(js_word!("Object")) {
return make_bool_expr(span, true, iter::once(left));
}
(left, right)
}
// Arithmetic operations
op!(bin, "-") | op!("/") | op!("%") => {
try_replace!(number, perform_arithmetic_op(op, &left, &right))
}
// These needs one more check.
//
// (a * 1) * 2 --> a * (1 * 2) --> a * 2
op!("*") | op!("&") | op!("|") | op!("^") => {
let (left, right) = try_replace!(number, perform_arithmetic_op(op, &left, &right));
// TODO: Try left.rhs * right
(left, right)
}
// Comparisons
op!("<") => try_replace!(perform_abstract_rel_cmp(span, &left, &right, false)),
op!(">") => try_replace!(perform_abstract_rel_cmp(span, &right, &left, false)),
op!("<=") => try_replace!(!perform_abstract_rel_cmp(span, &right, &left, true)),
op!(">=") => try_replace!(!perform_abstract_rel_cmp(span, &left, &right, true)),
op!("==") => try_replace!(perform_abstract_eq_cmp(span, &left, &right)),
op!("!=") => try_replace!(!perform_abstract_eq_cmp(span, &left, &right)),
op!("===") => try_replace!(perform_strict_eq_cmp(span, &left, &right)),
op!("!==") => try_replace!(!perform_strict_eq_cmp(span, &left, &right)),
_ => (left, right),
};
Expr::Bin(BinExpr {
left,
op,
right,
span,
})
}
fn fold_unary(UnaryExpr { span, op, arg }: UnaryExpr) -> Expr {
let may_have_side_effects = arg.may_have_side_effects();
match op {
op!("typeof") if !may_have_side_effects => {
let val = match *arg {
Expr::Fn(..) => "function",
Expr::Lit(Lit::Str { .. }) => "string",
Expr::Lit(Lit::Num(..)) => "number",
Expr::Lit(Lit::Bool(..)) => "boolean",
Expr::Lit(Lit::Null(..)) | Expr::Object { .. } | Expr::Array { .. } => "object",
Expr::Unary(UnaryExpr {
op: op!("void"), ..
})
| Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => {
// We can assume `undefined` is `undefined`,
// because overriding `undefined` is always hard error in swc.
"undefined"
}
_ => {
return Expr::Unary(UnaryExpr {
op: op!("typeof"),
arg,
span,
})
}
};
return Expr::Lit(Lit::Str(Str {
span,
value: val.into(),
has_escape: false,
}));
}
op!("!") => match arg.as_bool() {
(_, Known(val)) => return make_bool_expr(span, !val, iter::once(arg)),
_ => return Expr::Unary(UnaryExpr { op, arg, span }),
},
op!(unary, "+") => match arg.as_number() {
Known(v) => {
return preserve_effects(
span,
Expr::Lit(Lit::Num(Number { value: v, span })),
iter::once(arg),
)
}
_ => return Expr::Unary(UnaryExpr { op, arg, span }),
},
op!(unary, "-") => match *arg {
Expr::Ident(Ident {
sym: js_word!("Infinity"),
span,
}) => return Expr::Unary(UnaryExpr { op, arg, span }),
// "-NaN" is "NaN"
Expr::Ident(Ident {
sym: js_word!("NaN"),
..
}) => return *arg,
Expr::Lit(Lit::Num(Number { value: f, .. })) => {
return Expr::Lit(Lit::Num(Number { value: -f, span }))
}
_ => {
// TODO: Report that user is something bad (negating non-number value)
}
},
op!("void") if !may_have_side_effects => {
return Expr::Unary(UnaryExpr {
op: op!("void"),
arg: box Expr::Lit(Lit::Num(Number {
value: 0.0,
span: arg.span(),
})),
span,
})
}
_ => {}
}
Expr::Unary(UnaryExpr { op, arg, span })
}
/// Try to fold arithmetic binary operators
fn perform_arithmetic_op(op: BinaryOp, left: &Expr, right: &Expr) -> Value<f64> {
let (lv, rv) = (left.as_number(), right.as_number());
if (lv.is_unknown() && rv.is_unknown())
|| !left.get_type().casted_to_number_on_add()
|| !right.get_type().casted_to_number_on_add()
{
return Unknown;
}
match op {
op!(bin, "+") => {
if let (Known(lv), Known(rv)) = (lv, rv) {
return Known(lv + rv);
}
if lv == Known(0.0) {
return rv;
} else if rv == Known(0.0) {
return lv;
}
return Unknown;
}
op!(bin, "-") => {
if let (Known(lv), Known(rv)) = (lv, rv) {
return Known(lv - rv);
}
// 0 - x => -x
if lv == Known(0.0) {
return rv;
}
// x - 0 => x
if rv == Known(0.0) {
return lv;
}
return Unknown;
}
_ => {}
}
return Unknown;
}
/// This actually performs `<`.
///
/// https://tc39.github.io/ecma262/#sec-abstract-relational-comparison
fn perform_abstract_rel_cmp(
_span: Span,
left: &Expr,
right: &Expr,
will_negate: bool,
) -> Value<bool> {
match (left, right) {
// Special case: `x < x` is always false.
(&Expr::Ident(Ident { sym: ref li, .. }, ..), &Expr::Ident(Ident { sym: ref ri, .. }))
if !will_negate && li == ri =>
{
return Known(false)
}
// Special case: `typeof a < typeof a` is always false.
(
&Expr::Unary(UnaryExpr {
op: op!("typeof"),
arg: box Expr::Ident(Ident { sym: ref li, .. }),
..
}),
&Expr::Unary(UnaryExpr {
op: op!("typeof"),
arg: box Expr::Ident(Ident { sym: ref ri, .. }),
..
}),
)
if li == ri =>
{
return Known(false)
}
_ => {}
}
// Try to evaluate based on the general type.
let (lt, rt) = (left.get_type(), right.get_type());
match (lt, rt) {
(Known(StringType), Known(StringType)) => {
match (left.as_string(), right.as_string()) {
(Known(lv), Known(rv)) => {
// In JS, browsers parse \v differently. So do not compare strings if one
// contains \v.
if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
return Unknown;
} else {
return Known(lv < rv);
}
}
_ => {}
}
}
_ => {}
}
// Then, try to evaluate based on the value of the node. Try comparing as
// numbers.
let (lv, rv) = (left.as_number()?, right.as_number()?);
if lv.is_nan() || rv.is_nan() {
return Known(will_negate);
}
return Known(lv < rv);
}
/// https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
fn perform_abstract_eq_cmp(span: Span, left: &Expr, right: &Expr) -> Value<bool> {
let (lt, rt) = (left.get_type()?, right.get_type()?);
if lt == rt {
return perform_strict_eq_cmp(span, left, right);
}
match (lt, rt) {
(NullType, UndefinedType) | (UndefinedType, NullType) => return Known(true),
(NumberType, StringType) | (_, BoolType) => {
let rv = right.as_number()?;
return perform_abstract_eq_cmp(
span,
left,
&Expr::Lit(Lit::Num(Number { value: rv, span })),
);
}
(StringType, NumberType) | (BoolType, _) => {
let lv = left.as_number()?;
return perform_abstract_eq_cmp(
span,
&Expr::Lit(Lit::Num(Number { value: lv, span })),
right,
);
}
(StringType, ObjectType)
| (NumberType, ObjectType)
| (ObjectType, StringType)
| (ObjectType, NumberType) => return Unknown,
_ => return Known(false),
}
}
/// https://tc39.github.io/ecma262/#sec-strict-equality-comparison
fn perform_strict_eq_cmp(_span: Span, left: &Expr, right: &Expr) -> Value<bool> {
// Any strict equality comparison against NaN returns false.
if left.is_nan() || right.is_nan() {
return Known(false);
}
match (left, right) {
// Special case, typeof a == typeof a is always true.
(
&Expr::Unary(UnaryExpr {
op: op!("typeof"),
arg: box Expr::Ident(Ident { sym: ref li, .. }),
..
}),
&Expr::Unary(UnaryExpr {
op: op!("typeof"),
arg: box Expr::Ident(Ident { sym: ref ri, .. }),
..
}),
)
if li == ri =>
{
return Known(true)
}
_ => {}
}
let (lt, rt) = (left.get_type()?, right.get_type()?);
// Strict equality can only be true for values of the same type.
if lt != rt {
return Known(false);
}
match lt {
UndefinedType | NullType => return Known(true),
NumberType => {
return Known(left.as_number()? == right.as_number()?);
}
StringType => {
let (lv, rv) = (left.as_string()?, right.as_string()?);
// In JS, browsers parse \v differently. So do not consider strings
// equal if one contains \v.
if lv.contains('\u{000B}') || rv.contains('\u{000B}') {
return Unknown;
}
return Known(lv == rv);
}
BoolType => {
let (lv, rv) = (left.as_pure_bool(), right.as_pure_bool());
// lv && rv || !lv && !rv
return lv.and(rv).or((!lv).and(!rv));
}
ObjectType | SymbolType => return Unknown,
}
}
/// make a new boolean expression preserving side effects, if any.
fn make_bool_expr<I>(span: Span, value: bool, orig: I) -> Expr
where
I: IntoIterator<Item = Box<Expr>>,
{
preserve_effects(span, Expr::Lit(Lit::Bool(Bool { value, span })), orig)
}
/// make a new expression which evaluates `val` preserving side effects, if any.
fn preserve_effects<I>(span: Span, val: Expr, exprs: I) -> Expr
where
I: IntoIterator<Item = Box<Expr>>,
{
/// Add side effects of `expr` to `v`
/// preserving order and conditions. (think a() ? yield b() : c())
fn add_effects(v: &mut Vec<Box<Expr>>, box expr: Box<Expr>) {
match expr {
Expr::Lit(..) | Expr::This(..) | Expr::Fn(..) | Expr::Arrow(..) | Expr::Ident(..) => {
return
}
// In most case, we can do nothing for this.
Expr::Update(_) | Expr::Assign(_) | Expr::Yield(_) | Expr::Await(_) => v.push(box expr),
// TODO
Expr::MetaProp(_) => v.push(box expr),
Expr::Call(_) => v.push(box expr),
Expr::New(_) => v.push(box expr),
Expr::Member(_) => v.push(box expr),
// We are at here because we could not determine value of test.
//TODO: Drop values if it does not have side effects.
Expr::Cond(_) => v.push(box expr),
Expr::Unary(UnaryExpr { arg, .. }) => add_effects(v, arg),
Expr::Bin(BinExpr { left, right, .. }) => {
add_effects(v, left);
add_effects(v, right);
}
Expr::Seq(SeqExpr { exprs, .. }) => exprs.into_iter().for_each(|e| add_effects(v, e)),
Expr::Paren(e) => add_effects(v, e.expr),
Expr::Object(ObjectLit { props, .. }) => {
props.into_iter().for_each(|node| match node {
Prop::Shorthand(..) => return,
Prop::KeyValue(KeyValueProp { key, value }) => {
match key {
PropName::Computed(e) => add_effects(v, e),
_ => {}
}
add_effects(v, value)
}
Prop::Getter(GetterProp { key, .. })
| Prop::Setter(SetterProp { key, .. })
| Prop::Method(MethodProp { key, .. }) => match key {
PropName::Computed(e) => add_effects(v, e),
_ => {}
},
Prop::Assign(..) => {
unreachable!("assign property in object literal is not a valid syntax")
}
})
}
Expr::Array(ArrayLit { elems, .. }) => {
elems.into_iter().filter_map(|e| e).fold(v, |v, e| {
add_effects(v, e.expr);
v
});
return;
}
Expr::Tpl { .. } => unimplemented!("add_effects for template literal"),
Expr::Class(ClassExpr { .. }) => unimplemented!("add_effects for class expression"),
}
}
let mut exprs = exprs.into_iter().fold(vec![], |mut v, e| {
add_effects(&mut v, e);
v
});
if exprs.is_empty() {
return val;
} else {
exprs.push(box val);
Expr::Seq(SeqExpr { exprs, span })
}
}

View File

@ -0,0 +1,198 @@
//! Ported from closure compiler.
use swc_common::{FoldWith, Folder, DUMMY_SP};
use swc_ecma_ast::*;
use util::*;
mod expr;
#[derive(Debug, Clone, Copy, Default)]
pub struct Simplify;
impl<T: StmtLike> Folder<Vec<T>> for Simplify
where
Self: Folder<T>,
{
fn fold(&mut self, stmts: Vec<T>) -> Vec<T> {
let mut buf = Vec::with_capacity(stmts.len());
for stmt_like in stmts {
let stmt_like = self.fold(stmt_like);
let stmt_like = match stmt_like.try_into_stmt() {
Ok(stmt) => {
let stmt = match stmt {
// Remove empty statements.
Stmt::Empty(..) => continue,
// Control flow
Stmt::Throw(..)
| Stmt::Return { .. }
| Stmt::Continue { .. }
| Stmt::Break { .. } => {
let stmt_like = T::from_stmt(stmt);
buf.push(stmt_like);
return buf;
}
// Optimize if statement.
Stmt::If(IfStmt {
test,
cons,
alt,
span,
}) => {
// check if
let node = match test.as_bool() {
(Pure, Known(val)) => {
if val {
*cons
} else {
alt.map(|e| *e).unwrap_or(Stmt::Empty(EmptyStmt { span }))
}
}
// TODO: Impure
_ => Stmt::If(IfStmt {
test,
cons,
alt,
span,
}),
};
node
}
_ => stmt,
};
T::from_stmt(stmt)
}
Err(stmt_like) => stmt_like,
};
buf.push(stmt_like);
}
buf
}
}
impl Folder<Stmt> for Simplify {
fn fold(&mut self, stmt: Stmt) -> Stmt {
let stmt = stmt.fold_children(self);
match stmt {
// `1;` -> `;`
Stmt::Expr(box node) => match node {
Expr::Lit(Lit::Num(..)) | Expr::Lit(Lit::Bool(..)) | Expr::Lit(Lit::Regex(..)) => {
Stmt::Empty(EmptyStmt { span: DUMMY_SP })
}
//
// Function expressions are useless if they are not used.
//
// As function expressions cannot start with 'function',
// this will be reached only if other things
// are removed while folding chilren.
Expr::Fn(FnExpr {
function: Function { span, .. },
..
}) => Stmt::Empty(EmptyStmt { span }),
_ => Stmt::Expr(box node),
},
Stmt::Block(BlockStmt { span, stmts }) => {
if stmts.len() == 0 {
return Stmt::Empty(EmptyStmt { span });
} else if stmts.len() == 1 {
// TODO: Check if lexical variable exists.
return stmts.into_iter().next().unwrap();
} else {
Stmt::Block(BlockStmt { span, stmts })
}
}
Stmt::Try(TryStmt {
span,
block,
handler,
finalizer,
}) => {
// Only leave the finally block if try block is empty
if block.is_empty() {
return finalizer
.map(Stmt::Block)
.unwrap_or(Stmt::Empty(EmptyStmt { span }));
}
// If catch block and finally block is empty, remove try-catch is useless.
if handler.is_empty() && finalizer.is_empty() {
return Stmt::Block(block);
}
Stmt::Try(TryStmt {
span,
block,
handler,
finalizer,
})
}
// Remove empty else block.
// As we fold children before parent, unused expression
// statements without side effects are converted to
// Stmt::Empty before here.
Stmt::If(IfStmt {
span,
test,
cons,
alt,
}) => {
if alt.is_empty() {
return Stmt::If(IfStmt {
span,
test,
cons,
alt: None,
});
}
Stmt::If(IfStmt {
span,
test,
cons,
alt,
})
}
_ => stmt,
}
}
}
pub trait StmtLike: Sized {
fn try_into_stmt(self) -> Result<Stmt, Self>;
fn from_stmt(stmt: Stmt) -> Self;
}
impl StmtLike for Stmt {
fn try_into_stmt(self) -> Result<Stmt, Self> {
Ok(self)
}
fn from_stmt(stmt: Stmt) -> Self {
stmt
}
}
impl StmtLike for ModuleItem {
fn try_into_stmt(self) -> Result<Stmt, Self> {
match self {
ModuleItem::Stmt(stmt) => Ok(stmt),
_ => Err(self),
}
}
fn from_stmt(stmt: Stmt) -> Self {
ModuleItem::Stmt(stmt)
}
}
// impl Folder<Stmt> for Simplify {
// fn fold(&mut self, stmt: Stmt) -> Stmt {
// stmt.fold_children(&mut FoldConst)
// }
// }

View File

@ -0,0 +1,42 @@
use swc_common::{Span, Spanned};
use swc_ecma_ast::*;
pub(crate) trait ExprFactory: Into<Expr> {
fn as_arg(self) -> ExprOrSpread {
ExprOrSpread {
expr: box self.into(),
spread: None,
}
}
fn apply(self, span: Span, this: Box<Expr>, args: Vec<ExprOrSpread>) -> Expr {
let apply = Expr::Member(MemberExpr {
// TODO
span,
obj: ExprOrSuper::Expr(box self.into()),
prop: box Expr::Ident(Ident::new(js_word!("apply"), span)),
computed: false,
});
Expr::Call(CallExpr {
span,
callee: ExprOrSuper::Expr(box apply),
args: vec![ExprOrSpread {
expr: this,
spread: None,
}]
.into_iter()
.chain(args)
.collect(),
})
}
fn wrap_with_paren(self) -> Expr {
let expr = box self.into();
let span = expr.span();
Expr::Paren(ParenExpr { expr, span })
}
}
impl<T: Into<Expr>> ExprFactory for T {}

View File

@ -0,0 +1,595 @@
pub(crate) use self::{
factory::ExprFactory,
value::{
Type::{
self, Bool as BoolType, Null as NullType, Num as NumberType, Obj as ObjectType,
Str as StringType, Symbol as SymbolType, Undefined as UndefinedType,
},
Value::{self, Known, Unknown},
},
Purity::{MayBeImpure, Pure},
};
use std::{
borrow::Cow,
f64::{INFINITY, NAN},
num::FpCategory,
ops::Add,
};
use swc_atoms::JsWord;
use swc_ecma_ast::*;
mod factory;
mod value;
pub type BoolValue = Value<bool>;
pub trait IsEmpty {
fn is_empty(&self) -> bool;
}
impl IsEmpty for BlockStmt {
fn is_empty(&self) -> bool {
self.stmts.is_empty()
}
}
impl IsEmpty for CatchClause {
fn is_empty(&self) -> bool {
self.body.stmts.is_empty()
}
}
impl IsEmpty for Stmt {
fn is_empty(&self) -> bool {
match *self {
Stmt::Empty(_) => true,
Stmt::Block(ref b) => b.is_empty(),
_ => false,
}
}
}
impl<T: IsEmpty> IsEmpty for Option<T> {
fn is_empty(&self) -> bool {
match *self {
Some(ref node) => node.is_empty(),
None => true,
}
}
}
impl<T: IsEmpty> IsEmpty for Box<T> {
fn is_empty(&self) -> bool {
<T as IsEmpty>::is_empty(&*self)
}
}
pub trait ExprExt: Sized {
fn as_expr_kind(&self) -> &Expr;
/// Checks if `self` is `NaN`.
fn is_nan(&self) -> bool {
self.is_ident_ref_to(js_word!("NaN"))
}
/// Is `self` an IdentifierReference to `id`?
fn is_ident_ref_to(&self, id: JsWord) -> bool {
match *self.as_expr_kind() {
Expr::Ident(Ident { ref sym, .. }) if *sym == id => true,
_ => false,
}
}
fn as_pure_bool(&self) -> BoolValue {
match self.as_bool() {
(Pure, Known(b)) => Known(b),
_ => Unknown,
}
}
///
/// This method emulates the `Boolean()` JavaScript cast function.
///Note: unlike getPureBooleanValue this function does not return `None`
///for expressions with side-effects.
fn as_bool(&self) -> (Purity, BoolValue) {
let expr = self.as_expr_kind();
let val = match *expr {
Expr::Paren(ref e) => return e.expr.as_bool(),
Expr::Seq(SeqExpr { ref exprs, .. }) => return exprs.last().unwrap().as_bool(),
Expr::Assign(AssignExpr { ref right, .. }) => return right.as_bool(),
Expr::Unary(UnaryExpr {
op: op!("!"),
ref arg,
..
}) => {
let (p, v) = arg.as_bool();
return (p, !v);
}
Expr::Bin(BinExpr {
ref left,
op: op @ op!("&"),
ref right,
..
})
| Expr::Bin(BinExpr {
ref left,
op: op @ op!("|"),
ref right,
..
}) => {
// TODO: Ignore purity if value cannot be reached.
let (lp, lv) = left.as_bool();
let (rp, rv) = right.as_bool();
if lp + rp == Pure {
return (Pure, lv.and(rv));
}
if op == op!("&") {
lv.and(rv)
} else {
lv.or(rv)
}
}
Expr::Fn(..) | Expr::Class(..) | Expr::New(..) | Expr::Array(..) | Expr::Object(..) => {
Known(true)
}
Expr::Unary(UnaryExpr {
op: op!("void"),
arg: _,
..
}) => Known(false),
Expr::Lit(ref lit) => {
return (
Pure,
Known(match *lit {
Lit::Num(Number { value: n, .. }) => match n.classify() {
FpCategory::Nan | FpCategory::Zero => false,
_ => true,
},
Lit::Bool(b) => b.value,
Lit::Str(Str { ref value, .. }) => !value.is_empty(),
Lit::Null(..) => false,
Lit::Regex(..) => true,
}),
)
}
//TODO?
_ => Unknown,
};
(MayBeImpure, val)
}
/// Emulates javascript Number() cast function.
fn as_number(&self) -> Value<f64> {
let expr = self.as_expr_kind();
let v = match *expr {
Expr::Lit(ref l) => match *l {
Lit::Bool(Bool { value: true, .. }) => 1.0,
Lit::Bool(Bool { value: false, .. }) | Lit::Null(..) => 0.0,
Lit::Num(Number { value: n, .. }) => n,
Lit::Str(Str { ref value, .. }) => return num_from_str(value),
_ => return Unknown,
},
Expr::Ident(Ident { ref sym, .. }) => match &**sym {
"undefined" | "NaN" => NAN,
"Infinity" => INFINITY,
_ => return Unknown,
},
Expr::Unary(UnaryExpr {
op: op!(unary, "-"),
arg:
box Expr::Ident(Ident {
sym: js_word!("Infinity"),
..
}),
span: _,
}) => -INFINITY,
Expr::Unary(UnaryExpr {
op: op!("!"),
ref arg,
span: _,
}) => match arg.as_bool() {
(Pure, Known(v)) => {
if v {
0.0
} else {
1.0
}
}
_ => return Unknown,
},
Expr::Unary(UnaryExpr {
op: op!("void"),
ref arg,
span: _,
}) => {
if arg.may_have_side_effects() {
return Unknown;
} else {
NAN
}
}
Expr::Tpl(..) | Expr::Object(ObjectLit { .. }) | Expr::Array(ArrayLit { .. }) => {
return num_from_str(&*self.as_string()?)
}
_ => return Unknown,
};
Known(v)
}
fn as_string(&self) -> Value<Cow<str>> {
let expr = self.as_expr_kind();
match *expr {
Expr::Lit(ref l) => match *l {
Lit::Str(Str { ref value, .. }) => Known(Cow::Borrowed(value)),
Lit::Num(ref n) => Known(format!("{}", n).into()),
Lit::Bool(Bool { value: true, .. }) => Known(Cow::Borrowed("true")),
Lit::Bool(Bool { value: false, .. }) => Known(Cow::Borrowed("false")),
Lit::Null(..) => Known(Cow::Borrowed("null")),
_ => Unknown,
},
Expr::Tpl(_) => {
// TODO:
// Only convert a template literal if all its expressions can be converted.
unimplemented!("TplLit.as_string()")
}
Expr::Ident(Ident { ref sym, .. }) => match &**sym {
"undefined" | "Infinity" | "NaN" => Known(Cow::Borrowed(&**sym)),
_ => Unknown,
},
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}) => Known(Cow::Borrowed("undefined")),
Expr::Unary(UnaryExpr {
op: op!("!"),
ref arg,
..
}) => Known(Cow::Borrowed(if arg.as_pure_bool()? {
"false"
} else {
"true"
})),
Expr::Array(ArrayLit { ref elems, .. }) => {
let mut first = true;
let mut buf = String::new();
// null, undefined is "" in array literl.
for elem in elems {
let e = match *elem {
Some(ref elem) => match *elem {
ExprOrSpread { ref expr, .. } => match **expr {
Expr::Lit(Lit::Null(..))
| Expr::Ident(Ident {
sym: js_word!("undefined"),
..
}) => Cow::Borrowed(""),
_ => expr.as_string()?,
},
},
None => Cow::Borrowed(""),
};
buf.push_str(&e);
if first {
first = false;
} else {
buf.push(',');
}
}
Known(buf.into())
}
Expr::Object(ObjectLit { .. }) => Known(Cow::Borrowed("[object Object]")),
_ => Unknown,
}
}
fn get_type(&self) -> Value<Type> {
let expr = self.as_expr_kind();
match *expr {
Expr::Assign(AssignExpr {
ref right,
op: op!("="),
..
}) => right.get_type(),
Expr::Seq(SeqExpr { ref exprs, .. }) => exprs
.last()
.expect("sequence expression should not be empty")
.get_type(),
Expr::Bin(BinExpr {
ref left,
op: op!("&&"),
ref right,
..
})
| Expr::Bin(BinExpr {
ref left,
op: op!("||"),
ref right,
..
})
| Expr::Cond(CondExpr {
cons: ref left,
alt: ref right,
..
}) => {
let (lt, rt) = (left.get_type(), right.get_type());
if lt == rt {
return lt;
}
return Unknown;
}
Expr::Bin(BinExpr {
ref left,
op: op!(bin, "+"),
ref right,
..
}) => {
let rt = right.get_type();
if rt == Known(StringType) {
return Known(StringType);
}
let lt = left.get_type();
if lt == Known(StringType) {
return Known(StringType);
}
match (lt, rt) {
// There are some pretty weird cases for object types:
// {} + [] === "0"
// [] + {} ==== "[object Object]"
(Known(ObjectType), _) | (_, Known(ObjectType)) => return Unknown,
_ => {}
}
// ADD used with compilations of null, undefined, boolean and number always
// result in numbers.
if lt.casted_to_number_on_add() && rt.casted_to_number_on_add() {
return Known(NumberType);
}
// There are some pretty weird cases for object types:
// {} + [] === "0"
// [] + {} ==== "[object Object]"
return Unknown;
}
Expr::Assign(AssignExpr {
op: op!("+="),
ref right,
..
}) => {
if right.get_type() == Known(StringType) {
return Known(StringType);
}
return Unknown;
}
Expr::Ident(Ident { ref sym, .. }) => {
return Known(match *sym {
js_word!("undefined") => UndefinedType,
js_word!("NaN") | js_word!("Infinity") => NumberType,
_ => return Unknown,
})
}
Expr::Lit(Lit::Num(..))
| Expr::Assign(AssignExpr { op: op!("&="), .. })
| Expr::Assign(AssignExpr { op: op!("^="), .. })
| Expr::Assign(AssignExpr { op: op!("|="), .. })
| Expr::Assign(AssignExpr { op: op!("<<="), .. })
| Expr::Assign(AssignExpr { op: op!(">>="), .. })
| Expr::Assign(AssignExpr {
op: op!(">>>="), ..
})
| Expr::Assign(AssignExpr { op: op!("-="), .. })
| Expr::Assign(AssignExpr { op: op!("*="), .. })
| Expr::Assign(AssignExpr { op: op!("**="), .. })
| Expr::Assign(AssignExpr { op: op!("/="), .. })
| Expr::Assign(AssignExpr { op: op!("%="), .. })
| Expr::Unary(UnaryExpr { op: op!("~"), .. })
| Expr::Bin(BinExpr { op: op!("|"), .. })
| Expr::Bin(BinExpr { op: op!("^"), .. })
| Expr::Bin(BinExpr { op: op!("&"), .. })
| Expr::Bin(BinExpr { op: op!("<<"), .. })
| Expr::Bin(BinExpr { op: op!(">>"), .. })
| Expr::Bin(BinExpr { op: op!(">>>"), .. })
| Expr::Bin(BinExpr {
op: op!(bin, "-"), ..
})
| Expr::Bin(BinExpr { op: op!("*"), .. })
| Expr::Bin(BinExpr { op: op!("%"), .. })
| Expr::Bin(BinExpr { op: op!("/"), .. })
| Expr::Bin(BinExpr { op: op!("**"), .. })
| Expr::Update(UpdateExpr { op: op!("++"), .. })
| Expr::Update(UpdateExpr { op: op!("--"), .. })
| Expr::Unary(UnaryExpr {
op: op!(unary, "+"),
..
})
| Expr::Unary(UnaryExpr {
op: op!(unary, "-"),
..
}) => return Known(NumberType),
// Primitives
Expr::Lit(Lit::Bool(..))
| Expr::Bin(BinExpr { op: op!("=="), .. })
| Expr::Bin(BinExpr { op: op!("!="), .. })
| Expr::Bin(BinExpr { op: op!("==="), .. })
| Expr::Bin(BinExpr { op: op!("!=="), .. })
| Expr::Bin(BinExpr { op: op!("<"), .. })
| Expr::Bin(BinExpr { op: op!("<="), .. })
| Expr::Bin(BinExpr { op: op!(">"), .. })
| Expr::Bin(BinExpr { op: op!(">="), .. })
| Expr::Bin(BinExpr { op: op!("in"), .. })
| Expr::Bin(BinExpr {
op: op!("instanceof"),
..
})
| Expr::Unary(UnaryExpr { op: op!("!"), .. })
| Expr::Unary(UnaryExpr {
op: op!("delete"), ..
}) => return Known(BoolType),
Expr::Unary(UnaryExpr {
op: op!("typeof"), ..
})
| Expr::Lit(Lit::Str { .. }) => return Known(StringType),
Expr::Lit(Lit::Null(..)) => return Known(NullType),
Expr::Unary(UnaryExpr {
op: op!("void"), ..
}) => return Known(UndefinedType),
Expr::Fn(..)
| Expr::New(NewExpr { .. })
| Expr::Array(ArrayLit { .. })
| Expr::Object(ObjectLit { .. })
| Expr::Lit(Lit::Regex(..)) => return Known(ObjectType),
_ => Unknown,
}
}
fn may_have_side_effects(&self) -> bool {
match *self.as_expr_kind() {
Expr::Lit(..) | Expr::Ident(..) | Expr::This(..) => false,
Expr::Paren(ref e) => e.expr.may_have_side_effects(),
// Function expression does not have any side effect if it's not used.
Expr::Fn(..) | Expr::Arrow(ArrowExpr { .. }) => false,
// TODO
Expr::Class(..) => true,
Expr::Array(ArrayLit { ref elems, .. }) => elems
.iter()
.filter_map(|e| e.as_ref())
.any(|e| e.expr.may_have_side_effects()),
Expr::Unary(UnaryExpr { ref arg, .. }) => arg.may_have_side_effects(),
Expr::Bin(BinExpr {
ref left,
ref right,
..
}) => left.may_have_side_effects() || right.may_have_side_effects(),
//TODO
Expr::Tpl(_) => true,
Expr::MetaProp(_) => true,
Expr::Await(_)
| Expr::Yield(_)
| Expr::Member(_)
| Expr::Update(_)
| Expr::Assign(_) => true,
// TODO
Expr::New(_) => true,
// TODO
Expr::Call(_) => true,
Expr::Seq(SeqExpr { ref exprs, .. }) => exprs.iter().any(|e| e.may_have_side_effects()),
Expr::Cond(CondExpr {
ref test,
ref cons,
ref alt,
..
}) => {
test.may_have_side_effects()
|| cons.may_have_side_effects()
|| alt.may_have_side_effects()
}
Expr::Object(ObjectLit { ref props, .. }) => props.iter().any(|node| match *node {
Prop::Shorthand(..) => false,
Prop::KeyValue(KeyValueProp { ref key, ref value }) => {
let k = match *key {
PropName::Computed(ref e) => e.may_have_side_effects(),
_ => false,
};
k || value.may_have_side_effects()
}
_ => true,
}),
}
}
}
fn num_from_str(s: &str) -> Value<f64> {
if s.contains('\u{000b}') {
return Unknown;
}
// TODO: Check if this is correct
let s = s.trim();
if s.is_empty() {
return Known(0.0);
}
if s.starts_with("0x") || s.starts_with("0X") {
return match s[2..4].parse() {
Ok(n) => Known(n),
Err(_) => Known(NAN),
};
}
if (s.starts_with('-') || s.starts_with('+'))
&& (s[1..].starts_with("0x") || s[1..].starts_with("0X"))
{
// hex numbers with explicit signs vary between browsers.
return Unknown;
}
// Firefox and IE treat the "Infinity" differently. Firefox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
match s {
"infinity" | "+infinity" | "-infinity" => return Unknown,
_ => {}
}
Known(s.parse().ok().unwrap_or(NAN))
}
impl ExprExt for Box<Expr> {
fn as_expr_kind(&self) -> &Expr {
&self
}
}
impl ExprExt for Expr {
fn as_expr_kind(&self) -> &Expr {
&self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Purity {
MayBeImpure,
Pure,
}
impl Purity {
pub fn is_pure(self) -> bool {
self == Pure
}
}
impl Add for Purity {
type Output = Self;
fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Pure, Pure) => Pure,
_ => MayBeImpure,
}
}
}

View File

@ -0,0 +1,97 @@
use self::Value::{Known, Unknown};
use std::ops::{Not, Try};
/// Runtime value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Value<T> {
Known(T),
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Type {
Undefined,
Null,
Bool,
Str,
Symbol,
Num,
Obj,
}
impl Value<Type> {
pub fn casted_to_number_on_add(self) -> bool {
match self {
Known(Type::Bool) | Known(Type::Null) | Known(Type::Num) | Known(Type::Undefined) => {
true
}
_ => false,
}
}
}
pub struct UnknownError;
impl<T> Try for Value<T> {
type Ok = T;
type Error = UnknownError;
fn from_ok(t: T) -> Self {
Known(t)
}
fn from_error(_: UnknownError) -> Self {
Unknown
}
fn into_result(self) -> Result<T, UnknownError> {
match self {
Known(t) => Ok(t),
Unknown => Err(UnknownError),
}
}
}
impl<T> Value<T> {
pub fn is_unknown(&self) -> bool {
match *self {
Unknown => true,
_ => false,
}
}
pub fn is_known(&self) -> bool {
match *self {
Known(..) => true,
_ => false,
}
}
}
impl Value<bool> {
pub fn and(self, other: Self) -> Self {
match self {
Known(true) => other,
Known(false) => Known(false),
Unknown => match other {
Known(false) => Known(false),
_ => Unknown,
},
}
}
pub fn or(self, other: Self) -> Self {
match self {
Known(true) => Known(true),
Known(false) => other,
Unknown => match other {
Known(true) => Known(true),
_ => Unknown,
},
}
}
}
impl Not for Value<bool> {
type Output = Self;
fn not(self) -> Self {
match self {
Value::Known(b) => Value::Known(!b),
Value::Unknown => Value::Unknown,
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@ use std::{
rc::Rc, rc::Rc,
}; };
use swc::Compiler; use swc::Compiler;
use swc_common::errors::{CodeMap, FilePathMapping, Handler}; use swc_common::{errors::Handler, FilePathMapping, SourceMap};
fn main() { fn main() {
run().unwrap() run().unwrap()
@ -62,7 +62,7 @@ fn run() -> Result<(), Box<Error>> {
.build() .build()
.expect("failed to create rayon::ThreadPool?"); .expect("failed to create rayon::ThreadPool?");
let cm = Rc::new(CodeMap::new(FilePathMapping::empty())); let cm = Rc::new(SourceMap::new(FilePathMapping::empty()));
let handler = Handler::with_tty_emitter( let handler = Handler::with_tty_emitter(
::swc_common::errors::ColorConfig::Always, ::swc_common::errors::ColorConfig::Always,

View File

@ -19,7 +19,7 @@ use swc_ecmascript::{
}; };
pub struct Compiler { pub struct Compiler {
codemap: Lrc<CodeMap>, codemap: Lrc<SourceMap>,
threads: rayon::ThreadPool, threads: rayon::ThreadPool,
logger: Logger, logger: Logger,
handler: Handler, handler: Handler,
@ -28,7 +28,7 @@ pub struct Compiler {
impl Compiler { impl Compiler {
pub fn new( pub fn new(
logger: Logger, logger: Logger,
codemap: Lrc<SourceMapperDyn>, codemap: Lrc<SourceMap>,
handler: Handler, handler: Handler,
threads: rayon::ThreadPool, threads: rayon::ThreadPool,
) -> Self { ) -> Self {
@ -42,10 +42,7 @@ impl Compiler {
/// TODO /// TODO
pub fn parse_js(&self, path: &Path) -> PResult<Module> { pub fn parse_js(&self, path: &Path) -> PResult<Module> {
let fm = self let fm = self.codemap.load_file(path).expect("failed to load file");
.codemap
.load_file_and_lines(path)
.expect("failed to load file");
Parser::new( Parser::new(
ParseSess { ParseSess {