fix: readonly is not stripped from private prop (#916)

swc_ecma_codegen:
 - Fix printing logic of literals and template literals
 - Fix printing of hex

swc_ecma_parser:
 - Track raw string correctly

swc_ecma_transforms:
 - typescript: Handle class properties (readonly)
 - typescript: Handle enums inside function

Co-authored-by: 강동윤 <kdy1997.dev@gmail.com>
This commit is contained in:
Bartek Iwańczuk 2020-07-30 15:40:43 +02:00 committed by GitHub
parent a74eace7e2
commit 9cb32cbb75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 459 additions and 65 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_codegen"
version = "0.29.1"
version = "0.29.2"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -2298,7 +2298,13 @@ fn unescape(s: &str) -> String {
8 => write!(buf, "\\o{:o}", v).unwrap(),
16 => write!(buf, "\\x{:x}", v).unwrap(),
16 => {
if v < 16 {
write!(buf, "\\x0{:x}", v).unwrap()
} else {
write!(buf, "\\x{:x}", v).unwrap()
}
}
_ => unreachable!(),
}
@ -2308,14 +2314,25 @@ fn unescape(s: &str) -> String {
}
}
let s = s.replace("\\\\", "\\");
let mut result = String::with_capacity(s.len() * 6 / 5);
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c != '\\' {
result.push(c);
match c {
'\r' => {
result.push_str("\\r");
}
'\n' => {
result.push_str("\\n");
}
// TODO: Handle all escapes
_ => {
result.push(c);
}
}
continue;
}
@ -2324,30 +2341,32 @@ fn unescape(s: &str) -> String {
// This is wrong, but it seems like a mistake made by user.
result.push('\\');
}
Some(c) => match c {
'\\' => result.push('\\'),
'n' => result.push_str("\\\n"),
'r' => result.push_str("\\\r"),
't' => result.push_str("\\\t"),
'b' => result.push_str("\\\u{0008}"),
'f' => result.push_str("\\\u{000C}"),
'v' => result.push_str("\\\u{000B}"),
'0' => match chars.next() {
Some('b') => read_escaped(2, None, &mut result, &mut chars),
Some('o') => read_escaped(8, None, &mut result, &mut chars),
Some('x') => read_escaped(16, Some(2), &mut result, &mut chars),
nc => {
// This is wrong, but it seems like a mistake made by user.
result.push('0');
result.extend(nc);
}
},
Some(c) => {
match c {
'\\' => result.push_str(r"\\"),
'n' => result.push_str("\\n"),
'r' => result.push_str("\\r"),
't' => result.push_str("\\t"),
'b' => result.push_str("\\\u{0008}"),
'f' => result.push_str("\\\u{000C}"),
'v' => result.push_str("\\\u{000B}"),
'0' => match chars.next() {
Some('b') => read_escaped(2, None, &mut result, &mut chars),
Some('o') => read_escaped(8, None, &mut result, &mut chars),
Some('x') => read_escaped(16, Some(2), &mut result, &mut chars),
nc => {
// This is wrong, but it seems like a mistake made by user.
result.push_str("\\0");
result.extend(nc);
}
},
_ => {
result.push('\\');
result.push(c);
_ => {
result.push('\\');
result.push(c);
}
}
},
}
}
}

View File

@ -88,10 +88,20 @@ pub(crate) fn assert_pretty(from: &str, to: &str) {
assert_eq!(DebugUsingDisplay(&out.trim()), DebugUsingDisplay(to),);
}
fn test_from_to(from: &str, to: &str) {
fn test_from_to(from: &str, expected: &str) {
let out = parse_then_emit(from, Default::default(), Syntax::default());
assert_eq!(DebugUsingDisplay(out.trim()), DebugUsingDisplay(to.trim()),);
dbg!(&out);
dbg!(&expected);
assert_eq!(
DebugUsingDisplay(out.trim()),
DebugUsingDisplay(expected.trim()),
);
}
fn test_identical(from: &str) {
test_from_to(from, from)
}
fn test_from_to_custom_config(from: &str, to: &str, cfg: Config, syntax: Syntax) {
@ -276,12 +286,8 @@ fn issue_450() {
\`\`\`html
<h1>It works!</h1>
\`\`\`
`)"#,
r#"console.log(`
\`\`\`html
<h1>It works!</h1>
\`\`\`
`);"#,
r#"console.log(`\n\`\`\`html\n<h1>It works!</h1>\n\`\`\`\n`);"#,
);
}
@ -327,6 +333,100 @@ fn issue_910() {
);
}
#[test]
fn tpl_1() {
test_from_to(
"`id '${id}' must be a non-empty string`;",
"`id '${id}' must be a non-empty string`;",
)
}
#[test]
fn tpl_2() {
test_from_to(
"`${Module.wrapper[0]}${script}${Module.wrapper[1]}`",
"`${Module.wrapper[0]}${script}${Module.wrapper[1]}`;",
);
}
#[test]
fn tpl_escape_1() {
test_from_to(
"`${parent.path}\x00${request}`",
"`${parent.path}\x00${request}`;",
)
}
#[test]
fn tpl_escape_2() {
test_from_to("`${arg}\0`", "`${arg}\0`;");
}
#[test]
fn tpl_escape_3() {
test_from_to(
r#"`${resolvedDevice.toLowerCase()}\\`"#,
r#"`${resolvedDevice.toLowerCase()}\\`;"#,
);
}
#[test]
fn tpl_escape_4() {
test_from_to(
r#"`\\\\${firstPart}\\${path.slice(last)}`"#,
r#"`\\\\${firstPart}\\${path.slice(last)}`;"#,
);
}
#[test]
fn tpl_escape_5() {
test_from_to(
r#"const data = text.encode(`${arg}\0`);"#,
r#"const data = text.encode(`${arg}\0`);"#,
);
}
#[test]
fn tpl_escape_6() {
let from = r#"export class MultipartReader {
newLine = encoder.encode("\r\n");
newLineDashBoundary = encoder.encode(`\r\n--${this.boundary}`);
dashBoundaryDash = encoder.encode(`--${this.boundary}--`);
}"#;
let to = r#"export class MultipartReader {
newLine = encoder.encode("\r\n");
newLineDashBoundary = encoder.encode(`\r\n--${this.boundary}`);
dashBoundaryDash = encoder.encode(`--${this.boundary}--`);
}"#;
let out = parse_then_emit(
from,
Default::default(),
Syntax::Typescript(Default::default()),
);
assert_eq!(DebugUsingDisplay(out.trim()), DebugUsingDisplay(to.trim()),);
}
#[test]
fn issue_915_1() {
test_identical(r#"relResolveCacheIdentifier = `${parent.path}\x00${request}`;"#);
}
#[test]
fn issue_915_2() {
test_identical(r#"relResolveCacheIdentifier = `${parent.path}\x00${request}`;"#);
}
#[test]
fn issue_915_3() {
test_identical(r#"encoder.encode("\\r\\n");"#);
}
#[test]
fn issue_915_4() {
test_identical(r#"`\\r\\n--${this.boundary}`;"#);
}
#[derive(Debug, Clone)]
struct Buf(Arc<RwLock<Vec<u8>>>);
impl Write for Buf {

View File

@ -1,4 +1,2 @@
`\
\ \\ \ \ \
\ \
`;
`\n\r\\ \t\ \
\ \n`;

View File

@ -1,2 +1 @@
`
`;
`\n`;

View File

@ -1,3 +1 @@
`\ \
`;
`\r\n \n`;

View File

@ -1,3 +1 @@
`
\ \
`;
`\n\r\n\r`;

View File

@ -1,3 +1 @@
`
\ \
`;
`\n\r\n`;

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_parser"
version = "0.31.1"
version = "0.31.2"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -505,20 +505,20 @@ impl<'a, I: Input> Lexer<'a, I> {
'v' => push_c_and_ret!('\u{000b}'),
'f' => push_c_and_ret!('\u{000c}'),
'\r' => {
raw.push_str("\\r");
raw.push_str("\r");
self.bump(); // remove '\r'
if self.cur() == Some('\n') {
raw.push_str("\\n");
raw.push_str("\n");
self.bump();
}
return Ok(None);
}
'\n' | '\u{2028}' | '\u{2029}' => {
match c {
'\n' => raw.push_str("\\n"),
'\u{2028}' => raw.push_str("\\u{2028}"),
'\u{2029}' => raw.push_str("\\u{2029}"),
'\n' => raw.push_str("\n"),
'\u{2028}' => raw.push_str("\u{2028}"),
'\u{2029}' => raw.push_str("\u{2029}"),
_ => unreachable!(),
}
self.bump();
@ -538,6 +538,7 @@ impl<'a, I: Input> Lexer<'a, I> {
}
// octal escape sequences
'0'..='7' => {
raw.push(c);
self.bump();
let first_c = if c == '0' {
match self.cur() {

View File

@ -1058,6 +1058,27 @@ fn issue_481() {
);
}
#[test]
fn issue_915_1() {
assert_eq!(
lex_tokens(
crate::Syntax::Es(crate::EsConfig {
..Default::default()
}),
r##"encode("\r\n")"##
),
vec![
Word(Word::Ident("encode".into())),
LParen,
Token::Str {
value: "\r\n".into(),
has_escape: true
},
RParen
]
);
}
#[bench]
fn lex_colors_js(b: &mut Bencher) {
b.bytes = include_str!("../../colors.js").len() as _;

View File

@ -0,0 +1,3 @@
export class MultipartReader {
readonly newLine = encoder.encode("\r\n");
}

View File

@ -0,0 +1,131 @@
{
"type": "Module",
"span": {
"start": 0,
"end": 79,
"ctxt": 0
},
"body": [
{
"type": "ExportDeclaration",
"span": {
"start": 0,
"end": 79,
"ctxt": 0
},
"declaration": {
"type": "ClassDeclaration",
"identifier": {
"type": "Identifier",
"span": {
"start": 13,
"end": 28,
"ctxt": 0
},
"value": "MultipartReader",
"typeAnnotation": null,
"optional": false
},
"declare": false,
"span": {
"start": 7,
"end": 79,
"ctxt": 0
},
"decorators": [],
"body": [
{
"type": "ClassProperty",
"span": {
"start": 35,
"end": 77,
"ctxt": 0
},
"key": {
"type": "Identifier",
"span": {
"start": 44,
"end": 51,
"ctxt": 0
},
"value": "newLine",
"typeAnnotation": null,
"optional": false
},
"value": {
"type": "CallExpression",
"span": {
"start": 54,
"end": 76,
"ctxt": 0
},
"callee": {
"type": "MemberExpression",
"span": {
"start": 54,
"end": 68,
"ctxt": 0
},
"object": {
"type": "Identifier",
"span": {
"start": 54,
"end": 61,
"ctxt": 0
},
"value": "encoder",
"typeAnnotation": null,
"optional": false
},
"property": {
"type": "Identifier",
"span": {
"start": 62,
"end": 68,
"ctxt": 0
},
"value": "encode",
"typeAnnotation": null,
"optional": false
},
"computed": false
},
"arguments": [
{
"spread": null,
"expression": {
"type": "StringLiteral",
"span": {
"start": 69,
"end": 75,
"ctxt": 0
},
"value": "\r\n",
"hasEscape": true
}
}
],
"typeArguments": null
},
"typeAnnotation": null,
"isStatic": false,
"decorators": [],
"computed": false,
"accessibility": null,
"isAbstract": false,
"isOptional": false,
"readonly": true,
"declare": false,
"definite": false
}
],
"superClass": null,
"isAbstract": false,
"typeParams": null,
"superTypeParams": null,
"implements": []
}
}
],
"interpreter": null
}

View File

@ -1,6 +1,6 @@
[package]
name = "swc_ecma_transforms"
version = "0.16.0"
version = "0.16.1"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -3,9 +3,12 @@ use fxhash::FxHashMap;
use swc_atoms::js_word;
use swc_common::{util::move_map::MoveMap, Span, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id};
use swc_ecma_utils::{ident::IdentLike, Id, StmtLike};
use swc_ecma_visit::{Fold, FoldWith, Node, Visit, VisitWith};
/// Value does not contain TsLit::Bool
type EnumValues = FxHashMap<Id, TsLit>;
/// Strips type annotations out.
pub fn strip() -> impl Fold {
Strip::default()
@ -126,10 +129,10 @@ impl Strip {
node
}
fn handle_enum(&mut self, e: TsEnumDecl, stmts: &mut Vec<ModuleItem>) {
/// Value does not contain TsLit::Bool
type EnumValues = FxHashMap<Id, TsLit>;
fn handle_enum<T>(&mut self, e: TsEnumDecl, stmts: &mut Vec<T>)
where
T: StmtLike,
{
/// Called only for enums.
///
/// If both of the default value and the initialization is None, this
@ -342,7 +345,7 @@ impl Strip {
_ => true,
});
stmts.push(
stmts.push(T::from_stmt(
CallExpr {
span: DUMMY_SP,
callee: FnExpr {
@ -437,9 +440,8 @@ impl Strip {
.as_arg()],
type_args: Default::default(),
}
.into_stmt()
.into(),
)
.into_stmt(),
))
}
}
@ -667,6 +669,18 @@ impl Fold for Strip {
}
}
fn fold_private_prop(&mut self, mut prop: PrivateProp) -> PrivateProp {
prop = prop.fold_children_with(self);
prop.readonly = false;
prop
}
fn fold_class_prop(&mut self, mut prop: ClassProp) -> ClassProp {
prop = prop.fold_children_with(self);
prop.readonly = false;
prop
}
fn fold_stmt(&mut self, stmt: Stmt) -> Stmt {
let stmt = stmt.fold_children_with(self);
@ -688,6 +702,58 @@ impl Fold for Strip {
}
}
fn fold_stmts(&mut self, mut orig: Vec<Stmt>) -> Vec<Stmt> {
let old = self.phase;
// First pass
self.phase = Phase::Analysis;
orig = orig.fold_children_with(self);
self.phase = Phase::DropImports;
// Second pass
let mut stmts = Vec::with_capacity(orig.len());
for item in orig {
self.was_side_effect_import = false;
match item {
Stmt::Empty(..) => continue,
Stmt::Decl(Decl::TsEnum(e)) => {
// var Foo;
// (function (Foo) {
// Foo[Foo["a"] = 0] = "a";
// })(Foo || (Foo = {}));
stmts.push(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Var,
declare: false,
decls: vec![VarDeclarator {
span: e.span,
name: Pat::Ident(e.id.clone()),
definite: false,
init: None,
}],
})));
self.handle_enum(e, &mut stmts)
}
// Strip out ts-only extensions
Stmt::Decl(Decl::Fn(FnDecl {
function: Function { body: None, .. },
..
}))
| Stmt::Decl(Decl::TsInterface(..))
| Stmt::Decl(Decl::TsModule(..))
| Stmt::Decl(Decl::TsTypeAlias(..)) => continue,
_ => stmts.push(item.fold_with(self)),
};
}
self.phase = old;
stmts
}
fn fold_ts_interface_decl(&mut self, node: TsInterfaceDecl) -> TsInterfaceDecl {
TsInterfaceDecl {
span: node.span,

View File

@ -594,3 +594,65 @@ to!(
Direction[Direction['Left'] = 3] = 'Left';
})(Direction || (Direction = {}));"
);
to!(
issue_915,
"export class Logger {
#level: LogLevels;
#handlers: BaseHandler[];
readonly #loggerName: string;
constructor(
loggerName: string,
levelName: LevelName,
options: LoggerOptions = {},
) {
this.#loggerName = loggerName;
this.#level = getLevelByName(levelName);
this.#handlers = options.handlers || [];
}
}",
"export class Logger {
#level;
#handlers;
#loggerName;
constructor(loggerName, levelName, options = {
}){
this.#loggerName = loggerName;
this.#level = getLevelByName(levelName);
this.#handlers = options.handlers || [];
}
}"
);
to!(
issue_915_2,
r#"Deno.test("[ws] WebSocket should act as asyncIterator", async () => {
enum Frames {
ping,
hello,
close,
end,
}
});"#,
r#"Deno.test("[ws] WebSocket should act as asyncIterator", async ()=>{
var Frames;
(function(Frames) {
Frames[Frames["ping"] = 0] = "ping";
Frames[Frames["hello"] = 1] = "hello";
Frames[Frames["close"] = 2] = "close";
Frames[Frames["end"] = 3] = "end";
})(Frames || (Frames = {
}));
});"#
);
to!(
issue_915_3,
r#"export class MultipartReader {
readonly newLine = encoder.encode("\r\n");
}"#,
r#"export class MultipartReader {
newLine = encoder.encode("\r\n");
}"#
);