mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 13:51:19 +03:00
Compiler baseline
- Fix binary - Add base for transformers This will be last large squash.
This commit is contained in:
parent
f9be576e02
commit
e12dcf0452
@ -66,6 +66,19 @@ fn main() {
|
||||
"private",
|
||||
"protected",
|
||||
"public",
|
||||
//
|
||||
"Object",
|
||||
"length",
|
||||
"Infinity",
|
||||
"undefined",
|
||||
"NaN",
|
||||
"RegExp",
|
||||
// helpers
|
||||
"apply",
|
||||
"call",
|
||||
"concat",
|
||||
"_extends",
|
||||
"_toConsumableArray"
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -3,9 +3,9 @@
|
||||
//! -----
|
||||
//!
|
||||
//! This module use [`::rustc_errors`][] internally.
|
||||
//!
|
||||
|
||||
pub use self::{diagnostic::*, diagnostic_builder::DiagnosticBuilder, handler::*};
|
||||
#[doc(inline)]
|
||||
pub use rustc_errors::{
|
||||
ColorConfig,
|
||||
Level::{self, *},
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::*;
|
||||
use crate::{FileLoader, FilePathMapping, SourceMap};
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
@ -34,9 +35,9 @@ function foo() {
|
||||
|
||||
#[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
|
||||
.load_file_and_lines("tmp.js".as_ref())
|
||||
.load_file("tmp.js".into())
|
||||
.expect("failed to load tmp.js");
|
||||
println!(
|
||||
"File (start={},end={})",
|
||||
@ -60,7 +61,7 @@ fn test() {
|
||||
|
||||
DiagnosticBuilder::new_with_code(
|
||||
&handler,
|
||||
Warning,
|
||||
super::Warning,
|
||||
Some(DiagnosticId::Lint("WITH_STMT".into())),
|
||||
"Lint: With statement",
|
||||
)
|
||||
|
@ -22,7 +22,9 @@ pub use self::{
|
||||
pos::*,
|
||||
};
|
||||
|
||||
pub use syntax::source_map::{FilePathMapping, SourceMap, SpanSnippetError};
|
||||
pub use syntax::source_map::{
|
||||
FileLines, FileLoader, FileName, FilePathMapping, SourceMap, SpanSnippetError,
|
||||
};
|
||||
mod ast_node;
|
||||
pub mod errors;
|
||||
mod fold;
|
||||
|
@ -7,5 +7,6 @@ authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
swc_ecma_ast = { path = "./ast" }
|
||||
swc_ecma_codegen = { path = "./codegen" }
|
||||
swc_ecma_parser = { path = "./parser" }
|
||||
swc_ecma_transforms = { path = "./transforms" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -143,12 +143,12 @@ pub struct MemberExpr {
|
||||
pub prop: Box<Expr>,
|
||||
pub computed: bool,
|
||||
}
|
||||
|
||||
#[ast_node]
|
||||
pub struct CondExpr {
|
||||
#[span(lo)]
|
||||
pub span: Span,
|
||||
pub test: Box<Expr>,
|
||||
pub cons: Box<Expr>,
|
||||
#[span(hi)]
|
||||
pub alt: Box<Expr>,
|
||||
}
|
||||
|
||||
|
@ -86,4 +86,34 @@ impl Ident {
|
||||
pub fn new(sym: JsWord, span: Span) -> Self {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ impl<'a> Emitter<'a> {
|
||||
|
||||
break;
|
||||
}
|
||||
if ext_sp.lo() < BytePos(3) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// include `//`
|
||||
ext_sp = ext_sp.with_lo(ext_sp.lo() - BytePos(3));
|
||||
|
@ -1435,7 +1435,11 @@ fn get_text_of_node<T: Spanned>(
|
||||
node: &T,
|
||||
_include_travia: bool,
|
||||
) -> 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 src = match file.src {
|
||||
|
@ -4,7 +4,6 @@
|
||||
#![feature(specialization)]
|
||||
#![feature(never_type)]
|
||||
// #![feature(nll)]
|
||||
#![feature(proc_macro)]
|
||||
#![feature(try_from)]
|
||||
#![feature(try_trait)]
|
||||
#![deny(unreachable_patterns)]
|
||||
|
@ -96,7 +96,12 @@ impl<'a, I: Input> Parser<'a, I> {
|
||||
expect!(':');
|
||||
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 {
|
||||
return Ok(test);
|
||||
}
|
||||
|
@ -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))
|
||||
}),
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -244,7 +244,7 @@ macro_rules! span {
|
||||
start.0, end.0
|
||||
)
|
||||
}
|
||||
::swc_common::Span::new(start, end, Default::default())
|
||||
::swc_common::Span::new(start, end, ::swc_common::SyntaxContext::empty())
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -2,5 +2,5 @@ error: Unterminated block comment
|
||||
--> $DIR/tests/test262-parser/fail/100c329e6dd70e5a.js:1:1
|
||||
|
|
||||
1 | /*
|
||||
| ^^^^^
|
||||
| ^^^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Expected Comma
|
||||
--> $DIR/tests/test262-parser/fail/1a5b0dfa9fde985d.js:1:24
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/1a5b0dfa9fde985d.js:1:25
|
||||
|
|
||||
1 | function* f() { [yield {a = 0}]; }
|
||||
| ^
|
||||
| ^^^^^
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
error: Unterminated block comment
|
||||
--> $DIR/tests/test262-parser/fail/1c6ba8177a9624f0.js:1:1
|
||||
|
|
||||
1 | / /*
|
||||
2 | |
|
||||
| |_^
|
||||
1 | /*
|
||||
| ^^^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/3118eaa619345896.js:3:3
|
||||
--> $DIR/tests/test262-parser/fail/3118eaa619345896.js:2:3
|
||||
|
|
||||
3 | */]
|
||||
2 | */]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/42cb3f2a38cb2930.js:3:3
|
||||
--> $DIR/tests/test262-parser/fail/42cb3f2a38cb2930.js:2:3
|
||||
|
|
||||
3 | */]
|
||||
2 | */]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/62d72a3c3d14d150.js:3:1
|
||||
--> $DIR/tests/test262-parser/fail/62d72a3c3d14d150.js:2:2
|
||||
|
|
||||
3 | ]
|
||||
2 |
]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/67419010fc81184a.js:3:3
|
||||
--> $DIR/tests/test262-parser/fail/67419010fc81184a.js:2:4
|
||||
|
|
||||
3 | */]
|
||||
2 |
*/]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/85bb33accf520f1d.js:3:1
|
||||
--> $DIR/tests/test262-parser/fail/85bb33accf520f1d.js:2:1
|
||||
|
|
||||
3 | ]
|
||||
2 | ]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/8f7c7b7f4d70f975.js:3:1
|
||||
--> $DIR/tests/test262-parser/fail/8f7c7b7f4d70f975.js:2:1
|
||||
|
|
||||
3 | ]
|
||||
2 | ]
|
||||
| ^
|
||||
|
||||
|
@ -2,5 +2,5 @@ error: Unterminated block comment
|
||||
--> $DIR/tests/test262-parser/fail/ac1ee2739ad30d66.js:1:1
|
||||
|
|
||||
1 | /*
|
||||
| ^^^
|
||||
| ^^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/b7ae8c17f892abf6.js:3:1
|
||||
--> $DIR/tests/test262-parser/fail/b7ae8c17f892abf6.js:2:2
|
||||
|
|
||||
3 | ]
|
||||
2 |
]
|
||||
| ^
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
error: Expected ';', '}' or <eof>
|
||||
--> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:5
|
||||
--> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:3
|
||||
|
|
||||
1 | __
|
||||
| ^
|
||||
| ^
|
||||
|
|
||||
note: This is the expression part of an expression statement
|
||||
--> $DIR/tests/test262-parser/fail/b94278c0e4bdb364.js:1:1
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/c49106fce7ac6b09.js:2:1
|
||||
--> $DIR/tests/test262-parser/fail/c49106fce7ac6b09.js:1:2
|
||||
|
|
||||
2 | ]
|
||||
1 |
]
|
||||
| ^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
error: Unexpected token
|
||||
--> $DIR/tests/test262-parser/fail/c7023e867afea771.js:3:1
|
||||
--> $DIR/tests/test262-parser/fail/c7023e867afea771.js:2:1
|
||||
|
|
||||
3 | ]
|
||||
2 | ]
|
||||
| ^
|
||||
|
||||
|
@ -2,5 +2,5 @@ error: Unterminated block comment
|
||||
--> $DIR/tests/test262-parser/fail/d056300d8658429f.js:1:1
|
||||
|
|
||||
1 | /*
|
||||
| ^^^^^
|
||||
| ^^^
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
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
|
||||
| ^
|
||||
|
||||
|
@ -14,7 +14,7 @@ use std::{
|
||||
path::Path,
|
||||
};
|
||||
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 testing::NormalizedOutput;
|
||||
|
||||
@ -87,8 +87,12 @@ fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
|
||||
const IGNORED_ERROR_TESTS: &[&str] = &[
|
||||
// Wrong tests
|
||||
"0d5e450f1da8a92a.js",
|
||||
"346316bef54d805a.js",
|
||||
"976b6247ca78ab51.js",
|
||||
"ae0a7ac275bc9f5c.js",
|
||||
"748656edbfb2d0bb.js",
|
||||
"79f882da06f88c9f.js",
|
||||
"d28e80d99f819136.js",
|
||||
"92b6af54adef3624.js",
|
||||
"ef2d369cccc5386c.js",
|
||||
// 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>
|
||||
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 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(
|
||||
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]
|
||||
fn identity() {
|
||||
let args: Vec<_> = env::args().collect();
|
||||
|
@ -1,2 +1,4 @@
|
||||
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_transforms as transforms;
|
||||
|
17
ecmascript/transforms/Cargo.toml
Normal file
17
ecmascript/transforms/Cargo.toml
Normal 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"
|
57
ecmascript/transforms/README.md
Normal file
57
ecmascript/transforms/README.md
Normal 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
|
70
ecmascript/transforms/src/compat/arrow.rs
Normal file
70
ecmascript/transforms/src/compat/arrow.rs
Normal 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::*;
|
||||
|
||||
}
|
30
ecmascript/transforms/src/compat/es3/member_expr_lits.rs
Normal file
30
ecmascript/transforms/src/compat/es3/member_expr_lits.rs
Normal 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
|
||||
}
|
||||
}
|
7
ecmascript/transforms/src/compat/es3/mod.rs
Normal file
7
ecmascript/transforms/src/compat/es3/mod.rs
Normal 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;
|
82
ecmascript/transforms/src/compat/es3/prop_lits.rs
Normal file
82
ecmascript/transforms/src/compat/es3/prop_lits.rs
Normal 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
|
||||
};"#
|
||||
);
|
||||
|
||||
}
|
30
ecmascript/transforms/src/compat/es3/reserved_word.rs
Normal file
30
ecmascript/transforms/src/compat/es3/reserved_word.rs
Normal 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
|
||||
}
|
||||
}
|
109
ecmascript/transforms/src/compat/exponentation.rs
Normal file
109
ecmascript/transforms/src/compat/exponentation.rs
Normal 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);"
|
||||
// );
|
||||
}
|
16
ecmascript/transforms/src/compat/helpers.rs
Normal file
16
ecmascript/transforms/src/compat/helpers.rs
Normal 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
|
||||
}
|
||||
}
|
13
ecmascript/transforms/src/compat/mod.rs
Normal file
13
ecmascript/transforms/src/compat/mod.rs
Normal 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;
|
125
ecmascript/transforms/src/compat/shorthand.rs
Normal file
125
ecmascript/transforms/src/compat/shorthand.rs
Normal 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
|
||||
};"
|
||||
);
|
||||
|
||||
}
|
202
ecmascript/transforms/src/compat/spread.rs
Normal file
202
ecmascript/transforms/src/compat/spread.rs
Normal 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);"
|
||||
);
|
||||
}
|
80
ecmascript/transforms/src/compat/sticky_regex.rs
Normal file
80
ecmascript/transforms/src/compat/sticky_regex.rs
Normal 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+/;"
|
||||
);
|
||||
}
|
19
ecmascript/transforms/src/compat/template_literal.rs
Normal file
19
ecmascript/transforms/src/compat/template_literal.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
32
ecmascript/transforms/src/lib.rs
Normal file
32
ecmascript/transforms/src/lib.rs
Normal 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;
|
131
ecmascript/transforms/src/macros.rs
Normal file
131
ecmascript/transforms/src/macros.rs
Normal 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()
|
||||
}
|
||||
}
|
79
ecmascript/transforms/src/quote.rs
Normal file
79
ecmascript/transforms/src/quote.rs
Normal 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),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
50
ecmascript/transforms/src/scope.rs
Normal file
50
ecmascript/transforms/src/scope.rs
Normal 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,
|
||||
}
|
710
ecmascript/transforms/src/simplify/expr.rs
Normal file
710
ecmascript/transforms/src/simplify/expr.rs
Normal 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 })
|
||||
}
|
||||
}
|
198
ecmascript/transforms/src/simplify/mod.rs
Normal file
198
ecmascript/transforms/src/simplify/mod.rs
Normal 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)
|
||||
// }
|
||||
// }
|
42
ecmascript/transforms/src/util/factory.rs
Normal file
42
ecmascript/transforms/src/util/factory.rs
Normal 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 {}
|
595
ecmascript/transforms/src/util/mod.rs
Normal file
595
ecmascript/transforms/src/util/mod.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
97
ecmascript/transforms/src/util/value.rs
Normal file
97
ecmascript/transforms/src/util/value.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
1484
ecmascript/transforms/tests/simplify_expr.rs
Normal file
1484
ecmascript/transforms/tests/simplify_expr.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
use swc::Compiler;
|
||||
use swc_common::errors::{CodeMap, FilePathMapping, Handler};
|
||||
use swc_common::{errors::Handler, FilePathMapping, SourceMap};
|
||||
|
||||
fn main() {
|
||||
run().unwrap()
|
||||
@ -62,7 +62,7 @@ fn run() -> Result<(), Box<Error>> {
|
||||
.build()
|
||||
.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(
|
||||
::swc_common::errors::ColorConfig::Always,
|
||||
|
@ -19,7 +19,7 @@ use swc_ecmascript::{
|
||||
};
|
||||
|
||||
pub struct Compiler {
|
||||
codemap: Lrc<CodeMap>,
|
||||
codemap: Lrc<SourceMap>,
|
||||
threads: rayon::ThreadPool,
|
||||
logger: Logger,
|
||||
handler: Handler,
|
||||
@ -28,7 +28,7 @@ pub struct Compiler {
|
||||
impl Compiler {
|
||||
pub fn new(
|
||||
logger: Logger,
|
||||
codemap: Lrc<SourceMapperDyn>,
|
||||
codemap: Lrc<SourceMap>,
|
||||
handler: Handler,
|
||||
threads: rayon::ThreadPool,
|
||||
) -> Self {
|
||||
@ -42,10 +42,7 @@ impl Compiler {
|
||||
|
||||
/// TODO
|
||||
pub fn parse_js(&self, path: &Path) -> PResult<Module> {
|
||||
let fm = self
|
||||
.codemap
|
||||
.load_file_and_lines(path)
|
||||
.expect("failed to load file");
|
||||
let fm = self.codemap.load_file(path).expect("failed to load file");
|
||||
|
||||
Parser::new(
|
||||
ParseSess {
|
||||
|
Loading…
Reference in New Issue
Block a user