From e196275b985822603a83991ee69ad0a614de0cfa Mon Sep 17 00:00:00 2001 From: Alessandro Chitolina Date: Sun, 5 Nov 2023 01:33:13 +0100 Subject: [PATCH] fix: various fixes, jobject construction replace new operator --- .mocha.setup.js | 4 +- index.js | 1 + lib/_apply_decs_2203_r.js | 2 +- lib/_construct_jobject.js | 104 ++++++++ lib/reflection.js | 64 +++-- package.json | 5 +- src/parser/mod.rs | 4 +- src/parser/program.rs | 251 +++++++++++++----- .../transformers/class_define_fields.rs | 171 ------------ .../class_reflection_decorators.rs | 89 ++++++- src/parser/transformers/decorator_2022_03.rs | 5 + .../transformers/lazy_object_construction.rs | 42 +++ src/parser/transformers/mod.rs | 4 +- .../transformers/resolve_self_identifiers.rs | 11 - src/parser/transformers/wrap_in_function.rs | 82 +++++- src/stack/trace.rs | 20 +- src/testing/mod.rs | 34 ++- src/wasm/compile.rs | 9 + src/wasm/reflection.rs | 178 +++++++++++-- tests/wasm/reflection.js | 14 +- 20 files changed, 785 insertions(+), 309 deletions(-) create mode 100644 lib/_construct_jobject.js delete mode 100644 src/parser/transformers/class_define_fields.rs create mode 100644 src/parser/transformers/lazy_object_construction.rs diff --git a/.mocha.setup.js b/.mocha.setup.js index 231688f..1de17bb 100644 --- a/.mocha.setup.js +++ b/.mocha.setup.js @@ -2,4 +2,6 @@ // to use same assertion between node-swc & rest. require('@jymfony/util'); global.expect = require('expect'); -global.__jymfony.JObject = class {}; +global.__jymfony.JObject = class { + __construct() {} +}; diff --git a/index.js b/index.js index 28515aa..c519f69 100644 --- a/index.js +++ b/index.js @@ -16,4 +16,5 @@ exports.start = start; exports.getReflectionData = require('./lib/reflection').getReflectionData; global._apply_decs_2203_r = require('./lib/_apply_decs_2203_r')._; +global._construct_jobject = require('./lib/_construct_jobject')._; global.__jymfony_reflect = require('./lib/reflection')._; diff --git a/lib/_apply_decs_2203_r.js b/lib/_apply_decs_2203_r.js index a158e38..b3cc20b 100644 --- a/lib/_apply_decs_2203_r.js +++ b/lib/_apply_decs_2203_r.js @@ -672,7 +672,7 @@ function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) { return Object.defineProperty( Class, Symbol.metadata || Symbol.for('Symbol.metadata'), - { configurable: false, enumerable: false, value: metadata }, + { configurable: true, enumerable: false, value: metadata }, ); } diff --git a/lib/_construct_jobject.js b/lib/_construct_jobject.js new file mode 100644 index 0000000..4d190ba --- /dev/null +++ b/lib/_construct_jobject.js @@ -0,0 +1,104 @@ +exports._ = function _construct_jobject(callee, ...$args) { + let c, + r = new callee(...$args); + if (callee === Proxy) { + return r; + } + + if (global.__jymfony !== void 0 && r instanceof __jymfony.JObject) { + c = r.__construct(...$args); + } + + if ( + void 0 !== global.mixins && + void 0 !== r[global.mixins.initializerSymbol] + ) { + r[global.mixins.initializerSymbol](...$args); + } + + if (c !== void 0 && r !== c) { + return c; + } + + let self = r; + if ( + global.__jymfony !== void 0 && + r instanceof __jymfony.JObject && + __jymfony.autoload !== void 0 && + __jymfony.autoload.debug + ) { + Reflect.preventExtensions(self); + // self = new Proxy(self, { + // get: (target, p) => { + // if (p !== Symbol.toStringTag && ! Reflect.has(target, p)) { + // throw new TypeError('Undefined property ' + p.toString() + ' on instance of ' + ReflectionClass.getClassName(target)); + // } + // + // return Reflect.get(target, p); + // }, + // }); + } + + if (Reflect.has(r, '__invoke')) { + return new Proxy(self.__invoke, { + get: (_, key) => { + if (Symbol.for('jymfony.namespace.class') === key) { + return self; + } + + return Reflect.get(self, key); + }, + set(_, p, newValue, receiver) { + return Reflect.set(self, p, newValue, receiver); + }, + has(_, p) { + return Reflect.has(self, p); + }, + deleteProperty(_, p) { + return Reflect.deleteProperty(self, p); + }, + defineProperty(_, property, attributes) { + return Reflect.defineProperty(self, property, attributes); + }, + enumerate(_) { + return Reflect.enumerate(self); + }, + ownKeys(_) { + return Reflect.ownKeys(self); + }, + apply: (_, ctx, args) => { + return self.__invoke(...args); + }, + construct(_, argArray, newTarget) { + return Reflect.construct(self, argArray, newTarget); + }, + getPrototypeOf(_) { + return Reflect.getPrototypeOf(self); + }, + setPrototypeOf(_, v) { + return Reflect.setPrototypeOf(self, v); + }, + isExtensible(_) { + return Reflect.isExtensible(self); + }, + preventExtensions(_) { + Reflect.preventExtensions(self); + + return false; + }, + getOwnPropertyDescriptor(target, key) { + if (Symbol.for('jymfony.namespace.class') === key) { + return { + configurable: true, + enumerable: false, + value: self, + }; + } + + return Reflect.getOwnPropertyDescriptor(self, key); + }, + }); + } + + return self; +}; diff --git a/lib/reflection.js b/lib/reflection.js index b2556ed..18d2319 100644 --- a/lib/reflection.js +++ b/lib/reflection.js @@ -16,6 +16,46 @@ function docblockGetter(classId, memberIndex) { return member.docblock || null; } +function parametersGetter(classId, memberIndex) { + const data = getInternalReflectionData(classId); + if (data === void 0) { + return []; + } + + const member = data.members[memberIndex]; + + return member.params.map((p) => { + const { scalarDefault, ...parameter } = p; + if (scalarDefault) { + switch (scalarDefault.type) { + case 'Str': + parameter.default = scalarDefault.value; + break; + case 'Bool': + parameter.default = scalarDefault.value; + break; + case 'Num': + parameter.default = scalarDefault.value; + break; + case 'Null': + parameter.default = null; + break; + case 'BigInt': + parameter.default = BigInt(scalarDefault.value); + break; + case 'Regex': + parameter.default = new RegExp( + scalarDefault.value.exp, + scalarDefault.value.flags, + ); + break; + } + } + + return parameter; + }); +} + /** * @param {string} classId * @param {number|undefined} memberIndex @@ -57,14 +97,7 @@ exports._ = function __jymfony_reflect(classId, memberIndex = undefined) { return docblockGetter(classId, memberIndex); }, get parameters() { - const data = getInternalReflectionData(classId); - if (data === void 0) { - return []; - } - - const member = data.members[memberIndex]; - - return member.params.map((p) => ({ ...p })); + return parametersGetter(classId, memberIndex); }, }); } @@ -90,14 +123,7 @@ exports._ = function __jymfony_reflect(classId, memberIndex = undefined) { private: context.private, access: context.access, get parameters() { - const data = getInternalReflectionData(classId); - if (data === void 0) { - return []; - } - - const member = data.members[memberIndex]; - - return member.params.map((p) => ({ ...p })); + return parametersGetter(classId, memberIndex); }, get docblock() { return docblockGetter(classId, memberIndex); @@ -128,8 +154,10 @@ exports.getReflectionData = function getReflectionData(classIdOrValue) { return undefined; } - const metadata = - classIdOrValue[Symbol.metadata || Symbol.for('Symbol.metadata')]; + const sym = Symbol.metadata || Symbol.for('Symbol.metadata'); + const metadata = classIdOrValue.hasOwnProperty(sym) + ? classIdOrValue[sym] + : void 0; if (metadata !== void 0 && metadata[reflectionSymbol] !== void 0) { classIdOrValue = metadata[reflectionSymbol]; } diff --git a/package.json b/package.json index 930c7dd..2e1e439 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jymfony/compiler", - "version": "0.5.0-alpha.2", + "version": "0.5.0-beta.1", "type": "commonjs", "author": "Alessandro Chitolina ", "license": "MIT", @@ -9,7 +9,7 @@ "build": "wasm-pack build --out-name compiler --target nodejs --dev && RUSTFLAGS=\"-C target-feature=+simd128\" wasm-pack build --out-dir simd --out-name compiler --target nodejs --dev --features=simd --target-dir=simd-target", "pretest": "npm run build", "test": "mocha tests/wasm/", - "prepublishOnly": "npm run build-release && bash -c 'rm {pkg,simd}/{.gitignore,package.json}'" + "prepublishOnly": "npm run build-release && bash -c 'rm {pkg,simd}/{.gitignore,package.json,LICENSE}'" }, "main": "index.js", "types": "index.d.ts", @@ -23,6 +23,7 @@ "index.js", "index.d.ts", "lib/_apply_decs_2203_r.js", + "lib/_construct_jobject.js", "lib/reflection.js", "pkg/compiler.js", "pkg/compiler.d.ts", diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e6abad7..9e8a746 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -263,7 +263,7 @@ class y extends (__jymfony_JObject2 = __jymfony.JObject) { f() { var _initClass, __anonymous_xΞ2, _dec, __jymfony_JObject, _dec1, _initProto; _dec = __jymfony_reflect("00000000-0000-0000-0000-000000000003", void 0), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000003", 0); - return new (class _anonymous_xΞ2 extends (__jymfony_JObject = __jymfony.JObject) { + return _construct_jobject((class _anonymous_xΞ2 extends (__jymfony_JObject = __jymfony.JObject) { static #_ = { e: [_initProto], c: [__anonymous_xΞ2, _initClass] } = _apply_decs_2203_r(this, [ [ _dec1, @@ -281,7 +281,7 @@ class y extends (__jymfony_JObject2 = __jymfony.JObject) { return __anonymous_xΞ2; } static #_2 = _initClass(); - }, __anonymous_xΞ2); + }, __anonymous_xΞ2)); } static #_2 = _initClass2(); } diff --git a/src/parser/program.rs b/src/parser/program.rs index cc9ee30..581f5b5 100644 --- a/src/parser/program.rs +++ b/src/parser/program.rs @@ -1,6 +1,6 @@ use crate::parser::transformers::{ - anonymous_expr, class_define_fields, class_jobject, class_reflection_decorators, - decorator_2022_03, optional_import, remove_assert_calls, resolve_self_identifiers, + anonymous_expr, class_jobject, class_reflection_decorators, decorator_2022_03, + lazy_object_construction, optional_import, remove_assert_calls, resolve_self_identifiers, static_blocks, wrap_in_function, }; use crate::stack::register_source_map; @@ -18,7 +18,7 @@ use swc_ecma_codegen::Emitter; use swc_ecma_transforms_base::feature::FeatureFlag; use swc_ecma_transforms_base::fixer::fixer; use swc_ecma_transforms_base::helpers::{inject_helpers, Helpers, HELPERS}; -use swc_ecma_transforms_base::hygiene::hygiene; +use swc_ecma_transforms_base::hygiene::{hygiene_with_config, Config as HygieneConfig}; use swc_ecma_transforms_base::resolver; use swc_ecma_transforms_compat::es2020::{nullish_coalescing, optional_chaining}; use swc_ecma_transforms_module::common_js; @@ -31,6 +31,7 @@ pub struct CompileOptions { pub debug: bool, pub namespace: Option, pub as_function: bool, + pub as_module: bool, } pub struct Program { @@ -60,15 +61,13 @@ impl Program { HELPERS.set(&helpers, || { let unresolved_mark = Mark::new(); let top_level_mark = Mark::new(); - let static_block_mark = Mark::new(); + let static_blocks_mark = Mark::new(); let available_set = FeatureFlag::all(); let common_js_config = common_js::Config { import_interop: Some(ImportInterop::Swc), lazy: Lazy::Object(LazyObjectConfig { - patterns: vec![ - CachedRegex::new(".+").unwrap(), - ], + patterns: vec![CachedRegex::new(".+").unwrap()], }), ..Default::default() }; @@ -88,27 +87,45 @@ impl Program { resolve_self_identifiers(unresolved_mark), class_jobject(), decorator_2022_03(), - class_define_fields(), - static_blocks(static_block_mark), - common_js( - unresolved_mark, - common_js_config, - available_set, - Some(&self.comments) - ), - hygiene(), - fixer(Some(&self.comments)), - inject_helpers(top_level_mark), + lazy_object_construction(), + static_blocks(static_blocks_mark), )); + if !opts.as_module { + transformers = Box::new(chain!( + transformers, + common_js( + unresolved_mark, + common_js_config, + available_set, + Some(&self.comments) + ), + )); + } + if !opts.debug { transformers = Box::new(chain!(transformers, remove_assert_calls())); } + transformers = Box::new(chain!( + transformers, + hygiene_with_config(HygieneConfig { + top_level_mark, + ..Default::default() + }), + )); + if opts.as_function { - transformers = Box::new(chain!(transformers, wrap_in_function())); + transformers = + Box::new(chain!(transformers, wrap_in_function(unresolved_mark))); } + transformers = Box::new(chain!( + transformers, + fixer(Some(&self.comments)), + inject_helpers(top_level_mark), + )); + let program = self.program.fold_with(transformers.as_mut()); let mut buf = vec![]; let mut sm: Vec<(BytePos, LineCol)> = vec![]; @@ -152,23 +169,71 @@ mod tests { use crate::testing::uuid::reset_test_uuid; #[test] - pub fn should_add_explicit_constructor_to_decorator_metadata() -> anyhow::Result<()> { + pub fn should_compile_as_function_correctly() -> anyhow::Result<()> { reset_test_uuid(); let code = r#" export default class TestClass { constructor() { + require('vm'); console.log(this); } } "#; let program = code.parse_program(None)?; let code = program.compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, + as_function: true, + ..Default::default() })?; + assert_eq!( + code, + r#"(function(exports, require, module, __filename, __dirname) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + Object.defineProperty(exports, "default", { + enumerable: true, + get: function() { + return _default; + } + }); + var _initClass, _TestClass, _dec, __jymfony_JObject; + _dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0); + class TestClass extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = { c: [_TestClass, _initClass] } = _apply_decs_2203_r(this, [], [ + _dec + ], __jymfony_JObject); + constructor(){ + super(); + require('vm'); + console.log(this); + } + static #_2 = _initClass(); + } + const _default = _TestClass; +}); +"# + ); + + Ok(()) + } + + #[test] + pub fn should_add_explicit_constructor_to_decorator_metadata() -> anyhow::Result<()> { + reset_test_uuid(); + + let code = r#" +export default class TestClass { + constructor() { + console.log(this); + } +} +"#; + let program = code.parse_program(None)?; + let code = program.compile(Default::default())?; + assert_eq!( code, r#""use strict"; @@ -217,13 +282,7 @@ const parseHosts = (params, dsn) => {}; export default class RedisAdapter {} "#; let program = code.parse_program(None).unwrap(); - let code = program - .compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - }) - .unwrap(); + let code = program.compile(Default::default()).unwrap(); assert_eq!( code, @@ -277,13 +336,7 @@ class conn { } "#; let program = code.parse_program(None).unwrap(); - let code = program - .compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - }) - .unwrap(); + let code = program.compile(Default::default()).unwrap(); assert_eq!( code, @@ -311,7 +364,7 @@ class conn extends (__jymfony_JObject = __jymfony.JObject) { ], __jymfony_JObject); constructor(){ super(); - this._cluster = new RedisCluster(); + this._cluster = _construct_jobject(RedisCluster); this._redis = Redis; } static #_2 = _initClass(); @@ -336,11 +389,7 @@ export default class ClassLoaderTest extends TestCase { "# .parse_program(None)?; - let compiled = program.compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - })?; + let compiled = program.compile(Default::default())?; assert_eq!( compiled, @@ -369,12 +418,8 @@ class ClassLoaderTest extends (_TestCase = TestCase) { ], [ _dec ], _TestCase); + _classLoader = _init__classLoader(this); static #_2 = _initClass(); - [Symbol.__jymfony_field_initialization]() { - const superCall = super[Symbol.__jymfony_field_initialization]; - if (void 0 !== superCall) superCall.apply(this); - this._classLoader = _init__classLoader(this); - } } const _default = _ClassLoaderTest; "# @@ -396,11 +441,7 @@ export default class YamlFileLoaderTest extends JsonFileLoaderTest { "# .parse_program(None)?; - let compiled = program.compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - })?; + let compiled = program.compile(Default::default())?; assert_eq!( compiled, @@ -466,11 +507,7 @@ class TestAnnotation { "# .parse_program(None)?; - let compiled = program.compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - })?; + let compiled = program.compile(Default::default())?; assert_eq!( compiled, @@ -546,11 +583,7 @@ export default class Debug { "# .parse_program(None)?; - let compiled = program.compile(CompileOptions { - debug: false, - namespace: None, - as_function: false, - })?; + let compiled = program.compile(Default::default())?; assert_eq!( compiled, @@ -588,11 +621,11 @@ class Debug extends (__jymfony_JObject = __jymfony.JObject) { static enable() { __jymfony.autoload.debug = true; process.on('unhandledRejection', (reason, p)=>{ - throw new UnhandledRejectionException(p, reason instanceof Error ? reason : undefined); + throw _construct_jobject(UnhandledRejectionException, p, reason instanceof Error ? reason : undefined); }); __jymfony.ManagedProxy.enableDebug(); Timeout.enable(); - ErrorHandler.register(new ErrorHandler(new BufferingLogger(), true)); + ErrorHandler.register(_construct_jobject(ErrorHandler, _construct_jobject(BufferingLogger), true)); } static #_2 = _initClass(); } @@ -602,4 +635,92 @@ const _default = _Debug; Ok(()) } + + #[test] + pub fn should_compile_autoaccessors() -> anyhow::Result<()> { + reset_test_uuid(); + + let program = r#" +const Constraint = Jymfony.Component.Validator.Annotation.Constraint; +const Valid = Jymfony.Component.Validator.Constraints.Valid; +const FooBarBaz = Jymfony.Component.Validator.Fixtures.Valid.FooBarBaz; + +export default class FooBar { + publicField = 'x'; + + @Constraint(Valid, { groups: [ 'nested' ]}) + accessor fooBarBaz; + + __construct() { + this.fooBarBaz = new FooBarBaz(); + } +} +"# + .parse_program(None)?; + + let compiled = program.compile(Default::default())?; + + assert_eq!( + compiled, + r#""use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "default", { + enumerable: true, + get: function() { + return _default; + } +}); +var _initClass, _FooBar, _dec, __jymfony_JObject, _dec1, _dec2, _dec3, _dec4, _init_fooBarBaz, _init_publicField, _initProto; +const Constraint = Jymfony.Component.Validator.Annotation.Constraint; +const Valid = Jymfony.Component.Validator.Constraints.Valid; +const FooBarBaz = Jymfony.Component.Validator.Fixtures.Valid.FooBarBaz; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0), _dec2 = Constraint(Valid, { + groups: [ + 'nested' + ] +}), _dec3 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 1), _dec4 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 2); +class FooBar extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = { e: [_init_fooBarBaz, _init_publicField, _initProto], c: [_FooBar, _initClass] } = _apply_decs_2203_r(this, [ + [ + [ + _dec2, + _dec3 + ], + 1, + "fooBarBaz" + ], + [ + _dec4, + 2, + "__construct" + ], + [ + _dec1, + 0, + "publicField" + ] + ], [ + _dec + ], __jymfony_JObject); + publicField = _init_publicField(this, 'x'); + #___private_fooBarBaz = (_initProto(this), _init_fooBarBaz(this)); + get fooBarBaz() { + return this.#___private_fooBarBaz; + } + set fooBarBaz(_v) { + this.#___private_fooBarBaz = _v; + } + __construct() { + this.fooBarBaz = _construct_jobject(FooBarBaz); + } + static #_2 = _initClass(); +} +const _default = _FooBar; +"# + ); + + Ok(()) + } } diff --git a/src/parser/transformers/class_define_fields.rs b/src/parser/transformers/class_define_fields.rs deleted file mode 100644 index e6add0f..0000000 --- a/src/parser/transformers/class_define_fields.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::parser::util::*; -use lazy_static::lazy_static; -use swc_common::util::take::Take; -use swc_common::DUMMY_SP; -use swc_ecma_ast::*; -use swc_ecma_utils::undefined; -use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; - -lazy_static! { - static ref FIELD_INITIALIZATION_SYM: MemberExpr = { - let obj_expr = ident("Symbol"); - let prop = ident("__jymfony_field_initialization"); - - MemberExpr { - span: DUMMY_SP, - obj: Box::new(Expr::Ident(obj_expr)), - prop: MemberProp::Ident(prop), - } - }; -} - -pub fn class_define_fields() -> impl VisitMut + Fold { - as_folder(ClassDefineFields::default()) -} - -#[derive(Default)] -struct ClassDefineFields {} - -impl VisitMut for ClassDefineFields { - noop_visit_mut_type!(); - - fn visit_mut_class(&mut self, n: &mut Class) { - n.visit_mut_children_with(self); - - let mut stmts = vec![]; - let mut initializers = vec![]; - for mut member in n.body.drain(..) { - if let ClassMember::ClassProp(prop) = &mut member { - if !prop.is_static { - initializers.push(member); - continue; - } - }; - - stmts.push(member); - } - - n.body = stmts; - if !initializers.is_empty() { - let mut block_stmts = vec![]; - let sc_ident = ident("superCall"); - let super_call = Stmt::Decl(Decl::Var(Box::new(VarDecl { - span: DUMMY_SP, - kind: VarDeclKind::Const, - declare: false, - decls: vec![VarDeclarator { - span: DUMMY_SP, - name: Pat::Ident(sc_ident.clone().into()), - init: Some(Box::new(Expr::SuperProp(SuperPropExpr { - span: DUMMY_SP, - obj: Super::dummy(), - prop: SuperProp::Computed(ComputedPropName { - span: Default::default(), - expr: Box::new(Expr::Member(FIELD_INITIALIZATION_SYM.clone())), - }), - }))), - definite: false, - }], - }))); - - let if_block = Stmt::If(IfStmt { - span: DUMMY_SP, - test: Box::new(Expr::Bin(BinExpr { - span: DUMMY_SP, - op: BinaryOp::NotEqEq, - left: undefined(DUMMY_SP), - right: Box::new(Expr::Ident(sc_ident.clone())), - })), - cons: Box::new(Stmt::Expr(ExprStmt { - span: DUMMY_SP, - expr: Box::new(Expr::Call(CallExpr { - span: DUMMY_SP, - callee: Callee::Expr(Box::new(Expr::Member(MemberExpr { - span: DUMMY_SP, - obj: Box::new(Expr::Ident(sc_ident)), - prop: MemberProp::Ident(ident("apply")), - }))), - args: vec![ExprOrSpread { - spread: None, - expr: Box::new(Expr::This(ThisExpr::dummy())), - }], - type_args: None, - })), - })), - alt: None, - }); - - block_stmts.push(super_call); - block_stmts.push(if_block); - - for p in initializers.drain(..) { - let mut p = p.expect_class_prop(); - assert!(!p.is_static); - - let value = p.value.take(); - let prop_name = match p.key { - PropName::Ident(i) => MemberProp::Ident(i), - PropName::Str(s) => MemberProp::Computed(ComputedPropName { - span: s.span, - expr: Box::new(Expr::Lit(Lit::Str(s))), - }), - PropName::Num(n) => MemberProp::Computed(ComputedPropName { - span: n.span, - expr: Box::new(Expr::Lit(Lit::Num(n))), - }), - PropName::Computed(c) => MemberProp::Computed(ComputedPropName { - span: c.span, - expr: c.expr, - }), - PropName::BigInt(n) => MemberProp::Computed(ComputedPropName { - span: n.span, - expr: Box::new(Expr::Lit(Lit::BigInt(n))), - }), - }; - - let e = Expr::Assign(AssignExpr { - span: DUMMY_SP, - op: AssignOp::Assign, - left: PatOrExpr::Expr(Box::new(Expr::Member(MemberExpr { - span: DUMMY_SP, - obj: Box::new(Expr::This(ThisExpr::dummy())), - prop: prop_name, - }))), - right: value.unwrap_or_else(|| undefined(DUMMY_SP)), - }); - - block_stmts.push(Stmt::Expr(ExprStmt { - span: p.span, - expr: Box::new(e), - })); - } - - n.body.push(ClassMember::Method(ClassMethod { - span: DUMMY_SP, - key: PropName::Computed(ComputedPropName { - span: DUMMY_SP, - expr: Box::new(Expr::Member(FIELD_INITIALIZATION_SYM.clone())), - }), - function: Box::new(Function { - params: vec![], - decorators: vec![], - span: DUMMY_SP, - body: Some(BlockStmt { - span: DUMMY_SP, - stmts: block_stmts, - }), - is_generator: false, - is_async: false, - type_params: None, - return_type: None, - }), - kind: MethodKind::Method, - is_static: false, - accessibility: None, - is_abstract: false, - is_optional: false, - is_override: false, - })); - } - } -} diff --git a/src/parser/transformers/class_reflection_decorators.rs b/src/parser/transformers/class_reflection_decorators.rs index a1b7bf8..2f74487 100644 --- a/src/parser/transformers/class_reflection_decorators.rs +++ b/src/parser/transformers/class_reflection_decorators.rs @@ -43,11 +43,17 @@ impl<'a, C: Comments> ClassReflectionDecorators<'a, C> { }) } - fn process_class(&self, n: &mut Class, name: Ident) { + fn process_class(&self, n: &mut Class, name: Ident, outer_docblock: Option) { let id = generate_uuid(); let mut docblock = FxHashMap::default(); + if let Some(outer_db) = outer_docblock { + docblock.insert(n.span, Some(outer_db)); + } + if n.span != DUMMY_SP { - docblock.insert(n.span, self.get_element_docblock(n.span)); + if let Some(db) = self.get_element_docblock(n.span) { + docblock.insert(n.span, Some(db)); + } } n.decorators.push(Decorator { @@ -128,8 +134,38 @@ impl<'a, C: Comments> ClassReflectionDecorators<'a, C> { impl VisitMut for ClassReflectionDecorators<'_, C> { noop_visit_mut_type!(); + fn visit_mut_module_item(&mut self, n: &mut ModuleItem) { + match n { + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { + decl: DefaultDecl::Class(expr), + span, + })) => { + let Some(ident) = expr.ident.clone() else { + panic!("anonymous_expr transformer must be called before class_reflection_decorator"); + }; + + self.process_class(&mut expr.class, ident, self.get_element_docblock(*span)); + expr.visit_mut_children_with(self); + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::Class(decl), + span, + })) => { + self.process_class( + &mut decl.class, + decl.ident.clone(), + self.get_element_docblock(*span), + ); + decl.visit_mut_children_with(self); + } + _ => { + n.visit_mut_children_with(self); + } + } + } + fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) { - self.process_class(&mut n.class, n.ident.clone()); + self.process_class(&mut n.class, n.ident.clone(), None); n.visit_mut_children_with(self); } @@ -137,7 +173,52 @@ impl VisitMut for ClassReflectionDecorators<'_, C> { let Some(ident) = n.ident.clone() else { panic!("anonymous_expr transformer must be called before class_reflection_decorator"); }; - self.process_class(&mut n.class, ident); + + self.process_class(&mut n.class, ident, None); n.visit_mut_children_with(self); } } + +#[cfg(test)] +mod tests { + use crate::parser::transformers::class_reflection_decorators; + use crate::testing::compile_tr; + use swc_common::{chain, Mark}; + use swc_ecma_transforms_base::resolver; + use swc_ecma_transforms_testing::Tester; + use swc_ecma_visit::Fold; + + fn create_pass(tester: &mut Tester) -> Box { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + + Box::new(chain!( + resolver(unresolved_mark, top_level_mark, false), + class_reflection_decorators(None, None, tester.comments.clone()), + )) + } + + #[test] + pub fn should_compile_as_function_correctly() { + let code = r#" +export default class TestClass { + publicMethod(a, b = 12, c = {}) { + console.log('test'); + } +} +"#; + + let compiled = compile_tr(|tester| create_pass(tester), code); + assert_eq!( + compiled, + r#"export default @__jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0) +class TestClass { + @__jymfony_reflect("00000000-0000-0000-0000-000000000000", 0) + publicMethod(a, b = 12, c = {}) { + console.log('test'); + } +} +"# + ); + } +} diff --git a/src/parser/transformers/decorator_2022_03.rs b/src/parser/transformers/decorator_2022_03.rs index 1d83b0a..e1daa36 100644 --- a/src/parser/transformers/decorator_2022_03.rs +++ b/src/parser/transformers/decorator_2022_03.rs @@ -246,6 +246,11 @@ impl Decorator202203 { }) .as_callee(), args: vec![ + if is_constructor && self.state.super_class.is_some() { + undefined(DUMMY_SP).as_arg() + } else { + ThisExpr::dummy().as_arg() + }, if is_constructor && self.state.super_class.is_some() { undefined(DUMMY_SP).as_arg() } else { diff --git a/src/parser/transformers/lazy_object_construction.rs b/src/parser/transformers/lazy_object_construction.rs new file mode 100644 index 0000000..dfa883d --- /dev/null +++ b/src/parser/transformers/lazy_object_construction.rs @@ -0,0 +1,42 @@ +use crate::parser::util::ident; +use swc_common::util::take::Take; +use swc_ecma_ast::*; +use swc_ecma_utils::ExprFactory; +use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; + +pub fn lazy_object_construction() -> impl VisitMut + Fold { + as_folder(LazyObjectConstruction::default()) +} + +#[derive(Default)] +struct LazyObjectConstruction {} + +impl VisitMut for LazyObjectConstruction { + noop_visit_mut_type!(); + + fn visit_mut_expr(&mut self, n: &mut Expr) { + n.visit_mut_children_with(self); + + let Expr::New(new_expr) = n else { + return; + }; + let NewExpr { + span, + callee, + args, + type_args, + } = new_expr.take(); + + let new_args = vec![vec![callee.as_arg()], args.unwrap_or_default()] + .into_iter() + .flatten() + .collect(); + + *n = Expr::Call(CallExpr { + span, + callee: Callee::Expr(Box::new(Expr::Ident(ident("_construct_jobject")))), + args: new_args, + type_args, + }); + } +} diff --git a/src/parser/transformers/mod.rs b/src/parser/transformers/mod.rs index 69bf9c3..eac7125 100644 --- a/src/parser/transformers/mod.rs +++ b/src/parser/transformers/mod.rs @@ -1,8 +1,8 @@ mod anonymous_expr; -mod class_define_fields; mod class_jobject; mod class_reflection_decorators; mod decorator_2022_03; +mod lazy_object_construction; mod optional_import; mod remove_assert_calls; mod resolve_self_identifiers; @@ -10,10 +10,10 @@ mod static_blocks; mod wrap_in_function; pub(crate) use anonymous_expr::anonymous_expr; -pub(crate) use class_define_fields::class_define_fields; pub(crate) use class_jobject::class_jobject; pub(crate) use class_reflection_decorators::class_reflection_decorators; pub(crate) use decorator_2022_03::decorator_2022_03; +pub(crate) use lazy_object_construction::lazy_object_construction; pub(crate) use optional_import::optional_import; pub(crate) use remove_assert_calls::remove_assert_calls; pub(crate) use resolve_self_identifiers::resolve_self_identifiers; diff --git a/src/parser/transformers/resolve_self_identifiers.rs b/src/parser/transformers/resolve_self_identifiers.rs index 907bc73..7cd16ea 100644 --- a/src/parser/transformers/resolve_self_identifiers.rs +++ b/src/parser/transformers/resolve_self_identifiers.rs @@ -42,16 +42,5 @@ impl VisitMut for ResolveSelfIdentifiers { }; let _ = replace(n, current_class.clone()); } - - // match expr { - // Expr::Ident(i) if i.span.ctxt != self.unresolved => false, - // _ => { - // if is_call && self.c.pure_getter { - // !is_simple_member(expr) - // } else { - // true - // } - // } - // } } } diff --git a/src/parser/transformers/wrap_in_function.rs b/src/parser/transformers/wrap_in_function.rs index 029a4e7..dee0991 100644 --- a/src/parser/transformers/wrap_in_function.rs +++ b/src/parser/transformers/wrap_in_function.rs @@ -1,18 +1,22 @@ use crate::parser::util::*; use lazy_static::lazy_static; use swc_common::util::take::Take; -use swc_common::DUMMY_SP; +use swc_common::{Mark, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; lazy_static! {} -pub fn wrap_in_function() -> impl VisitMut + Fold { - as_folder(WrapInFunction::default()) +pub fn wrap_in_function(top_level_mark: Mark) -> impl VisitMut + Fold { + as_folder(WrapInFunction { + unresolved_mark: top_level_mark, + }) } #[derive(Default)] -struct WrapInFunction {} +struct WrapInFunction { + unresolved_mark: Mark, +} impl VisitMut for WrapInFunction { noop_visit_mut_type!(); @@ -26,6 +30,8 @@ impl VisitMut for WrapInFunction { "must be called after commonjs transformation" ); + let span = DUMMY_SP.apply_mark(self.unresolved_mark); + let wrapped = Stmt::Expr(ExprStmt { span: DUMMY_SP, expr: Box::new(Expr::Paren(ParenExpr { @@ -35,27 +41,27 @@ impl VisitMut for WrapInFunction { function: Box::new(Function { params: vec![ Param { - span: DUMMY_SP, + span, decorators: vec![], pat: Pat::Ident(ident("exports").into()), }, Param { - span: DUMMY_SP, + span, decorators: vec![], pat: Pat::Ident(ident("require").into()), }, Param { - span: DUMMY_SP, + span, decorators: vec![], pat: Pat::Ident(ident("module").into()), }, Param { - span: DUMMY_SP, + span, decorators: vec![], pat: Pat::Ident(ident("__filename").into()), }, Param { - span: DUMMY_SP, + span, decorators: vec![], pat: Pat::Ident(ident("__dirname").into()), }, @@ -134,3 +140,61 @@ impl VisitMut for WrapInFunction { n.body = vec![wrapped]; } } + +#[cfg(test)] +mod tests { + use crate::parser::transformers::wrap_in_function; + use crate::testing::compile_tr; + use swc_common::comments::SingleThreadedComments; + use swc_common::{chain, Mark}; + use swc_ecma_transforms_base::resolver; + use swc_ecma_transforms_compat::es2022::static_blocks; + use swc_ecma_transforms_module::common_js; + use swc_ecma_visit::Fold; + + fn create_pass() -> Box { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + let static_block_mark = Mark::new(); + + Box::new(chain!( + resolver(unresolved_mark, top_level_mark, false), + common_js::( + unresolved_mark, + Default::default(), + Default::default(), + None + ), + wrap_in_function(top_level_mark), + static_blocks(static_block_mark), + )) + } + + #[test] + pub fn should_compile_as_function_correctly() { + let code = r#" +export default class TestClass { +} +"#; + + let compiled = compile_tr(|_| create_pass(), code); + assert_eq!( + compiled, + r#"(function(exports, require, module, __filename, __dirname) { + "use strict"; + Object.defineProperty(exports, "__esModule", { + value: true + }); + Object.defineProperty(exports, "default", { + enumerable: true, + get: function() { + return TestClass; + } + }); + class TestClass { + } +}); +"# + ); + } +} diff --git a/src/stack/trace.rs b/src/stack/trace.rs index 738243a..1b58b60 100644 --- a/src/stack/trace.rs +++ b/src/stack/trace.rs @@ -10,20 +10,28 @@ pub(crate) fn remap_stack_trace( let mappings = FILE_MAPPINGS.read().unwrap(); let new_stack = stack .iter() - .map(|frame| { + .filter_map(|frame| { + if frame + .function_name + .as_deref() + .is_some_and(|f| f == "_construct_jobject") + { + return None; + } + let file_name = frame.filename.as_deref().unwrap_or_default(); if frame.is_native { - return frame.string_repr.clone(); + return Some(frame.string_repr.clone()); } let Some(source_map) = mappings.get(file_name) else { - return frame.string_repr.clone(); + return Some(frame.string_repr.clone()); }; let line_no = frame.line_no - 1; let col_no = frame.col_no - 1; let Some(token) = source_map.0.lookup_token(line_no, col_no) else { - return frame.string_repr.clone(); + return Some(frame.string_repr.clone()); }; let file_location = format!( @@ -102,7 +110,7 @@ pub(crate) fn remap_stack_trace( }; processed = true; - format!( + Some(format!( "{}{}{}", if frame.is_async { "async " } else { "" }, if frame.is_promise_all { @@ -111,7 +119,7 @@ pub(crate) fn remap_stack_trace( "".to_string() }, generate_function_call() - ) + )) }) .collect::>(); diff --git a/src/testing/mod.rs b/src/testing/mod.rs index a627a34..74798ff 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -10,7 +10,7 @@ use std::path::Path; use std::process::Command; use std::{env, fs}; use swc_common::Mark; -use swc_ecma_parser::Syntax; +use swc_ecma_parser::{EsConfig, Syntax}; use swc_ecma_transforms_base::{fixer, hygiene}; use swc_ecma_transforms_testing::{HygieneVisualizer, Tester}; use swc_ecma_visit::{Fold, FoldWith}; @@ -33,6 +33,38 @@ fn calc_hash(s: &str) -> String { hex::encode(sum) } +pub fn compile_tr(tr: F, input: &str) -> String +where + F: FnOnce(&mut Tester<'_>) -> P, + P: Fold, +{ + Tester::run(|tester| { + let tr = tr(tester); + let module = tester.apply_transform( + tr, + "input.js", + Syntax::Es(EsConfig { + jsx: false, + fn_bind: false, + decorators: true, + decorators_before_export: false, + export_default_from: false, + import_attributes: false, + allow_super_outside_method: false, + allow_return_outside_function: true, + auto_accessors: true, + explicit_resource_management: true, + }), + input, + )?; + + let module = module.fold_with(&mut fixer::fixer(Some(&tester.comments))); + + let src = tester.print(&module, &tester.comments.clone()); + Ok(src) + }) +} + /// Execute `jest` after transpiling `input` using `tr`. pub fn exec_tr(test_name: &str, syntax: Syntax, tr: F, input: &str) where diff --git a/src/wasm/compile.rs b/src/wasm/compile.rs index 4ac8ff6..17a60b2 100644 --- a/src/wasm/compile.rs +++ b/src/wasm/compile.rs @@ -7,6 +7,7 @@ interface CompileOptions { debug?: boolean; namespace?: string; asFunction?: boolean; + asModule?: boolean; } "#; @@ -23,6 +24,9 @@ extern "C" { #[wasm_bindgen(structural, method, getter, js_name = "asFunction")] fn as_function(this: &WasmCompileOptions) -> Option; + + #[wasm_bindgen(structural, method, getter, js_name = "asModule")] + fn as_module(this: &WasmCompileOptions) -> Option; } #[wasm_bindgen(js_name = compile)] @@ -37,6 +41,10 @@ pub fn compile( .as_ref() .and_then(|c| c.as_function()) .unwrap_or_default(); + let as_module = opts + .as_ref() + .and_then(|c| c.as_module()) + .unwrap_or_default(); let program = match source.parse_program(filename.as_deref()) { Ok(p) => p, @@ -53,5 +61,6 @@ pub fn compile( debug, namespace, as_function, + as_module, })?) } diff --git a/src/wasm/reflection.rs b/src/wasm/reflection.rs index 8e8de57..06c07d6 100644 --- a/src/wasm/reflection.rs +++ b/src/wasm/reflection.rs @@ -1,15 +1,46 @@ use crate::parse_uuid; -use crate::reflection::get_reflection_data; +use crate::reflection::{get_reflection_data, ReflectionData}; use serde::{Deserialize, Serialize}; use swc_ecma_ast::*; use wasm_bindgen::prelude::*; +#[derive(Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] +pub enum Scalar { + Str(String), + Bool(bool), + Null, + Num(f64), + BigInt(String), + Regex { exp: String, flags: String }, +} + +impl TryFrom<&Lit> for Scalar { + type Error = (); + + fn try_from(value: &Lit) -> Result { + match value { + Lit::Str(s) => Ok(Scalar::Str(s.value.to_string())), + Lit::Bool(b) => Ok(Scalar::Bool(b.value)), + Lit::Null(_) => Ok(Scalar::Null), + Lit::Num(n) => Ok(Scalar::Num(n.value)), + Lit::BigInt(n) => Ok(Scalar::BigInt(n.value.to_string())), + Lit::Regex(r) => Ok(Scalar::Regex { + exp: r.exp.to_string(), + flags: r.flags.to_string(), + }), + _ => Err(()), + } + } +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JsMethodParameter { pub name: Option, pub index: usize, pub has_default: bool, + pub scalar_default: Option, pub is_object_pattern: bool, pub is_array_pattern: bool, pub is_rest_element: bool, @@ -23,10 +54,18 @@ impl From<&Param> for JsMethodParameter { (false, Box::new(value.pat.clone())) }; + let (ident, def) = if let Pat::Assign(a) = pat.as_ref() { + let def = a.right.as_lit().and_then(|l| Scalar::try_from(l).ok()); + (a.left.as_ident(), def) + } else { + (pat.as_ident(), None) + }; + JsMethodParameter { - name: pat.as_ident().map(|i| i.sym.to_string()), + name: ident.map(|i| i.sym.to_string()), index: 0, has_default: pat.is_assign(), + scalar_default: def, is_object_pattern: pat.is_object(), is_array_pattern: pat.is_array(), is_rest_element: is_rest, @@ -64,15 +103,7 @@ pub struct JsReflectionData { pub docblock: Option, } -#[wasm_bindgen(js_name = getInternalReflectionData)] -pub fn get_js_reflection_data(class_id: &str) -> Result { - let Ok(class_id) = parse_uuid(class_id) else { - return Ok(JsValue::undefined()); - }; - let Some(reflection_data) = get_reflection_data(&class_id) else { - return Ok(JsValue::undefined()); - }; - +fn process_reflection_data(reflection_data: &ReflectionData) -> JsReflectionData { let class = &reflection_data.class; let namespace = reflection_data.namespace.clone(); @@ -91,6 +122,11 @@ pub fn get_js_reflection_data(class_id: &str) -> Result { name: tp.param.as_ident().map(|i| i.sym.to_string()), index: i, has_default: tp.param.is_assign(), + scalar_default: tp + .param + .as_assign() + .and_then(|a| a.right.as_lit()) + .and_then(|l| Scalar::try_from(l).ok()), is_object_pattern: tp .param .as_assign() @@ -199,13 +235,14 @@ pub fn get_js_reflection_data(class_id: &str) -> Result { .collect(); let class_name = reflection_data.name.sym.to_string(); - let fqcn = if let Some(ns) = namespace.as_deref() { - format!("{}.{}", ns, class_name) + let ns = namespace.as_deref(); + let fqcn = if ns.is_some_and(|n| !n.is_empty()) { + format!("{}.{}", ns.unwrap(), class_name) } else { class_name.clone() }; - Ok(serde_wasm_bindgen::to_value(&JsReflectionData { + JsReflectionData { fqcn, class_name, namespace, @@ -216,5 +253,116 @@ pub fn get_js_reflection_data(class_id: &str) -> Result { .get(&class.span) .cloned() .unwrap_or_default(), - })?) + } +} + +#[wasm_bindgen(js_name = getInternalReflectionData)] +pub fn get_js_reflection_data(class_id: &str) -> Result { + let Ok(class_id) = parse_uuid(class_id) else { + return Ok(JsValue::undefined()); + }; + let Some(reflection_data) = get_reflection_data(&class_id) else { + return Ok(JsValue::undefined()); + }; + + Ok(serde_wasm_bindgen::to_value(&process_reflection_data( + &reflection_data, + ))?) +} + +#[cfg(test)] +mod tests { + use crate::parser::CodeParser; + use crate::reflection::ReflectionData; + use crate::wasm::reflection::{process_reflection_data, JsMemberData}; + use swc_common::DUMMY_SP; + use swc_ecma_ast::Ident; + + #[test] + pub fn should_process_method_parameters_correctly() -> anyhow::Result<()> { + let code = r#" +/** class docblock */ +export default class x { + static #staticPrivateField; + #privateField; + accessor #privateAccessor; + static staticPublicField; + publicField; + accessor publicAccessor; + + /** constructor docblock */ + constructor(@type(String) constructorParam1) { + } + + /** + * computed method docblock + */ + [a()]() {} + #privateMethod(a, b = 1, [c, d], {f, g}) {} + + /** + * public method docblock + */ + publicMethod({a, b} = {}, c = new Object(), ...x) {} + static #staticPrivateMethod() {} + static staticPublicMethod() {} + + get [a()]() {} + set b(v) {} + + get #ap() {} + set #bp(v) {} + + act(@type(String) param1) {} + [a()](@type(String) param1) {} + [Symbol.for('xtest')](@type(String) param1) {} +} + +return x[Symbol.metadata].act[Symbol.parameters][0].type; +"#; + + let program = code.parse_program(None)?; + let mut module = program.program.expect_module(); + let body = module.body.drain(..); + let item = body.take(1).into_iter().nth(0).unwrap(); + let class_decl = item + .expect_module_decl() + .expect_export_default_decl() + .decl + .expect_class(); + + let data = process_reflection_data(&ReflectionData { + class: *class_decl.class, + name: Ident { + span: DUMMY_SP, + sym: "x".into(), + optional: false, + }, + filename: None, + namespace: None, + docblock: Default::default(), + }); + + let JsMemberData::Method(method) = data.members.as_slice().iter().nth(8).unwrap() else { + panic!("not a method"); + }; + assert!(method + .params + .as_slice() + .iter() + .nth(0) + .unwrap() + .name + .is_some()); + assert!(method + .params + .as_slice() + .iter() + .nth(1) + .unwrap() + .name + .is_some()); + + Ok(()) + } } diff --git a/tests/wasm/reflection.js b/tests/wasm/reflection.js index 341a8a1..14c9f26 100644 --- a/tests/wasm/reflection.js +++ b/tests/wasm/reflection.js @@ -20,7 +20,8 @@ const type = t => { }; }; -export default /** class docblock */ class x { +/** class docblock */ +export default class x { static #staticPrivateField; #privateField; accessor #privateAccessor; @@ -54,6 +55,8 @@ export default /** class docblock */ class x { act(@type(String) param1) {} [a()](@type(String) param1) {} [Symbol.for('xtest')](@type(String) param1) {} + + publicMethodWithDefaults(a = {}, b = 1, c = 'test', d = /test/g, e = 42n, f = true, g = null) {} } return x[Symbol.metadata].act[Symbol.parameters][0].type; @@ -71,6 +74,9 @@ return x[Symbol.metadata].act[Symbol.parameters][0].type; const data = getReflectionData(exports['default']); const construct = data.members.find((o) => o.name === 'constructor'); const member = data.members.find((o) => o.name === 'publicMethod'); + const defaults = data.members.find( + (o) => o.name === 'publicMethodWithDefaults', + ); expect(construct).not.toBeUndefined(); expect(construct.docblock).toEqual('/** constructor docblock */'); @@ -81,5 +87,11 @@ return x[Symbol.metadata].act[Symbol.parameters][0].type; '/**\n * public method docblock\n */', ); expect(member.parameters).toHaveLength(3); + expect(member.parameters[1].name).toEqual('c'); + expect(member.parameters[2].name).toEqual('x'); + expect(defaults.parameters).toHaveLength(7); + expect(defaults.parameters[1].default).toEqual(1); + expect(defaults.parameters[2].default).toEqual('test'); + expect(defaults.parameters[6].default).toEqual(null); }); });