Fix codegen: preserve input (#1221)

swc_ecma_codegen:
 - Preserve input. (#1204)
This commit is contained in:
강동윤 2020-11-21 02:04:29 +09:00 committed by GitHub
parent 4294b5e7ba
commit 6888c69bda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 372 additions and 96 deletions

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.14.1"
version = "0.14.2"
[features]
codegen = ["swc_ecma_codegen"]
@ -21,7 +21,7 @@ react = ["swc_ecma_transforms", "swc_ecma_transforms/react"]
[dependencies]
swc_ecma_ast = {version = "0.35.0", path = "./ast"}
swc_ecma_codegen = {version = "0.41.0", path = "./codegen", optional = true}
swc_ecma_codegen = {version = "0.41.1", path = "./codegen", optional = true}
swc_ecma_dep_graph = {version = "0.9.0", path = "./dep-graph", optional = true}
swc_ecma_parser = {version = "0.43.0", path = "./parser", optional = true}
swc_ecma_transforms = {version = "0.30.1", path = "./transforms", optional = true}

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.41.0"
version = "0.41.1"
[dependencies]
bitflags = "1"

View File

@ -382,19 +382,17 @@ impl<'a> Emitter<'a> {
fn emit_str_lit(&mut self, node: &Str) -> Result {
self.emit_leading_comments_of_pos(node.span().lo())?;
let single_quote = if let Ok(s) = self.cm.span_to_snippet(node.span) {
s.starts_with("'")
} else {
true
};
let single_quote = is_single_quote(&self.cm, node.span);
// if let Some(s) = get_text_of_node(&self.cm, node, false) {
// self.wr.write_str_lit(node.span, &s)?;
// return Ok(());
// }
let value = escape(&node.value);
let value = escape(&self.cm, node.span, &node.value, single_quote);
// let value = node.value.replace("\n", "\\n");
let single_quote = single_quote.unwrap_or(false);
if single_quote {
punct!("'");
self.wr.write_str_lit(node.span, &value)?;
@ -2414,33 +2412,151 @@ fn unescape(s: &str) -> String {
result
}
fn escape(s: &str) -> Cow<str> {
// let patterns = &[
// "\\", "\u{0008}", "\u{000C}", "\n", "\r", "\t", "\u{000B}", "\00", "\01",
// "\02", "\03", "\04", "\05", "\06", "\07", "\08", "\09", "\0",
// ];
// let replace_with = &[
// "\\\\", "\\b", "\\f", "\\n", "\\r", "\\t", "\\v", "\\x000", "\\x001",
// "\\x002", "\\x003", "\\x004", "\\x005", "\\x006", "\\x007", "\\x008",
// "\\x009", "\\0", ];
fn escape<'s>(cm: &SourceMap, span: Span, s: &'s str, single_quote: Option<bool>) -> Cow<'s, str> {
if span.is_dummy() {
return Cow::Owned(s.escape_default().to_string());
}
//
// {
// let mut found = false;
// for pat in patterns {
// if s.contains(pat) {
// found = true;
// break;
// }
// }
// if !found {
// return Cow::Borrowed(s);
// }
// }
//
// let ac = AhoCorasick::new(patterns);
//
// Cow::Owned(ac.replace_all(s, replace_with))
Cow::Owned(s.escape_default().to_string())
let orig = cm.span_to_snippet(span);
let orig = match orig {
Ok(orig) => orig,
Err(v) => {
return Cow::Owned(s.escape_default().to_string());
}
};
if orig.len() <= 2 {
return Cow::Owned(s.escape_default().to_string());
}
let mut orig = &*orig;
if (single_quote == Some(true) && orig.starts_with('\''))
|| (single_quote == Some(false) && orig.starts_with('"'))
{
orig = &orig[1..orig.len() - 1];
} else {
return Cow::Owned(s.escape_default().to_string());
}
let mut buf = String::with_capacity(s.len());
let mut orig_iter = orig.chars().peekable();
let mut s_iter = s.chars();
while let Some(orig_c) = orig_iter.next() {
if orig_c == '\\' {
buf.push('\\');
match orig_iter.next() {
Some('\\') => {
buf.push('\\');
s_iter.next();
continue;
}
Some(escaper) => {
buf.push(escaper);
match escaper {
'x' => {
buf.extend(orig_iter.next());
buf.extend(orig_iter.next());
s_iter.next();
}
'u' => match orig_iter.next() {
Some('{') => {
loop {
if orig_iter.next() == Some('}') {
break;
}
}
s_iter.next();
}
Some(ch) => {
buf.push(ch);
buf.extend(orig_iter.next());
buf.extend(orig_iter.next());
buf.extend(orig_iter.next());
s_iter.next();
}
None => break,
},
'b' | 'f' | 'n' | 'r' | 't' | 'v' | '0' => {
s_iter.next();
}
'\'' if single_quote == Some(true) => {
s_iter.next();
}
'"' if single_quote == Some(false) => {
s_iter.next();
}
_ => {
buf.extend(s_iter.next());
}
}
continue;
}
_ => {}
}
}
s_iter.next();
buf.push(orig_c);
}
buf.extend(s_iter);
Cow::Owned(buf)
}
/// Returns [Some] if the span points to a string literal written by user.
///
/// Returns [None] if the span is created from a pass of swc. For example,
/// spans of string literals created from [TplElement] do not have `starting`
/// quote.
fn is_single_quote(cm: &SourceMap, span: Span) -> Option<bool> {
// This is a workaround for TplElement issue.
if span.ctxt != SyntaxContext::empty() {
return None;
}
let start = cm.lookup_byte_offset(span.lo);
let end = cm.lookup_byte_offset(span.hi);
if start.sf.start_pos != end.sf.start_pos {
return None;
}
// Empty file
if start.sf.start_pos == start.sf.end_pos {
return None;
}
let start_index = start.pos.0;
let end_index = end.pos.0;
let source_len = (start.sf.end_pos - start.sf.start_pos).0;
if start_index > end_index || end_index > source_len {
return None;
}
let src = &start.sf.src;
let single_quote = match src.as_bytes()[start_index as usize] {
b'\'' => true,
b'"' => false,
_ => return None,
};
if end_index == 0 {
return None;
}
if src.as_bytes()[start_index as usize] != src.as_bytes()[(end_index - 1) as usize] {
return None;
}
Some(single_quote)
}
#[cold]

View File

@ -167,10 +167,10 @@ fn no_octal_escape() {
'\x000';
'\x001';
'\x009'"#,
r#"'\u{0}a';
'\u{0}0';
'\u{0}1';
'\u{0}9';"#,
r#"'\x00a';
'\x000';
'\x001';
'\x009';"#,
);
}
@ -432,6 +432,44 @@ fn jsx_1() {
);
}
#[test]
fn deno_8162() {
test_from_to(
r#""\x00\r\n\x85\u2028\u2029";"#,
r#""\x00\r\n\x85\u2028\u2029";"#,
);
}
#[test]
fn integration_01() {
test_from_to(
r#"
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
"#,
"
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + `\"${unexpectedKeys.join('\", \
\"')}\" found in ${argumentName}. ` + `Expected to find one of the known reducer keys \
instead: ` + `\"${reducerKeys.join('\", \"')}\". Unexpected keys will be ignored.`;
",
);
}
#[test]
fn integration_01_reduced_01() {
test_from_to(
r#"
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. `
"#,
"
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` + `\"${unexpectedKeys.join('\", \
\"')}\" found in ${argumentName}. `;",
);
}
#[derive(Debug, Clone)]
struct Buf(Arc<RwLock<Vec<u8>>>);
impl Write for Buf {

View File

@ -1,2 +1,2 @@
'use strict';
'use\x20strict';
with (a)b = c;

View File

@ -1 +1 @@
"a";
"\u0061";

View File

@ -1 +1 @@
'\u{0}\u{1}\u{2}\u{3}\u{4}\u{5}\u{6}\u{7}\u{8}\t\n\u{b}\u{c}\r\u{e}\u{f}\u{10}\u{11}\u{12}\u{13}\u{14}\u{15}\u{16}\u{17}\u{18}\u{19}\u{1a}\u{1b}\u{1c}\u{1d}\u{1e}\u{1f} !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{7f}\u{80}\u{81}\u{82}\u{83}\u{84}\u{85}\u{86}\u{87}\u{88}\u{89}\u{8a}\u{8b}\u{8c}\u{8d}\u{8e}\u{8f}\u{90}\u{91}\u{92}\u{93}\u{94}\u{95}\u{96}\u{97}\u{98}\u{99}\u{9a}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}\u{a0}\u{a1}\u{a2}\u{a3}\u{a4}\u{a5}\u{a6}\u{a7}\u{a8}\u{a9}\u{aa}\u{ab}\u{ac}\u{ad}\u{ae}\u{af}\u{b0}\u{b1}\u{b2}\u{b3}\u{b4}\u{b5}\u{b6}\u{b7}\u{b8}\u{b9}\u{ba}\u{bb}\u{bc}\u{bd}\u{be}\u{bf}\u{c0}\u{c1}\u{c2}\u{c3}\u{c4}\u{c5}\u{c6}\u{c7}\u{c8}\u{c9}\u{ca}\u{cb}\u{cc}\u{cd}\u{ce}\u{cf}\u{d0}\u{d1}\u{d2}\u{d3}\u{d4}\u{d5}\u{d6}\u{d7}\u{d8}\u{d9}\u{da}\u{db}\u{dc}\u{dd}\u{de}\u{df}\u{e0}\u{e1}\u{e2}\u{e3}\u{e4}\u{e5}\u{e6}\u{e7}\u{e8}\u{e9}\u{ea}\u{eb}\u{ec}\u{ed}\u{ee}\u{ef}\u{f0}\u{f1}\u{f2}\u{f3}\u{f4}\u{f5}\u{f6}\u{f7}\u{f8}\u{f9}\u{fa}\u{fb}\u{fc}\u{fd}\u{fe}' + a;
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe' + a;

View File

@ -1 +1 @@
"Hello\nWorld";
"Hello\012World";

View File

@ -1 +1,2 @@
"Helloworld";
"Hello\ w
world";

View File

@ -1 +1 @@
"Hello\u{ca}World";
"Hello\3Ê12World";

View File

@ -1 +1 @@
('\u{10ffff}');
('\u');

View File

@ -1 +1 @@
"Hello92World";
"Hello\7912World";

View File

@ -1 +1 @@
('I1');
('\1I111');

View File

@ -1 +1 @@
"4";
"\u";

View File

@ -1 +1 @@
('\u{1}');
('\1');

View File

@ -1,2 +1,2 @@
'use strict';
('\u{0}');
('\0');

View File

@ -1 +1,2 @@
"Helloworld";
"Hello\
wworld";

View File

@ -1,2 +1,2 @@
'use strict';
('\u{0}x');
('\0x');

View File

@ -1 +1 @@
('\u{5}a');
('\5a');

View File

@ -1 +1 @@
"Hello!2World";
"Hello\4!12World";

View File

@ -1 +1 @@
"\n\r\t\u{b}\u{8}\u{c}\\\'\"\u{0}";
"\n\r\t\v\b\f\\\''\"\0";

View File

@ -1 +1 @@
('\u{f8}');
('\u');

View File

@ -1 +1 @@
"Hello\u{0}World";
"Hello\0World";

View File

@ -1 +1 @@
('\u{202a}');
('');

View File

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

View File

@ -1,5 +1,5 @@
a["b"] = "c";
a["if"] = "if";
a["*"] = "d";
a["\u{eb3}"] = "e";
a["\u0EB3"] = "e";
a[""] = "f";

View File

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

View File

@ -1 +1,2 @@
('');
('\
');

View File

@ -1 +1 @@
"\u{714e}\u{8336}";
"\u\u";

View File

@ -1 +1 @@
"Hello\u{2}World";
"Hello\02World";

View File

@ -1,4 +1,4 @@
function a() {
'use strict';
"\u{0}";
"\0";
}

View File

@ -1 +1 @@
'a&b';
'a\u0026b';

View File

@ -1 +1 @@
('\u{0}');
('\0');

View File

@ -1,4 +1,4 @@
(function() {
'use strict';
'use\x20strict';
with (a);
}());

View File

@ -1 +1 @@
('\u{7}a');
('\7a');

View File

@ -1 +1 @@
('I');
('\1I11');

View File

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

View File

@ -1 +1,2 @@
('');
('\
');

View File

@ -1,2 +1,2 @@
"use strict";
"use\x20strict";
with (a)b = c;

View File

@ -1 +1 @@
"HelloRWorld";
"Hello\1R22World";

View File

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

View File

@ -1 +1 @@
('\u{1}');
('\01');

View File

@ -1 +1 @@
('\u{89}1');
('\2‰111');

View File

@ -1,4 +1,4 @@
(function() {
'use strict';
'use\x20strict';
with (a);
});

View File

@ -1 +1 @@
('\u{f8}');
('\u');

View File

@ -1 +1 @@
(')11');
('\5)111');

View File

@ -1 +1 @@
"Hello\n2World";
"Hello\0122World";

View File

@ -1 +1 @@
('\u{0}');
('\u');

View File

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

View File

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

View File

@ -1 +1 @@
a("\u{b}");
a("\v");

View File

@ -1 +1 @@
"\u{20bb7}\u{91ce}\u{5bb6}";
"\u\u\u";

View File

@ -1,6 +1,6 @@
a["b"] = "c";
a["if"] = "if";
a["*"] = "d";
a["\u{eb3}"] = "e";
a["\u0EB3"] = "e";
a[""] = "f";
a["1_1"] = "b";

View File

@ -1 +1 @@
"Hello\u{1}World";
"Hello\1World";

View File

@ -1 +1 @@
"a";
"\x61";

View File

@ -1,17 +1,23 @@
use crate::util::{is_literal, prepend_stmts, ExprFactory, StmtLike};
use std::{iter, mem};
use swc_atoms::js_word;
use swc_common::{BytePos, Spanned, DUMMY_SP};
use swc_common::{BytePos, Mark, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
pub fn template_literal() -> impl Fold {
TemplateLiteral::default()
TemplateLiteral {
added: Default::default(),
str_ctxt: SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())),
}
}
#[derive(Default)]
struct TemplateLiteral {
added: Vec<Stmt>,
/// Applied to [Str] created by this pass.
///
/// This is to workaround codegen issue.
str_ctxt: SyntaxContext,
}
impl Fold for TemplateLiteral {
@ -63,7 +69,9 @@ impl Fold for TemplateLiteral {
match quasis.next() {
Some(TplElement { cooked, raw, .. }) => {
Box::new(Lit::Str(cooked.unwrap_or_else(|| raw)).into())
let mut s = cooked.unwrap_or_else(|| raw);
s.span.ctxt = self.str_ctxt;
Box::new(Lit::Str(s).into())
}
_ => unreachable!(),
}

View File

@ -806,3 +806,104 @@ test!(
}
"
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_01,
"`\"`",
r#""\"""#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_02,
"`\"\"`",
r#"
"\"\""
"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_03,
"`\"${foo}`",
r#"
"\"".concat(foo);
"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_04,
"`\"${foo}\"`",
r#"
"\"".concat(foo, "\"");
"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_05,
"`\"\"${foo}\"\"`",
r#"
"\"\"".concat(foo, "\"\"");
"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_06,
"\"``\"",
"\"``\"",
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_07,
r#"`The ${argumentName} has unexpected type of "`"#,
r#""The ".concat(argumentName, " has unexpected type of \"");"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_08,
r#"`". Expected argument to be an object with the following `"#,
r#""\". Expected argument to be an object with the following ";"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_09,
r#"`keys: "${reducerKeys.join('", "')}"`"#,
r#""keys: \"".concat(reducerKeys.join('\", \"'), "\"");"#,
ok_if_code_eq
);
test!(
syntax(),
|_| tr(Default::default()),
codegen_10,
r#"`The ${argumentName} has unexpected type of "` +
matchType +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`"#,
r#""The ".concat(argumentName, " has unexpected type of \"") + matchType + "\". Expected argument to be an object with the following " + "keys: \"".concat(reducerKeys.join('", "'), "\"")"#,
ok_if_code_eq
);

View File

@ -1037,7 +1037,7 @@ class ServerRequest {
if (transferEncoding != null) {
const parts = transferEncoding.split(",").map((e)=>e.trim().toLowerCase()
);
assert3(parts.includes("chunked"), 'transfer-encoding must include \"chunked\" if content-length is not set');
assert3(parts.includes("chunked"), 'transfer-encoding must include "chunked" if content-length is not set');
this._body = chunkedBodyReader2(this.headers, this.r);
} else // Neither content-length nor transfer-encoding: chunked
this._body = emptyReader2();

View File

@ -363,7 +363,7 @@ class ServerRequest {
if (transferEncoding != null) {
const parts = transferEncoding.split(",").map((e)=>e.trim().toLowerCase()
);
assert(parts.includes("chunked"), 'transfer-encoding must include \"chunked\" if content-length is not set');
assert(parts.includes("chunked"), 'transfer-encoding must include "chunked" if content-length is not set');
this._body = chunkedBodyReader2(this.headers, this.r);
} else // Neither content-length nor transfer-encoding: chunked
this._body = emptyReader2();

View File

@ -565,3 +565,11 @@ fn issue_1203() {
assert!(!f.contains("return //"))
}
#[test]
fn codegen_1() {
let f = file("tests/projects/codegen-1/input.js").unwrap();
println!("{}", f);
assert_eq!(f.to_string(), "\"\\\"\";\n");
}

View File

@ -0,0 +1 @@
`"`