mirror of
https://github.com/swc-project/swc.git
synced 2024-10-05 12:49:21 +03:00
Fix bugs (#949)
swc_ecma_parser: - Allow `in` in class properties (#944) - Make `delete` with optional chaining valid (#947) swc_ecma_transforms: - Add a `typescript_class_properties` pass (#930)
This commit is contained in:
parent
26f49099aa
commit
1315d58059
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "swc_ecma_parser"
|
||||
version = "0.33.1"
|
||||
version = "0.33.2"
|
||||
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
|
@ -364,6 +364,7 @@ impl Error {
|
||||
|
||||
Eof => "Unexpected eof".into(),
|
||||
|
||||
TS2703 => "The operand of a delete operator must be a property reference.".into(),
|
||||
// TODO:
|
||||
_ => format!("{:?}", kind).into(),
|
||||
};
|
||||
|
@ -750,6 +750,7 @@ impl<'a, I: Tokens> Parser<I> {
|
||||
let ctx = Context {
|
||||
in_class_prop: true,
|
||||
in_method: false,
|
||||
include_in_expr: true,
|
||||
..self.ctx()
|
||||
};
|
||||
self.with_ctx(ctx).parse_with(|p| {
|
||||
|
@ -284,8 +284,13 @@ impl<'a, I: Tokens> Parser<I> {
|
||||
_ => e,
|
||||
}
|
||||
}
|
||||
match *arg {
|
||||
match &*arg {
|
||||
Expr::Member(..) => {}
|
||||
Expr::OptChain(e)
|
||||
if match &*e.expr {
|
||||
Expr::Member(..) => true,
|
||||
_ => false,
|
||||
} => {}
|
||||
_ => self.emit_err(unwrap_paren(&arg).span(), SyntaxError::TS2703),
|
||||
}
|
||||
}
|
||||
|
3
ecmascript/parser/tests/typescript/issue-944/input.ts
Normal file
3
ecmascript/parser/tests/typescript/issue-944/input.ts
Normal file
@ -0,0 +1,3 @@
|
||||
class CTest {
|
||||
myFunc = () => "key" in {};
|
||||
}
|
109
ecmascript/parser/tests/typescript/issue-944/input.ts.json
Normal file
109
ecmascript/parser/tests/typescript/issue-944/input.ts.json
Normal file
@ -0,0 +1,109 @@
|
||||
{
|
||||
"type": "Module",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 47,
|
||||
"ctxt": 0
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "ClassDeclaration",
|
||||
"identifier": {
|
||||
"type": "Identifier",
|
||||
"span": {
|
||||
"start": 6,
|
||||
"end": 11,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "CTest",
|
||||
"typeAnnotation": null,
|
||||
"optional": false
|
||||
},
|
||||
"declare": false,
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 47,
|
||||
"ctxt": 0
|
||||
},
|
||||
"decorators": [],
|
||||
"body": [
|
||||
{
|
||||
"type": "ClassProperty",
|
||||
"span": {
|
||||
"start": 18,
|
||||
"end": 45,
|
||||
"ctxt": 0
|
||||
},
|
||||
"key": {
|
||||
"type": "Identifier",
|
||||
"span": {
|
||||
"start": 18,
|
||||
"end": 24,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "myFunc",
|
||||
"typeAnnotation": null,
|
||||
"optional": false
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrowFunctionExpression",
|
||||
"span": {
|
||||
"start": 27,
|
||||
"end": 44,
|
||||
"ctxt": 0
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"type": "BinaryExpression",
|
||||
"span": {
|
||||
"start": 33,
|
||||
"end": 44,
|
||||
"ctxt": 0
|
||||
},
|
||||
"operator": "in",
|
||||
"left": {
|
||||
"type": "StringLiteral",
|
||||
"span": {
|
||||
"start": 33,
|
||||
"end": 38,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "key",
|
||||
"hasEscape": false
|
||||
},
|
||||
"right": {
|
||||
"type": "ObjectExpression",
|
||||
"span": {
|
||||
"start": 42,
|
||||
"end": 44,
|
||||
"ctxt": 0
|
||||
},
|
||||
"properties": []
|
||||
}
|
||||
},
|
||||
"async": false,
|
||||
"generator": false,
|
||||
"typeParameters": null,
|
||||
"returnType": null
|
||||
},
|
||||
"typeAnnotation": null,
|
||||
"isStatic": false,
|
||||
"decorators": [],
|
||||
"computed": false,
|
||||
"accessibility": null,
|
||||
"isAbstract": false,
|
||||
"isOptional": false,
|
||||
"readonly": false,
|
||||
"declare": false,
|
||||
"definite": false
|
||||
}
|
||||
],
|
||||
"superClass": null,
|
||||
"isAbstract": false,
|
||||
"typeParams": null,
|
||||
"superTypeParams": null,
|
||||
"implements": []
|
||||
}
|
||||
],
|
||||
"interpreter": null
|
||||
}
|
1
ecmascript/parser/tests/typescript/issue-947/input.ts
Normal file
1
ecmascript/parser/tests/typescript/issue-947/input.ts
Normal file
@ -0,0 +1 @@
|
||||
delete obj?.myProp;
|
72
ecmascript/parser/tests/typescript/issue-947/input.ts.json
Normal file
72
ecmascript/parser/tests/typescript/issue-947/input.ts.json
Normal file
@ -0,0 +1,72 @@
|
||||
{
|
||||
"type": "Module",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 19,
|
||||
"ctxt": 0
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 19,
|
||||
"ctxt": 0
|
||||
},
|
||||
"expression": {
|
||||
"type": "UnaryExpression",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 18,
|
||||
"ctxt": 0
|
||||
},
|
||||
"operator": "delete",
|
||||
"argument": {
|
||||
"type": "OptionalChainingExpression",
|
||||
"span": {
|
||||
"start": 7,
|
||||
"end": 18,
|
||||
"ctxt": 0
|
||||
},
|
||||
"questionDotToken": {
|
||||
"start": 10,
|
||||
"end": 11,
|
||||
"ctxt": 0
|
||||
},
|
||||
"expr": {
|
||||
"type": "MemberExpression",
|
||||
"span": {
|
||||
"start": 7,
|
||||
"end": 18,
|
||||
"ctxt": 0
|
||||
},
|
||||
"object": {
|
||||
"type": "Identifier",
|
||||
"span": {
|
||||
"start": 7,
|
||||
"end": 10,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "obj",
|
||||
"typeAnnotation": null,
|
||||
"optional": false
|
||||
},
|
||||
"property": {
|
||||
"type": "Identifier",
|
||||
"span": {
|
||||
"start": 12,
|
||||
"end": 18,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "myProp",
|
||||
"typeAnnotation": null,
|
||||
"optional": false
|
||||
},
|
||||
"computed": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"interpreter": null
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
{
|
||||
"type": "Module",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 30,
|
||||
"ctxt": 0
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"type": "TryStatement",
|
||||
"span": {
|
||||
"start": 0,
|
||||
"end": 30,
|
||||
"ctxt": 0
|
||||
},
|
||||
"block": {
|
||||
"type": "BlockStatement",
|
||||
"span": {
|
||||
"start": 4,
|
||||
"end": 7,
|
||||
"ctxt": 0
|
||||
},
|
||||
"stmts": []
|
||||
},
|
||||
"handler": {
|
||||
"type": "CatchClause",
|
||||
"span": {
|
||||
"start": 8,
|
||||
"end": 30,
|
||||
"ctxt": 0
|
||||
},
|
||||
"param": {
|
||||
"type": "Identifier",
|
||||
"span": {
|
||||
"start": 15,
|
||||
"end": 16,
|
||||
"ctxt": 0
|
||||
},
|
||||
"value": "e",
|
||||
"typeAnnotation": {
|
||||
"type": "TsTypeAnnotation",
|
||||
"span": {
|
||||
"start": 16,
|
||||
"end": 25,
|
||||
"ctxt": 0
|
||||
},
|
||||
"typeAnnotation": {
|
||||
"type": "TsKeywordType",
|
||||
"span": {
|
||||
"start": 18,
|
||||
"end": 25,
|
||||
"ctxt": 0
|
||||
},
|
||||
"kind": "unknown"
|
||||
}
|
||||
},
|
||||
"optional": false
|
||||
},
|
||||
"body": {
|
||||
"type": "BlockStatement",
|
||||
"span": {
|
||||
"start": 27,
|
||||
"end": 30,
|
||||
"ctxt": 0
|
||||
},
|
||||
"stmts": []
|
||||
}
|
||||
},
|
||||
"finalizer": null
|
||||
}
|
||||
],
|
||||
"interpreter": null
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
pub use self::{
|
||||
class_properties::class_properties, nullish_coalescing::nullish_coalescing,
|
||||
class_properties::{class_properties, typescript_class_properties},
|
||||
nullish_coalescing::nullish_coalescing,
|
||||
opt_chaining::optional_chaining,
|
||||
};
|
||||
|
||||
|
@ -30,11 +30,23 @@ mod used_name;
|
||||
///
|
||||
/// We use custom helper to handle export defaul class
|
||||
pub fn class_properties() -> impl Fold {
|
||||
ClassProperties { mark: Mark::root() }
|
||||
ClassProperties {
|
||||
typescript: false,
|
||||
mark: Mark::root(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Class properties pass for the typescript.
|
||||
pub fn typescript_class_properties() -> impl Fold {
|
||||
ClassProperties {
|
||||
typescript: true,
|
||||
mark: Mark::root(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct ClassProperties {
|
||||
typescript: bool,
|
||||
mark: Mark,
|
||||
}
|
||||
|
||||
@ -329,77 +341,135 @@ impl ClassProperties {
|
||||
);
|
||||
}
|
||||
|
||||
let key = match *prop.key {
|
||||
Expr::Ident(ref i) if !prop.computed => Lit::Str(Str {
|
||||
span: i.span,
|
||||
value: i.sym.clone(),
|
||||
has_escape: false,
|
||||
})
|
||||
.as_arg(),
|
||||
Expr::Lit(ref lit) if !prop.computed => lit.clone().as_arg(),
|
||||
let key = if self.typescript {
|
||||
// `b` in
|
||||
//
|
||||
// class A {
|
||||
// b = 'foo';
|
||||
// }
|
||||
prop.key
|
||||
} else {
|
||||
match *prop.key {
|
||||
Expr::Ident(ref i) if !prop.computed => {
|
||||
Box::new(Expr::from(Lit::Str(Str {
|
||||
span: i.span,
|
||||
value: i.sym.clone(),
|
||||
has_escape: false,
|
||||
})))
|
||||
}
|
||||
Expr::Lit(ref lit) if !prop.computed => {
|
||||
Box::new(Expr::from(lit.clone()))
|
||||
}
|
||||
|
||||
_ => {
|
||||
let (ident, aliased) = if let Expr::Ident(ref i) = *prop.key {
|
||||
if used_key_names.contains(&i.sym) {
|
||||
(alias_ident_for(&prop.key, "_ref"), true)
|
||||
_ => {
|
||||
let (ident, aliased) = if let Expr::Ident(ref i) = *prop.key {
|
||||
if used_key_names.contains(&i.sym) {
|
||||
(alias_ident_for(&prop.key, "_ref"), true)
|
||||
} else {
|
||||
alias_if_required(&prop.key, "_ref")
|
||||
}
|
||||
} else {
|
||||
alias_if_required(&prop.key, "_ref")
|
||||
};
|
||||
// ident.span = ident.span.apply_mark(Mark::fresh(Mark::root()));
|
||||
if aliased {
|
||||
// Handle computed property
|
||||
vars.push(VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(ident.clone()),
|
||||
init: Some(prop.key),
|
||||
definite: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
alias_if_required(&prop.key, "_ref")
|
||||
};
|
||||
// ident.span = ident.span.apply_mark(Mark::fresh(Mark::root()));
|
||||
if aliased {
|
||||
// Handle computed property
|
||||
vars.push(VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(ident.clone()),
|
||||
init: Some(prop.key),
|
||||
definite: false,
|
||||
});
|
||||
Box::new(Expr::from(ident))
|
||||
}
|
||||
ident.as_arg()
|
||||
}
|
||||
};
|
||||
|
||||
let value = prop.value.unwrap_or_else(|| undefined(prop_span)).as_arg();
|
||||
let value = prop.value.unwrap_or_else(|| undefined(prop_span));
|
||||
let value = if prop.is_static {
|
||||
value
|
||||
.fold_with(&mut SuperFieldAccessFolder {
|
||||
class_name: &ident,
|
||||
vars: &mut vars,
|
||||
constructor_this_mark: None,
|
||||
is_static: true,
|
||||
folding_constructor: false,
|
||||
in_injected_define_property_call: false,
|
||||
in_nested_scope: false,
|
||||
this_alias_mark: None,
|
||||
})
|
||||
.fold_with(&mut ThisInStaticFolder {
|
||||
ident: ident.clone(),
|
||||
})
|
||||
} else {
|
||||
value
|
||||
};
|
||||
|
||||
let callee = helper!(define_property, "defineProperty");
|
||||
if self.typescript {
|
||||
if prop.is_static {
|
||||
extra_stmts.push(
|
||||
AssignExpr {
|
||||
span: DUMMY_SP,
|
||||
left: PatOrExpr::Expr(Box::new(
|
||||
MemberExpr {
|
||||
span: DUMMY_SP,
|
||||
obj: ident.clone().as_obj(),
|
||||
computed: false,
|
||||
prop: key,
|
||||
}
|
||||
.into(),
|
||||
)),
|
||||
op: op!("="),
|
||||
right: value,
|
||||
}
|
||||
.into_stmt(),
|
||||
);
|
||||
} else {
|
||||
constructor_exprs.push(Box::new(Expr::Assign(AssignExpr {
|
||||
span: DUMMY_SP,
|
||||
left: (PatOrExpr::Expr(Box::new(
|
||||
MemberExpr {
|
||||
span: DUMMY_SP,
|
||||
obj: ThisExpr { span: DUMMY_SP }.as_obj(),
|
||||
computed: false,
|
||||
prop: key,
|
||||
}
|
||||
.into(),
|
||||
))),
|
||||
op: op!("="),
|
||||
right: value,
|
||||
})));
|
||||
}
|
||||
} else {
|
||||
let callee = helper!(define_property, "defineProperty");
|
||||
|
||||
if prop.is_static {
|
||||
extra_stmts.push(
|
||||
CallExpr {
|
||||
if prop.is_static {
|
||||
extra_stmts.push(
|
||||
CallExpr {
|
||||
span: DUMMY_SP,
|
||||
callee,
|
||||
args: vec![
|
||||
ident.clone().as_arg(),
|
||||
key.as_arg(),
|
||||
value.as_arg(),
|
||||
],
|
||||
type_args: Default::default(),
|
||||
}
|
||||
.into_stmt(),
|
||||
)
|
||||
} else {
|
||||
constructor_exprs.push(Box::new(Expr::Call(CallExpr {
|
||||
span: DUMMY_SP,
|
||||
callee,
|
||||
args: vec![
|
||||
ident.clone().as_arg(),
|
||||
key,
|
||||
value
|
||||
.fold_with(&mut SuperFieldAccessFolder {
|
||||
class_name: &ident,
|
||||
vars: &mut vars,
|
||||
constructor_this_mark: None,
|
||||
is_static: true,
|
||||
folding_constructor: false,
|
||||
in_injected_define_property_call: false,
|
||||
in_nested_scope: false,
|
||||
this_alias_mark: None,
|
||||
})
|
||||
.fold_with(&mut ThisInStaticFolder {
|
||||
ident: ident.clone(),
|
||||
}),
|
||||
ThisExpr { span: DUMMY_SP }.as_arg(),
|
||||
key.as_arg(),
|
||||
value.as_arg(),
|
||||
],
|
||||
type_args: Default::default(),
|
||||
}
|
||||
.into_stmt(),
|
||||
)
|
||||
} else {
|
||||
constructor_exprs.push(Box::new(Expr::Call(CallExpr {
|
||||
span: DUMMY_SP,
|
||||
callee,
|
||||
args: vec![ThisExpr { span: DUMMY_SP }.as_arg(), key, value],
|
||||
type_args: Default::default(),
|
||||
})));
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
ClassMember::PrivateProp(prop) => {
|
||||
@ -524,6 +594,35 @@ impl ClassProperties {
|
||||
)
|
||||
}
|
||||
|
||||
/// # Legacy support.
|
||||
///
|
||||
/// ## Why is this required?
|
||||
///
|
||||
/// Hygiene data of
|
||||
///
|
||||
///```ts
|
||||
/// class A {
|
||||
/// b = this.a;
|
||||
/// constructor(a){
|
||||
/// this.a = a;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// is
|
||||
///
|
||||
///```ts
|
||||
/// class A0 {
|
||||
/// constructor(a1){
|
||||
/// this.a0 = a0;
|
||||
/// this.b0 = this.a0;
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// which is valid only for es2020 properties.
|
||||
///
|
||||
/// Legacy proposal which is used by typescript requires different hygiene.
|
||||
#[allow(clippy::vec_box)]
|
||||
fn process_constructor(
|
||||
&mut self,
|
||||
@ -534,28 +633,32 @@ impl ClassProperties {
|
||||
) -> Option<Constructor> {
|
||||
let constructor = constructor
|
||||
.map(|c| {
|
||||
let mut folder = UsedNameRenamer {
|
||||
mark: Mark::fresh(Mark::root()),
|
||||
used_names,
|
||||
};
|
||||
if self.typescript {
|
||||
c
|
||||
} else {
|
||||
let mut folder = UsedNameRenamer {
|
||||
mark: Mark::fresh(Mark::root()),
|
||||
used_names,
|
||||
};
|
||||
|
||||
// Handle collisions like
|
||||
//
|
||||
// var foo = "bar";
|
||||
//
|
||||
// class Foo {
|
||||
// bar = foo;
|
||||
// static bar = baz;
|
||||
//
|
||||
// constructor() {
|
||||
// var foo = "foo";
|
||||
// var baz = "baz";
|
||||
// }
|
||||
// }
|
||||
let body = c.body.fold_with(&mut folder);
|
||||
// Handle collisions like
|
||||
//
|
||||
// var foo = "bar";
|
||||
//
|
||||
// class Foo {
|
||||
// bar = foo;
|
||||
// static bar = baz;
|
||||
//
|
||||
// constructor() {
|
||||
// var foo = "foo";
|
||||
// var baz = "baz";
|
||||
// }
|
||||
// }
|
||||
let body = c.body.fold_with(&mut folder);
|
||||
|
||||
let params = c.params.fold_with(&mut folder);
|
||||
Constructor { body, params, ..c }
|
||||
let params = c.params.fold_with(&mut folder);
|
||||
Constructor { body, params, ..c }
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
if constructor_exprs.is_empty() {
|
||||
@ -565,8 +668,19 @@ impl ClassProperties {
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(c) = constructor {
|
||||
Some(inject_after_super(c, constructor_exprs))
|
||||
if let Some(mut c) = constructor {
|
||||
if self.typescript {
|
||||
// Append properties
|
||||
c.body
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.stmts
|
||||
.extend(constructor_exprs.into_iter().map(|v| v.into_stmt()));
|
||||
Some(c)
|
||||
} else {
|
||||
// Prepend properties
|
||||
Some(inject_after_super(c, constructor_exprs))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -1,15 +1,22 @@
|
||||
#![feature(test)]
|
||||
use swc_common::chain;
|
||||
use swc_ecma_transforms::{resolver, typescript::strip};
|
||||
use swc_ecma_transforms::{
|
||||
compat::es2020::typescript_class_properties, resolver, typescript::strip,
|
||||
};
|
||||
use swc_ecma_visit::Fold;
|
||||
|
||||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
fn tr() -> impl Fold {
|
||||
strip()
|
||||
}
|
||||
|
||||
macro_rules! to {
|
||||
($name:ident, $from:expr, $to:expr) => {
|
||||
test!(
|
||||
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|
||||
|_| strip(),
|
||||
|_| tr(),
|
||||
$name,
|
||||
$from,
|
||||
$to,
|
||||
@ -695,3 +702,37 @@ to!(
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
test!(
|
||||
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|
||||
|_| chain!(tr(), typescript_class_properties()),
|
||||
issue_930_instance,
|
||||
"class A {
|
||||
b = this.a;
|
||||
constructor(a){
|
||||
this.a = a;
|
||||
}
|
||||
}",
|
||||
"class A {
|
||||
constructor(a) {
|
||||
this.a = a;
|
||||
this.b = this.a;
|
||||
}
|
||||
}"
|
||||
);
|
||||
|
||||
test!(
|
||||
::swc_ecma_parser::Syntax::Typescript(Default::default()),
|
||||
|_| chain!(tr(), typescript_class_properties()),
|
||||
issue_930_static,
|
||||
"class A {
|
||||
static b = 'foo';
|
||||
constructor(a){
|
||||
}
|
||||
}",
|
||||
"class A {
|
||||
constructor(a) {
|
||||
}
|
||||
}
|
||||
A.b = 'foo';"
|
||||
);
|
||||
|
@ -144,10 +144,17 @@ impl<'a, 'b, P: swc_ecma_visit::Fold> PassBuilder<'a, 'b, P> {
|
||||
compat::es2020::optional_chaining(),
|
||||
self.target < JscTarget::Es2020
|
||||
),
|
||||
Optional::new(
|
||||
compat::es2020::class_properties(),
|
||||
self.target < JscTarget::Es2020
|
||||
),
|
||||
if syntax.typescript() {
|
||||
Either::Left(Optional::new(
|
||||
compat::es2020::typescript_class_properties(),
|
||||
self.target < JscTarget::Es2020,
|
||||
))
|
||||
} else {
|
||||
Either::Right(Optional::new(
|
||||
compat::es2020::class_properties(),
|
||||
self.target < JscTarget::Es2020,
|
||||
))
|
||||
},
|
||||
Optional::new(compat::es2018(), self.target <= JscTarget::Es2018),
|
||||
Optional::new(compat::es2017(), self.target <= JscTarget::Es2017),
|
||||
Optional::new(compat::es2016(), self.target <= JscTarget::Es2016),
|
||||
|
Loading…
Reference in New Issue
Block a user