fix(es/minifier): Improve output of minifier (#1990)

swc_common:
 - Add `Span.has_mark`.

swc_ecma_codegen:
 - Emit `1e3` for `1000`.
 - Optimize output. (#1986)

swc_ecma_minifier:
 - name mangler: Don't use keywords as an id.
 - `properties`: Optimize member expression with string properties.
 - `inline`: Inline some function expressions even if it's not fn-local.
 - `analyzer`: Track reassignment correctly.
 - `analyzer`: Track fn-local correctly.
 - `sequences`: Inject `void` if required.
 - `inline`: Inline function declarations correctly.
 - `sequences`: Merge expressions into test of if statements.
 - `sequences`: Reduce calls to an assigned variable.
 - Use `Marks` instead of `&dyn Comments`.


swc_ecma_transforms_optimization:
 - `expr_simplifier`: Fix infinite loops.

node/swc:
 - Ensure that `.transform` performs minification. (#1989)
This commit is contained in:
강동윤 2021-08-04 00:52:47 +09:00 committed by GitHub
parent e916b35dd2
commit f44e25c3af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4347 changed files with 11501 additions and 2254 deletions

39
Cargo.lock generated
View File

@ -1178,6 +1178,7 @@ dependencies = [
"anyhow",
"backtrace",
"fxhash",
"log",
"napi",
"napi-build",
"napi-derive",
@ -2260,7 +2261,7 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "swc"
version = "0.34.0"
version = "0.35.0"
dependencies = [
"ahash 0.7.4",
"anyhow",
@ -2353,7 +2354,7 @@ dependencies = [
[[package]]
name = "swc_bundler"
version = "0.48.0"
version = "0.49.0"
dependencies = [
"ahash 0.7.4",
"anyhow",
@ -2390,7 +2391,7 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.11.1"
version = "0.11.2"
dependencies = [
"arbitrary",
"ast_node",
@ -2432,7 +2433,7 @@ dependencies = [
[[package]]
name = "swc_ecma_codegen"
version = "0.64.3"
version = "0.65.0"
dependencies = [
"bitflags",
"num-bigint",
@ -2502,7 +2503,7 @@ dependencies = [
[[package]]
name = "swc_ecma_minifier"
version = "0.16.1"
version = "0.17.0"
dependencies = [
"ansi_term 0.12.1",
"anyhow",
@ -2557,7 +2558,7 @@ dependencies = [
[[package]]
name = "swc_ecma_preset_env"
version = "0.31.0"
version = "0.32.0"
dependencies = [
"dashmap",
"fxhash",
@ -2583,7 +2584,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms"
version = "0.61.0"
version = "0.62.0"
dependencies = [
"pretty_assertions 0.6.1",
"sourcemap",
@ -2610,7 +2611,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_base"
version = "0.24.1"
version = "0.25.0"
dependencies = [
"fxhash",
"once_cell",
@ -2629,7 +2630,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_classes"
version = "0.10.0"
version = "0.11.0"
dependencies = [
"swc_atoms",
"swc_common",
@ -2641,7 +2642,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_compat"
version = "0.27.0"
version = "0.28.0"
dependencies = [
"arrayvec",
"fxhash",
@ -2677,7 +2678,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_module"
version = "0.28.0"
version = "0.29.0"
dependencies = [
"Inflector",
"anyhow",
@ -2700,7 +2701,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_optimization"
version = "0.31.0"
version = "0.32.0"
dependencies = [
"dashmap",
"fxhash",
@ -2727,7 +2728,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_proposal"
version = "0.28.0"
version = "0.29.0"
dependencies = [
"either",
"fxhash",
@ -2749,7 +2750,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_react"
version = "0.29.0"
version = "0.30.0"
dependencies = [
"base64 0.13.0",
"dashmap",
@ -2775,7 +2776,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_testing"
version = "0.24.0"
version = "0.25.0"
dependencies = [
"ansi_term 0.12.1",
"serde",
@ -2793,7 +2794,7 @@ dependencies = [
[[package]]
name = "swc_ecma_transforms_typescript"
version = "0.30.1"
version = "0.31.0"
dependencies = [
"fxhash",
"serde",
@ -2829,7 +2830,7 @@ dependencies = [
[[package]]
name = "swc_ecma_visit"
version = "0.35.0"
version = "0.35.1"
dependencies = [
"num-bigint",
"swc_atoms",
@ -2840,7 +2841,7 @@ dependencies = [
[[package]]
name = "swc_ecmascript"
version = "0.50.0"
version = "0.51.0"
dependencies = [
"swc_ecma_ast",
"swc_ecma_codegen",
@ -2884,7 +2885,7 @@ dependencies = [
[[package]]
name = "swc_visit"
version = "0.2.4"
version = "0.2.5"
dependencies = [
"either",
"swc_visit_macros",

View File

@ -9,7 +9,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc"
repository = "https://github.com/swc-project/swc.git"
version = "0.34.0"
version = "0.35.0"
[lib]
name = "swc"
@ -29,16 +29,16 @@ serde = {version = "1", features = ["derive"]}
serde_json = "1"
sourcemap = "6"
swc_atoms = {version = "0.2", path = "./atoms"}
swc_bundler = {version = "0.48.0", path = "./bundler"}
swc_bundler = {version = "0.49.0", path = "./bundler"}
swc_common = {version = "0.11.0", path = "./common", features = ["sourcemap", "concurrent"]}
swc_ecma_ast = {version = "0.49.0", path = "./ecmascript/ast"}
swc_ecma_codegen = {version = "0.64.0", path = "./ecmascript/codegen"}
swc_ecma_codegen = {version = "0.65.0", path = "./ecmascript/codegen"}
swc_ecma_ext_transforms = {version = "0.23.0", path = "./ecmascript/ext-transforms"}
swc_ecma_loader = {version = "0.12.0", path = "./ecmascript/loader", features = ["lru", "node", "tsc"]}
swc_ecma_minifier = {version = "0.16.0", path = "./ecmascript/minifier"}
swc_ecma_minifier = {version = "0.17.0", path = "./ecmascript/minifier"}
swc_ecma_parser = {version = "0.65.0", path = "./ecmascript/parser"}
swc_ecma_preset_env = {version = "0.31.0", path = "./ecmascript/preset-env"}
swc_ecma_transforms = {version = "0.61.0", path = "./ecmascript/transforms", features = [
swc_ecma_preset_env = {version = "0.32.0", path = "./ecmascript/preset-env"}
swc_ecma_transforms = {version = "0.62.0", path = "./ecmascript/transforms", features = [
"compat",
"module",
"optimization",
@ -46,7 +46,7 @@ swc_ecma_transforms = {version = "0.61.0", path = "./ecmascript/transforms", fea
"react",
"typescript",
]}
swc_ecma_transforms_base = {version = "0.24.1", path = "./ecmascript/transforms/base"}
swc_ecma_transforms_base = {version = "0.25.0", path = "./ecmascript/transforms/base"}
swc_ecma_utils = {version = "0.41.0", path = "./ecmascript/utils"}
swc_ecma_visit = {version = "0.35.0", path = "./ecmascript/visit"}
swc_node_base = {version = "0.2.0", path = "./node/base"}

View File

@ -9,7 +9,7 @@ include = ["Cargo.toml", "build.rs", "src/**/*.rs", "src/**/*.js"]
license = "Apache-2.0/MIT"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.48.0"
version = "0.49.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
@ -34,10 +34,10 @@ retain_mut = "0.1.2"
swc_atoms = {version = "0.2.4", path = "../atoms"}
swc_common = {version = "0.11.0", path = "../common"}
swc_ecma_ast = {version = "0.49.0", path = "../ecmascript/ast"}
swc_ecma_codegen = {version = "0.64.0", path = "../ecmascript/codegen"}
swc_ecma_codegen = {version = "0.65.0", path = "../ecmascript/codegen"}
swc_ecma_loader = {version = "0.12.0", path = "../ecmascript/loader"}
swc_ecma_parser = {version = "0.65.0", path = "../ecmascript/parser"}
swc_ecma_transforms = {version = "0.61.0", path = "../ecmascript/transforms", features = ["optimization"]}
swc_ecma_transforms = {version = "0.62.0", path = "../ecmascript/transforms", features = ["optimization"]}
swc_ecma_utils = {version = "0.41.0", path = "../ecmascript/utils"}
swc_ecma_visit = {version = "0.35.0", path = "../ecmascript/visit"}
@ -46,7 +46,7 @@ hex = "0.4"
ntest = "0.7.2"
reqwest = {version = "0.10.8", features = ["blocking"]}
sha-1 = "0.9"
swc_ecma_transforms = {version = "0.61.0", path = "../ecmascript/transforms", features = ["react", "typescript"]}
swc_ecma_transforms = {version = "0.62.0", path = "../ecmascript/transforms", features = ["react", "typescript"]}
tempfile = "3.1.0"
testing = {version = "0.12.0", path = "../testing"}
url = "2.1.1"

View File

@ -2,7 +2,7 @@ use anyhow::{Context, Error};
use crc::{crc64, crc64::Digest, Hasher64};
use std::io;
use swc_common::{sync::Lrc, SourceMap, Span};
use swc_ecma_ast::Module;
use swc_ecma_ast::{EsVersion, Module};
use swc_ecma_codegen::{text_writer::WriteJs, Emitter};
pub(crate) fn calc_hash(cm: Lrc<SourceMap>, m: &Module) -> Result<String, Error> {
@ -38,6 +38,10 @@ impl Hasher {
}
impl WriteJs for &mut Hasher {
fn target(&self) -> EsVersion {
EsVersion::latest()
}
fn increase_indent(&mut self) -> io::Result<()> {
Ok(())
}

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_common"
repository = "https://github.com/swc-project/swc.git"
version = "0.11.1"
version = "0.11.2"
[features]
concurrent = ["parking_lot"]

View File

@ -361,6 +361,34 @@ impl Span {
*self = Span::new(span.lo, span.hi, span.ctxt);
mark
}
/// Returns `true` if `self` is marked with `mark`.
///
/// Panics if `mark` is not a valid mark.
#[inline]
pub fn has_mark(self, mark: Mark) -> bool {
debug_assert_ne!(
mark,
Mark::root(),
"Cannot check if a span contains a `ROOT` mark"
);
let mut ctxt = self.ctxt;
loop {
if ctxt == SyntaxContext::empty() {
return false;
}
let m = ctxt.remove_mark();
if m == mark {
return true;
}
if m == Mark::root() {
return false;
}
}
}
}
#[derive(Clone, Debug)]

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecmascript"
repository = "https://github.com/swc-project/swc.git"
version = "0.50.0"
version = "0.51.0"
[package.metadata.docs.rs]
all-features = true
@ -32,11 +32,11 @@ typescript = ["typescript-parser", "swc_ecma_transforms/typescript"]
[dependencies]
swc_ecma_ast = {version = "0.49.0", path = "./ast"}
swc_ecma_codegen = {version = "0.64.0", path = "./codegen", optional = true}
swc_ecma_codegen = {version = "0.65.0", path = "./codegen", optional = true}
swc_ecma_dep_graph = {version = "0.33.0", path = "./dep-graph", optional = true}
swc_ecma_minifier = {version = "0.16.0", path = "./minifier", optional = true}
swc_ecma_minifier = {version = "0.17.0", path = "./minifier", optional = true}
swc_ecma_parser = {version = "0.65.0", path = "./parser", optional = true, default-features = false}
swc_ecma_transforms = {version = "0.61.0", path = "./transforms", optional = true}
swc_ecma_transforms = {version = "0.62.0", path = "./transforms", optional = true}
swc_ecma_utils = {version = "0.41.0", path = "./utils", optional = true}
swc_ecma_visit = {version = "0.35.0", path = "./visit", optional = true}

View File

@ -172,3 +172,5 @@ pub trait IdentExt: AsRef<str> {
impl IdentExt for JsWord {}
impl IdentExt for Ident {}
impl IdentExt for &'_ str {}
impl IdentExt for String {}

View File

@ -7,7 +7,7 @@ include = ["Cargo.toml", "src/**/*.rs"]
license = "Apache-2.0/MIT"
name = "swc_ecma_codegen"
repository = "https://github.com/swc-project/swc.git"
version = "0.64.3"
version = "0.65.0"
[dependencies]
bitflags = "1"

View File

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -eu
echo ''
echo ''
echo ''
echo "Comparing $1"
EXPECTED=$(terser "$1" | xargs)
ACTUAL=$(cat "$1" | xargs)
if [[ $EXPECTED == $ACTUAL || $EXPECTED == "$ACTUAL;" ]]; then
echo "PASSED";
git add "$1"
else
echo $EXPECTED
echo $ACTUAL
echo "FAILED"
fi

View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -eu
git status -u --porcelain \
| grep '?? ecmascript/codegen/tests' \
| sed -e 's!^?? ecmascript/codegen/!!' \
| xargs -L 1 ./scripts/compare-file.sh

View File

@ -81,7 +81,19 @@ impl<'a> Emitter<'a> {
let span = self.cm.span_until_char(node.span, ' ');
keyword!(span, node.kind.as_str());
}
space!();
let starts_with_ident = match node.decls.first() {
Some(VarDeclarator {
name: Pat::Array(..) | Pat::Rest(..) | Pat::Object(..),
..
}) => false,
_ => true,
};
if starts_with_ident {
space!();
} else {
formatting_space!();
}
self.emit_list(
node.span(),

View File

@ -1,6 +1,8 @@
#![recursion_limit = "1024"]
#![allow(unused_variables)]
use crate::util::EndsWithAlphaNum;
pub use self::config::Config;
use self::{
list::ListFormat,
@ -132,8 +134,15 @@ impl<'a> Emitter<'a> {
keyword!("export");
space!();
keyword!("default");
space!();
emit!(node.expr);
{
let starts_with_alpha_num = node.expr.starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
emit!(node.expr);
}
formatting_semi!();
}
@ -158,7 +167,16 @@ impl<'a> Emitter<'a> {
self.emit_leading_comments_of_span(node.span(), false)?;
keyword!("import");
space!();
let starts_with_ident = !node.specifiers.is_empty()
&& match &node.specifiers[0] {
ImportSpecifier::Default(_) => true,
_ => false,
};
if starts_with_ident {
space!();
} else {
formatting_space!();
}
let mut specifiers = vec![];
let mut emitted_default = false;
@ -182,7 +200,7 @@ impl<'a> Emitter<'a> {
assert!(node.specifiers.len() <= 2);
punct!("*");
space!();
formatting_space!();
keyword!("as");
space!();
emit!(ns.local);
@ -344,11 +362,11 @@ impl<'a> Emitter<'a> {
self.emit_leading_comments_of_span(node.span(), false)?;
keyword!("export");
space!();
formatting_space!();
punct!("*");
formatting_space!();
keyword!("from");
space!();
formatting_space!();
emit!(node.src);
formatting_semi!();
}
@ -440,7 +458,18 @@ impl<'a> Emitter<'a> {
if num.value.is_sign_negative() && num.value == 0.0 {
self.wr.write_str_lit(num.span, "-0")?;
} else {
self.wr.write_str_lit(num.span, &format!("{}", num.value))?;
let mut s = num.value.to_string();
if self.cfg.minify {
if !s.contains('.') && !s.contains('e') && s.ends_with("000") {
let cnt = s.as_bytes().iter().rev().filter(|&&v| v == b'0').count();
s.truncate(s.len() - cnt);
s.push('e');
s.push_str(&cnt.to_string());
}
}
self.wr.write_str_lit(num.span, &s)?;
}
}
}
@ -589,7 +618,14 @@ impl<'a> Emitter<'a> {
let span = self.cm.span_until_char(node.span, ' ');
keyword!(span, "new");
}
space!();
let starts_with_alpha_num = node.callee.starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
emit!(node.callee);
if let Some(type_args) = &node.type_args {
@ -745,16 +781,27 @@ impl<'a> Emitter<'a> {
fn emit_bin_expr_trailing(&mut self, node: &BinExpr) -> Result {
// let indent_before_op = needs_indention(node, &node.left, node.op);
// let indent_after_op = needs_indention(node, node.op, &node.right);
let need_space = match node.op {
let is_kwd_op = match node.op {
op!("in") | op!("instanceof") => true,
_ => false,
};
let need_pre_space = need_space
|| match *node.left {
Expr::Update(UpdateExpr { prefix: false, .. }) => true,
_ => false,
};
let need_pre_space = if self.cfg.minify {
if is_kwd_op {
node.left.ends_with_alpha_num()
} else {
match *node.left {
Expr::Update(UpdateExpr { prefix: false, .. }) => true,
_ => false,
}
}
} else {
is_kwd_op
|| match *node.left {
Expr::Update(UpdateExpr { prefix: false, .. }) => true,
_ => false,
}
};
if need_pre_space {
space!(self);
} else {
@ -762,11 +809,42 @@ impl<'a> Emitter<'a> {
}
operator!(self, node.op.as_str());
let need_post_space = need_space
|| match *node.right {
Expr::Unary(..) | Expr::Update(UpdateExpr { prefix: true, .. }) => true,
_ => false,
};
let need_post_space = if self.cfg.minify {
if is_kwd_op {
node.right.starts_with_alpha_num()
} else {
match (node.op, &*node.right) {
(
_,
Expr::Unary(UnaryExpr {
op: op!("typeof") | op!("void") | op!("delete"),
..
}),
) => false,
(op!("||") | op!("&&"), Expr::Unary(UnaryExpr { op: op!("!"), .. })) => false,
(op!("*") | op!("/"), Expr::Unary(..)) => false,
(
op!("||") | op!("&&"),
Expr::Unary(UnaryExpr {
op: op!(unary, "+") | op!(unary, "-") | op!("!"),
..
}),
) => false,
(_, Expr::Update(UpdateExpr { prefix: true, .. }) | Expr::Unary(..)) => true,
_ => false,
}
}
} else {
is_kwd_op
|| match *node.right {
Expr::Unary(..) | Expr::Update(UpdateExpr { prefix: true, .. }) => true,
_ => false,
}
};
if need_post_space {
space!(self);
} else {
@ -844,7 +922,17 @@ impl<'a> Emitter<'a> {
if node.super_class.is_some() {
space!();
keyword!("extends");
space!();
{
let starts_with_alpha_num =
node.super_class.as_ref().unwrap().starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!()
}
}
emit!(node.super_class);
}
@ -923,7 +1011,26 @@ impl<'a> Emitter<'a> {
if n.is_static {
keyword!("static");
space!();
let starts_with_alpha_num = match n.kind {
MethodKind::Method => {
if n.function.is_async {
true
} else if n.function.is_generator {
false
} else {
n.key.starts_with_alpha_num()
}
}
MethodKind::Getter => true,
MethodKind::Setter => true,
};
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
}
match n.kind {
MethodKind::Method => {
@ -939,13 +1046,23 @@ impl<'a> Emitter<'a> {
}
MethodKind::Getter => {
keyword!("get");
space!();
if n.key.starts_with_alpha_num() {
space!();
} else {
formatting_space!()
}
emit!(n.key);
}
MethodKind::Setter => {
keyword!("set");
space!();
if n.key.starts_with_alpha_num() {
space!();
} else {
formatting_space!()
}
emit!(n.key);
}
@ -1286,7 +1403,7 @@ impl<'a> Emitter<'a> {
}
if let Some(ref arg) = node.arg {
if arg.starts_with_alpha_num() {
if !node.delegate && arg.starts_with_alpha_num() {
space!()
} else {
formatting_space!()
@ -1350,7 +1467,7 @@ impl<'a> Emitter<'a> {
self.emit_list(
node.span(),
Some(&node.props),
ListFormat::ObjectLiteralExpressionProperties,
ListFormat::ObjectLiteralExpressionProperties | ListFormat::CanSkipTrailingComma,
)?;
if !self.cfg.minify {
self.wr.write_line()?;
@ -1394,9 +1511,18 @@ impl<'a> Emitter<'a> {
self.emit_leading_comments_of_span(node.span(), false)?;
keyword!("get");
space!();
let starts_with_alpha_num = match node.key {
PropName::Str(_) | PropName::Computed(_) => false,
_ => true,
};
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
emit!(node.key);
space!();
formatting_space!();
punct!("(");
punct!(")");
formatting_space!();
@ -1408,9 +1534,20 @@ impl<'a> Emitter<'a> {
self.emit_leading_comments_of_span(node.span(), false)?;
keyword!("set");
space!();
let starts_with_alpha_num = match node.key {
PropName::Str(_) | PropName::Computed(_) => false,
_ => true,
};
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
emit!(node.key);
space!();
formatting_space!();
punct!("(");
emit!(node.param);
@ -1663,8 +1800,10 @@ impl<'a> Emitter<'a> {
};
if has_trailing_comma && format.contains(ListFormat::CommaDelimited) {
punct!(self, ",");
formatting_space!(self);
if !self.cfg.minify || !format.contains(ListFormat::CanSkipTrailingComma) {
punct!(self, ",");
formatting_space!(self);
}
}
{
@ -1843,7 +1982,11 @@ impl<'a> Emitter<'a> {
};
punct!("{");
self.emit_list(node.span(), Some(&node.props), format)?;
self.emit_list(
node.span(),
Some(&node.props),
format | ListFormat::CanSkipTrailingComma,
)?;
punct!("}");
if node.optional {
punct!("?");
@ -1873,7 +2016,7 @@ impl<'a> Emitter<'a> {
punct!(":");
formatting_space!();
emit!(node.value);
space!();
formatting_space!();
}
#[emitter]
@ -1885,7 +2028,7 @@ impl<'a> Emitter<'a> {
if let Some(ref value) = node.value {
punct!("=");
emit!(node.value);
space!();
formatting_space!();
}
}
@ -2014,7 +2157,11 @@ impl<'a> Emitter<'a> {
if need_paren {
punct!("(");
} else {
space!();
if arg.starts_with_alpha_num() {
space!();
} else {
formatting_space!();
}
}
emit!(arg);
@ -2120,7 +2267,7 @@ impl<'a> Emitter<'a> {
punct!(")");
}
space!();
formatting_space!();
emit!(node.body);
}
@ -2131,7 +2278,15 @@ impl<'a> Emitter<'a> {
if let Some(ref test) = node.test {
keyword!("case");
space!();
let starts_with_alpha_num = test.starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!();
}
emit!(test);
} else {
keyword!("default");
@ -2164,8 +2319,15 @@ impl<'a> Emitter<'a> {
let throw_span = self.cm.span_until_char(node.span, ' ');
keyword!(throw_span, "throw");
space!();
emit!(node.arg);
{
if node.arg.starts_with_alpha_num() {
space!();
} else {
formatting_space!();
}
emit!(node.arg);
}
formatting_semi!();
}
@ -2247,10 +2409,25 @@ impl<'a> Emitter<'a> {
keyword!("for");
punct!("(");
emit!(node.left);
space!();
if node.left.ends_with_alpha_num() {
space!();
} else {
formatting_space!();
}
keyword!("in");
space!();
emit!(node.right);
{
let starts_with_alpha_num = node.right.starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!()
}
emit!(node.right);
}
punct!(")");
emit!(node.body);
@ -2268,10 +2445,23 @@ impl<'a> Emitter<'a> {
formatting_space!();
punct!("(");
emit!(node.left);
space!();
if node.left.ends_with_alpha_num() {
space!();
} else {
formatting_space!();
}
keyword!("of");
space!();
emit!(node.right);
{
let starts_with_alpha_num = node.right.starts_with_alpha_num();
if starts_with_alpha_num {
space!();
} else {
formatting_space!()
}
emit!(node.right);
}
punct!(")");
emit!(node.body);
}

View File

@ -79,6 +79,10 @@ add_bitflags!(
NoSpaceIfEmpty: 1 << 18,
SingleElement: 1 << 19,
},
// Optimization.
Values {
CanSkipTrailingComma: 1 << 20
},
/// Precomputed Formats
Values {
Modifiers: SingleLine | SpaceBetweenSiblings | NoInterveningComments,

View File

@ -594,7 +594,7 @@ fn issue_1619_1() {
assert_min_target(
"\"\\x00\" + \"\\x31\"",
"\"\\x00\"+\"\\x31\"",
EsVersion::Es3,
EsVersion::latest(),
);
}

View File

@ -20,9 +20,7 @@ pub trait WriteJs {
///
/// Implementor **should return same value** regardless how much time it is
/// called.
fn target(&self) -> JscTarget {
JscTarget::Es2020
}
fn target(&self) -> JscTarget;
fn increase_indent(&mut self) -> Result;
fn decrease_indent(&mut self) -> Result;

View File

@ -55,9 +55,26 @@ impl<W: WriteJs> WriteJs for OmitTrailingSemi<W> {
with_semi!(write_symbol(span: Span, s: &str));
fn write_punct(&mut self, span: Option<Span>, s: &'static str) -> Result {
self.pending_semi = false;
match s {
"\"" | "'" => {
self.commit_pending_semi()?;
}
"{" | "(" => {
self.commit_pending_semi()?;
}
_ => {
self.pending_semi = false;
}
}
Ok(self.inner.write_punct(span, s)?)
}
fn target(&self) -> swc_ecma_ast::EsVersion {
self.inner.target()
}
}
impl<W: WriteJs> OmitTrailingSemi<W> {

View File

@ -151,11 +151,70 @@ impl SourceMapperExt for Rc<SourceMap> {
}
}
pub trait EndsWithAlphaNum {
fn ends_with_alpha_num(&self) -> bool;
}
impl EndsWithAlphaNum for VarDeclOrPat {
fn ends_with_alpha_num(&self) -> bool {
match self {
VarDeclOrPat::VarDecl(n) => n.ends_with_alpha_num(),
VarDeclOrPat::Pat(n) => n.ends_with_alpha_num(),
}
}
}
impl EndsWithAlphaNum for Pat {
fn ends_with_alpha_num(&self) -> bool {
match self {
Pat::Object(_) | Pat::Array(_) => false,
Pat::Rest(p) => p.arg.ends_with_alpha_num(),
Pat::Assign(p) => p.right.ends_with_alpha_num(),
Pat::Expr(p) => p.ends_with_alpha_num(),
_ => true,
}
}
}
impl EndsWithAlphaNum for VarDecl {
fn ends_with_alpha_num(&self) -> bool {
match self.decls.last() {
None => true,
Some(d) => match d.init.as_deref() {
Some(e) => e.ends_with_alpha_num(),
None => d.name.ends_with_alpha_num(),
},
}
}
}
impl EndsWithAlphaNum for Expr {
fn ends_with_alpha_num(&self) -> bool {
match self {
Expr::Array(..)
| Expr::Object(..)
| Expr::Lit(Lit::Str(..))
| Expr::Paren(..)
| Expr::Member(MemberExpr { computed: true, .. }) => false,
_ => true,
}
}
}
/// Leftmost recursion
pub trait StartsWithAlphaNum {
fn starts_with_alpha_num(&self) -> bool;
}
impl StartsWithAlphaNum for PropName {
fn starts_with_alpha_num(&self) -> bool {
match self {
PropName::Str(_) | PropName::Computed(_) => false,
PropName::Ident(_) | PropName::Num(_) | PropName::BigInt(_) => true,
}
}
}
impl StartsWithAlphaNum for Expr {
fn starts_with_alpha_num(&self) -> bool {
match *self {

View File

@ -1,18 +1,26 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use swc_common::input::SourceFileInput;
use swc_ecma_ast::EsVersion;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_codegen::{
text_writer::{JsWriter, WriteJs},
Emitter,
};
use swc_ecma_parser::{lexer::Lexer, Parser, Syntax};
use testing::{run_test2, NormalizedOutput};
#[testing::fixture("tests/fixture/**/input.ts")]
fn test_fixture(input: PathBuf) {
fn run(input: &Path, minify: bool) {
let dir = input.parent().unwrap();
let output = dir.join(format!(
"output.{}",
input.extension().unwrap().to_string_lossy()
));
let output = if minify {
dir.join(format!(
"output.min.{}",
input.extension().unwrap().to_string_lossy()
))
} else {
dir.join(format!(
"output.{}",
input.extension().unwrap().to_string_lossy()
))
};
run_test2(false, |cm, _| {
let fm = cm.load_file(&input).unwrap();
@ -31,11 +39,18 @@ fn test_fixture(input: PathBuf) {
let mut buf = vec![];
{
let mut wr =
Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)) as Box<dyn WriteJs>;
if minify {
wr = Box::new(swc_ecma_codegen::text_writer::omit_trailing_semi(wr));
}
let mut emitter = Emitter {
cfg: swc_ecma_codegen::Config { minify: false },
cfg: swc_ecma_codegen::Config { minify },
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)),
wr,
};
emitter.emit_module(&m).unwrap();
@ -49,3 +64,14 @@ fn test_fixture(input: PathBuf) {
})
.unwrap();
}
#[testing::fixture("tests/fixture/**/input.ts")]
fn ts(input: PathBuf) {
run(&input, false);
}
#[testing::fixture("tests/fixture/**/input.js")]
fn js(input: PathBuf) {
run(&input, false);
run(&input, true);
}

View File

@ -0,0 +1,6 @@
import other from 'other'
const [foo] = other
export var __N_SSG = true
export default function Home() {
return __jsx('div', null)
}

View File

@ -0,0 +1,6 @@
import other from 'other';
const [foo] = other;
export var __N_SSG = true;
export default function Home() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
import other from'other';const[foo]=other;export var __N_SSG=true;export default function Home(){return __jsx('div',null)}

View File

@ -0,0 +1,6 @@
import other from 'other'
const { a, cat: bar } = other
export var __N_SSG = true
export default function Home() {
return __jsx('div', null)
}

View File

@ -0,0 +1,6 @@
import other from 'other';
const { a , cat: bar } = other;
export var __N_SSG = true;
export default function Home() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
import other from'other';const{a,cat:bar}=other;export var __N_SSG=true;export default function Home(){return __jsx('div',null)}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true
export class MyClass {}
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,6 @@
export var __N_SSG = true;
export class MyClass {
}
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export class MyClass{}export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,11 @@
function Function1() {
return {
a: function bug(a) {
return 2
},
}
}
function Function2() {
var bug = 1
return { bug }
}

View File

@ -0,0 +1,13 @@
function Function1() {
return {
a: function bug(a) {
return 2;
}
};
}
function Function2() {
var bug = 1;
return {
bug
};
}

View File

@ -0,0 +1 @@
function Function1(){return{a:function bug(a){return 2}}}function Function2(){var bug=1;return{bug}}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true
export function Noop() {}
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,6 @@
export var __N_SSG = true;
export function Noop() {
}
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export function Noop(){}export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true
export const foo = 2
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true;
export const foo = 2;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export const foo=2;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true
export { foo, bar as baz } from '.'
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,5 @@
export var __N_SSG = true;
export { foo, bar as baz } from '.';
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export{foo,bar as baz}from'.';export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,10 @@
import keep_me from 'hello'
import { keep_me2 } from 'hello2'
import * as keep_me3 from 'hello3'
import { but_not_me } from 'bar'
var leave_me_alone = 1
function dont_bug_me_either() {}
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,11 @@
import keep_me from 'hello';
import { keep_me2 } from 'hello2';
import * as keep_me3 from 'hello3';
import { but_not_me } from 'bar';
var leave_me_alone = 1;
function dont_bug_me_either() {
}
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
import keep_me from'hello';import{keep_me2}from'hello2';import*as keep_me3 from'hello3';import{but_not_me}from'bar';var leave_me_alone=1;function dont_bug_me_either(){}export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,5 @@
const a = 2
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,5 @@
const a = 2;
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
const a=2;export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Test() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Test() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Test(){return __jsx('div',null)}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true
export default function Home() {
return __jsx('div', null)
}

View File

@ -0,0 +1,4 @@
export var __N_SSG = true;
export default function Home() {
return __jsx('div', null);
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default function Home(){return __jsx('div',null)}

View File

@ -0,0 +1,7 @@
class Test extends React.Component {
render() {
return __jsx('div', null)
}
}
export var __N_SSG = true
export default Test

View File

@ -0,0 +1,7 @@
class Test extends React.Component {
render() {
return __jsx('div', null);
}
}
export var __N_SSG = true;
export default Test;

View File

@ -0,0 +1 @@
class Test extends React.Component{render(){return __jsx('div',null)}}export var __N_SSG=true;export default Test

View File

@ -0,0 +1,6 @@
export var __N_SSG = true
export default class Test extends React.Component {
render() {
return __jsx('div', null)
}
}

View File

@ -0,0 +1,6 @@
export var __N_SSG = true;
export default class Test extends React.Component {
render() {
return __jsx('div', null);
}
};

View File

@ -0,0 +1 @@
export var __N_SSG=true;export default class Test extends React.Component{render(){return __jsx('div',null)}}

View File

@ -0,0 +1,8 @@
class El extends React.Component {
render() {
return __jsx('div', null)
}
}
const a = 5
export var __N_SSG = true
export { El as default, a }

View File

@ -0,0 +1,8 @@
class El extends React.Component {
render() {
return __jsx('div', null);
}
}
const a = 5;
export var __N_SSG = true;
export { El as default, a };

View File

@ -0,0 +1 @@
class El extends React.Component{render(){return __jsx('div',null)}}const a=5;export var __N_SSG=true;export{El as default,a}

View File

@ -0,0 +1,6 @@
function El() {
return __jsx('div', null)
}
const a = 5
export var __N_SSG = true
export { El as default, a }

View File

@ -0,0 +1,6 @@
function El() {
return __jsx('div', null);
}
const a = 5;
export var __N_SSG = true;
export { El as default, a };

View File

@ -0,0 +1 @@
function El(){return __jsx('div',null)}const a=5;export var __N_SSG=true;export{El as default,a}

View File

@ -0,0 +1,2 @@
export var __N_SSG = true
export { default } from 'a'

View File

@ -0,0 +1,2 @@
export var __N_SSG = true;
export { default } from 'a';

View File

@ -0,0 +1 @@
export var __N_SSG=true;export{default}from'a'

View File

@ -0,0 +1,5 @@
function El() {
return __jsx('div', null)
}
export var __N_SSG = true
export { El as default }

View File

@ -0,0 +1,5 @@
function El() {
return __jsx('div', null);
}
export var __N_SSG = true;
export { El as default };

View File

@ -0,0 +1 @@
function El(){return __jsx('div',null)}export var __N_SSG=true;export{El as default}

View File

@ -0,0 +1,4 @@
foo
if (foo) {
}

View File

@ -0,0 +1,3 @@
foo;
if (foo) {
}

View File

@ -0,0 +1 @@
foo;if(foo){}

View File

@ -0,0 +1,2 @@
foo
"s"

View File

@ -0,0 +1,2 @@
foo;
"s";

View File

@ -0,0 +1 @@
foo;"s"

View File

@ -0,0 +1,2 @@
foo
bar

View File

@ -0,0 +1,2 @@
foo;
bar;

View File

@ -0,0 +1 @@
foo;bar

Some files were not shown because too many files have changed in this diff Show More