diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..4aa7c6c --- /dev/null +++ b/.cargo/config @@ -0,0 +1,4 @@ +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-args=-z stack-size=4194304", +] \ No newline at end of file diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index a75933b..13f064f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,91 +1,91 @@ name: Test Suite on: - push: - branches: - - develop - pull_request_target: + push: + branches: + - develop + pull_request_target: jobs: - test_rust: - name: Test Rust - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - name: Checkout source + test_rust: + name: Test Rust + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + name: Checkout source - - uses: actions/cache@v3 - name: Setup Cache - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - simd-target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - uses: actions/cache@v3 + name: Setup Cache + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + simd-target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Run Rust tests - run: | - cargo install grcov - npm install -g mocha - npm ci - cargo clean - cargo test - grcov . --binary-path ./target/debug -s . -t lcov --branch -o ./coverage.lcov - env: - RUSTFLAGS: -Cinstrument-coverage - LLVM_PROFILE_FILE: "%p-%m.profraw" + - name: Run Rust tests + run: | + cargo install grcov + npm install -g mocha + npm ci + cargo clean + cargo test + grcov . --binary-path ./target/debug -s . -t lcov --branch -o ./coverage.lcov + env: + RUSTFLAGS: -Cinstrument-coverage + LLVM_PROFILE_FILE: '%p-%m.profraw' - - name: Publish coverage report - uses: codecov/codecov-action@v3 - with: - file: ./coverage.lcov + - name: Publish coverage report + uses: codecov/codecov-action@v3 + with: + file: ./coverage.lcov - test_node: - name: Test (Node.JS ${{ matrix.node_version }}) - runs-on: ubuntu-latest - strategy: - matrix: - node_version: [16, 18, 20] - fail-fast: false + test_node: + name: Test (Node.JS ${{ matrix.node_version }}) + runs-on: ubuntu-latest + strategy: + matrix: + node_version: [16, 18, 20] + fail-fast: false - steps: - - uses: actions/checkout@v4 - name: Checkout source + steps: + - uses: actions/checkout@v4 + name: Checkout source - - uses: actions/cache@v3 - name: Setup Cache - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target/ - simd-target/ - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - uses: actions/cache@v3 + name: Setup Cache + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + simd-target/ + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Get npm cache directory - id: npm-cache-dir - shell: bash - run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} + - name: Get npm cache directory + id: npm-cache-dir + shell: bash + run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT} - - uses: actions/cache@v3 - with: - path: ${{ steps.npm-cache-dir.outputs.dir }} - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + - uses: actions/cache@v3 + with: + path: ${{ steps.npm-cache-dir.outputs.dir }} + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - - uses: actions/setup-node@v3 - name: Setup Node.JS - with: - node-version: ${{ matrix.node_version }} + - uses: actions/setup-node@v3 + name: Setup Node.JS + with: + node-version: ${{ matrix.node_version }} - - name: Run WASM tests - run: | - cargo install wasm-pack --force - cargo clean - npm install -g mocha - npm ci - npm test \ No newline at end of file + - name: Run WASM tests + run: | + cargo install wasm-pack --force + cargo clean + npm install -g mocha + npm ci + npm test diff --git a/Cargo.toml b/Cargo.toml index 911e242..22959c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde = { version = "1.0", features = ["derive"] } serde-wasm-bindgen = "0.6" sourcemap = "6.4.1" swc_atoms = "0.6.0" +swc_cached = "0.3.18" swc_common = { version = "0.33.0", features = ["anyhow", "sourcemap"] } swc_ecma_ast = { version = "0.110.0", features = ["default", "serde"] } swc_ecma_codegen = "0.146.1" diff --git a/index.d.ts b/index.d.ts index 02566a4..cc43493 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1 +1,34 @@ -export * from './pkg/compiler'; +export { compile, start, prepareStackTrace } from './pkg/compiler'; + +declare interface JsMethodParameter { + name?: String; + index: number; + hasDefault: boolean; + isObjectPattern: boolean; + isArrayPattern: boolean; + isRestElement: boolean; +} + +declare interface JsMemberData { + memberIndex: number; + kind: 'method' | 'field' | 'accessor' | 'getter' | 'setter'; + name: string | symbol; + static?: boolean; + private?: boolean; + access?: { get?: () => any; set?: (v: any) => void }; + parameters?: JsMethodParameter[]; + docblock?: String; +} + +declare interface JsReflectionData { + fqcn: String; + className: String; + namespace?: String; + filename?: String; + members: JsMemberData[]; + docblock?: String; +} + +export function getReflectionData( + classIdOrValue: any, +): JsReflectionData | undefined; diff --git a/lib/reflection.js b/lib/reflection.js index 8ce5349..b2556ed 100644 --- a/lib/reflection.js +++ b/lib/reflection.js @@ -22,9 +22,54 @@ function docblockGetter(classId, memberIndex) { */ exports._ = function __jymfony_reflect(classId, memberIndex = undefined) { return (value, context) => { - const c = reflectionDataMap.get(classId) || { members: [] }; + const c = (() => { + const d = reflectionDataMap.get(classId); + if (d !== undefined) { + return d; + } + + const data = getInternalReflectionData(classId); + if (data === void 0) { + return { + fqcn: context.name, + className: context.name, + members: [], + }; + } + + const c = { ...data }; + c.members = []; + + return c; + })(); + if (context.kind === 'class') { context.metadata[reflectionSymbol] = classId; + if (memberIndex !== void 0) { + c.members.push({ + memberIndex, + kind: 'method', + name: 'constructor', + static: false, + private: false, + access: { get: () => value }, + get docblock() { + 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 })); + }, + }); + } + + reflectionDataMap.set(classId, c); return; } @@ -32,27 +77,18 @@ exports._ = function __jymfony_reflect(classId, memberIndex = undefined) { return; } - const data = getInternalReflectionData(classId); - if (data === void 0) { - return; - } - - if (context.kind === 'method') { - const getter = (() => { - if (context.access) { - return context.access.get; - } - - return () => value; - })(); - + if ( + context.kind === 'method' || + context.kind === 'getter' || + context.kind === 'setter' + ) { c.members.push({ memberIndex, kind: context.kind, name: context.name, static: context.static, private: context.private, - access: { get: getter }, + access: context.access, get parameters() { const data = getInternalReflectionData(classId); if (data === void 0) { diff --git a/package.json b/package.json index c521c4b..930c7dd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jymfony/compiler", - "version": "0.5.0-alpha.1", + "version": "0.5.0-alpha.2", "type": "commonjs", "author": "Alessandro Chitolina ", "license": "MIT", @@ -22,6 +22,8 @@ "files": [ "index.js", "index.d.ts", + "lib/_apply_decs_2203_r.js", + "lib/reflection.js", "pkg/compiler.js", "pkg/compiler.d.ts", "pkg/compiler_bg.wasm", diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 083436d..e6abad7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -19,75 +19,87 @@ mod util; pub const ES_VERSION: EsVersion = EsVersion::EsNext; -pub fn parse(code: String, filename: Option<&str>) -> Result { - let source_map: Lrc = Default::default(); - let source_file = source_map.new_source_file( - filename - .map(|f| FileName::Real(PathBuf::from(f))) - .unwrap_or_else(|| FileName::Anon), - code, - ); - - let comments = SingleThreadedComments::default(); - let is_typescript = filename.is_some_and(|f| f.ends_with(".ts")); - let syntax = if is_typescript { - Syntax::Typescript(TsConfig { - tsx: false, - decorators: true, - dts: false, - no_early_errors: false, - disallow_ambiguous_jsx_like: false, - }) - } else { - 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, - }) - }; - - let lexer = Lexer::new( - syntax, - ES_VERSION, - StringInput::from(&*source_file), - Some(&comments), - ); - - let mut parser = Parser::new_from(lexer); - let parse_result = parser.parse_program(); - let orig_srcmap = sourcemap::get_orig_src_map(&source_file).unwrap_or_default(); - - if let Ok(program) = parse_result { - let errors = parser.take_errors(); - if !errors.is_empty() { - let e = errors.first().unwrap(); - Err(SyntaxError::from_parser_error(e, &source_file).into()) +pub trait CodeParser { + fn parse_program(self, filename: Option<&str>) -> Result; +} + +impl CodeParser for String { + fn parse_program(self, filename: Option<&str>) -> Result { + self.as_str().parse_program(filename) + } +} + +impl CodeParser for &str { + fn parse_program(self, filename: Option<&str>) -> Result { + let source_map: Lrc = Default::default(); + let source_file = source_map.new_source_file( + filename + .map(|f| FileName::Real(PathBuf::from(f))) + .unwrap_or_else(|| FileName::Anon), + self.to_string(), + ); + + let comments = SingleThreadedComments::default(); + let is_typescript = filename.is_some_and(|f| f.ends_with(".ts")); + let syntax = if is_typescript { + Syntax::Typescript(TsConfig { + tsx: false, + decorators: true, + dts: false, + no_early_errors: false, + disallow_ambiguous_jsx_like: false, + }) } else { - Ok(Program { - source_map, - orig_srcmap, - filename: filename.map(|f| f.to_string()), - program, - comments: Rc::new(comments), - is_typescript, + Syntax::Es(EsConfig { + jsx: false, + fn_bind: false, + decorators: true, + decorators_before_export: false, + export_default_from: false, + import_attributes: true, + allow_super_outside_method: false, + allow_return_outside_function: true, + auto_accessors: true, + explicit_resource_management: true, }) + }; + + let lexer = Lexer::new( + syntax, + ES_VERSION, + StringInput::from(&*source_file), + Some(&comments), + ); + + let mut parser = Parser::new_from(lexer); + let parse_result = parser.parse_program(); + let orig_srcmap = sourcemap::get_orig_src_map(&source_file).unwrap_or_default(); + + if let Ok(program) = parse_result { + let errors = parser.take_errors(); + if !errors.is_empty() { + let e = errors.first().unwrap(); + Err(SyntaxError::from_parser_error(e, &source_file).into()) + } else { + Ok(Program { + source_map, + orig_srcmap, + filename: filename.map(|f| f.to_string()), + program, + comments: Rc::new(comments), + is_typescript, + }) + } + } else { + let e = parse_result.unwrap_err(); + Err(SyntaxError::from_parser_error(&e, &source_file).into()) } - } else { - let e = parse_result.unwrap_err(); - Err(SyntaxError::from_parser_error(&e, &source_file).into()) } } #[cfg(test)] mod tests { - use super::parse; + use super::CodeParser; use crate::parser::transformers::decorator_2022_03; use crate::testing::exec_tr; use crate::testing::uuid::reset_test_uuid; @@ -165,7 +177,7 @@ export class y { } "#; - let result = parse(code.into(), None).expect("failed to parse"); + let result = code.parse_program(None).expect("failed to parse_program"); let compiled = result .compile(Default::default()) .expect("failed to compile"); @@ -191,7 +203,7 @@ _export(exports, { } }); var _initClass, __anonymous_xΞ1, _dec, __jymfony_JObject, _dec1, _initProto, _dec2, _initClass1, __jymfony_JObject1, _dec3, _initProto1, _dec4, _initClass2, __jymfony_JObject2, _dec5, _initProto2; -_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000"), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0); +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0); const p = (class _anonymous_xΞ1 extends (__jymfony_JObject = __jymfony.JObject) { static #_ = { e: [_initProto], c: [__anonymous_xΞ1, _initClass] } = _apply_decs_2203_r(this, [ [ @@ -212,7 +224,7 @@ const p = (class _anonymous_xΞ1 extends (__jymfony_JObject = __jymfony.JObject) static #_2 = _initClass(); }, __anonymous_xΞ1); let _x; -_dec2 = __jymfony_reflect("00000000-0000-0000-0000-000000000001"), _dec3 = __jymfony_reflect("00000000-0000-0000-0000-000000000001", 0); +_dec2 = __jymfony_reflect("00000000-0000-0000-0000-000000000001", void 0), _dec3 = __jymfony_reflect("00000000-0000-0000-0000-000000000001", 0); class x extends (__jymfony_JObject1 = __jymfony.JObject) { static #_ = { e: [_initProto1], c: [_x, _initClass1] } = _apply_decs_2203_r(this, [ [ @@ -233,7 +245,7 @@ class x extends (__jymfony_JObject1 = __jymfony.JObject) { static #_2 = _initClass1(); } let _y; -_dec4 = __jymfony_reflect("00000000-0000-0000-0000-000000000002"), _dec5 = __jymfony_reflect("00000000-0000-0000-0000-000000000002", 0); +_dec4 = __jymfony_reflect("00000000-0000-0000-0000-000000000002", void 0), _dec5 = __jymfony_reflect("00000000-0000-0000-0000-000000000002", 0); class y extends (__jymfony_JObject2 = __jymfony.JObject) { static #_ = { e: [_initProto2], c: [_y, _initClass2] } = _apply_decs_2203_r(this, [ [ @@ -250,7 +262,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"), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000003", 0); + _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) { static #_ = { e: [_initProto], c: [__anonymous_xΞ2, _initClass] } = _apply_decs_2203_r(this, [ [ @@ -282,7 +294,7 @@ class y extends (__jymfony_JObject2 = __jymfony.JObject) { let code = r#" new class ext impl test {[]} "#; - let result = parse(code.into(), Some("a.js")); + let result = code.parse_program(Some("a.js")); assert!(result.is_err()); let error = result.unwrap_err(); @@ -370,7 +382,7 @@ export default @logger.logged class x { } "#; - let parsed = parse(code.into(), Some("a.js")).unwrap(); + let parsed = code.parse_program(Some("a.js")).unwrap(); assert!(parsed.program.is_module()); assert!(parsed.program.as_module().unwrap().body.iter().any(|s| s diff --git a/src/parser/program.rs b/src/parser/program.rs index 0848b53..cc9ee30 100644 --- a/src/parser/program.rs +++ b/src/parser/program.rs @@ -1,6 +1,7 @@ use crate::parser::transformers::{ - anonymous_expr, class_jobject, class_reflection_decorators, decorator_2022_03, - remove_assert_calls, resolve_self_identifiers, static_blocks, + anonymous_expr, class_define_fields, class_jobject, class_reflection_decorators, + decorator_2022_03, optional_import, remove_assert_calls, resolve_self_identifiers, + static_blocks, wrap_in_function, }; use crate::stack::register_source_map; use base64::prelude::BASE64_STANDARD; @@ -8,6 +9,7 @@ use base64::Engine; use sourcemap::SourceMap; use std::fmt::{Debug, Formatter}; use std::rc::Rc; +use swc_cached::regex::CachedRegex; use swc_common::comments::SingleThreadedComments; use swc_common::sync::Lrc; use swc_common::{chain, BytePos, LineCol, Mark, GLOBALS}; @@ -15,10 +17,12 @@ use swc_ecma_codegen::text_writer::JsWriter; 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::resolver; use swc_ecma_transforms_compat::es2020::{nullish_coalescing, optional_chaining}; use swc_ecma_transforms_module::common_js; +use swc_ecma_transforms_module::util::{ImportInterop, Lazy, LazyObjectConfig}; use swc_ecma_transforms_typescript::strip; use swc_ecma_visit::{Fold, FoldWith}; @@ -26,6 +30,7 @@ use swc_ecma_visit::{Fold, FoldWith}; pub struct CompileOptions { pub debug: bool, pub namespace: Option, + pub as_function: bool, } pub struct Program { @@ -51,72 +56,550 @@ impl Debug for Program { impl Program { pub fn compile(self, opts: CompileOptions) -> std::io::Result { GLOBALS.set(&Default::default(), || { - let unresolved_mark = Mark::new(); - let top_level_mark = Mark::new(); - let static_block_mark = Mark::new(); - let available_set = FeatureFlag::all(); - - let mut transformers: Box = Box::new(chain!( - resolver(unresolved_mark, top_level_mark, self.is_typescript), - anonymous_expr(), - class_reflection_decorators( - self.filename.as_deref(), - opts.namespace.as_deref(), - self.comments.clone() - ), - strip(top_level_mark), - nullish_coalescing(Default::default()), - optional_chaining(Default::default(), unresolved_mark), - resolve_self_identifiers(unresolved_mark), - class_jobject(), - decorator_2022_03(), - static_blocks(static_block_mark), - common_js( - unresolved_mark, - Default::default(), - available_set, - Some(&self.comments) - ), - hygiene(), - fixer(Some(&self.comments)) - )); - - if !opts.debug { - transformers = Box::new(chain!(transformers, remove_assert_calls())); - } - - let program = self.program.fold_with(transformers.as_mut()); - let mut buf = vec![]; - let mut sm: Vec<(BytePos, LineCol)> = vec![]; - - { - let mut emitter = Emitter { - cfg: Default::default(), - cm: self.source_map.clone(), - comments: Some(&self.comments), - wr: JsWriter::new(Default::default(), "\n", &mut buf, Some(&mut sm)), + let helpers = Helpers::new(false); + HELPERS.set(&helpers, || { + let unresolved_mark = Mark::new(); + let top_level_mark = Mark::new(); + let static_block_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(), + ], + }), + ..Default::default() }; - emitter.emit_program(&program)? - }; + let mut transformers: Box = Box::new(chain!( + resolver(unresolved_mark, top_level_mark, self.is_typescript), + anonymous_expr(), + class_reflection_decorators( + self.filename.as_deref(), + opts.namespace.as_deref(), + self.comments.clone() + ), + strip(top_level_mark), + optional_import(unresolved_mark), + nullish_coalescing(Default::default()), + optional_chaining(Default::default(), unresolved_mark), + 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), + )); - let mut src = String::from_utf8(buf).expect("non-utf8?"); - if let Some(f) = self.filename.as_deref() { - let srcmap = self - .source_map - .build_source_map_from(&sm, self.orig_srcmap.as_ref()); + if !opts.debug { + transformers = Box::new(chain!(transformers, remove_assert_calls())); + } - register_source_map(f.to_string(), srcmap.clone()); + if opts.as_function { + transformers = Box::new(chain!(transformers, wrap_in_function())); + } + let program = self.program.fold_with(transformers.as_mut()); let mut buf = vec![]; - srcmap.to_writer(&mut buf).ok(); + let mut sm: Vec<(BytePos, LineCol)> = vec![]; + + { + let mut emitter = Emitter { + cfg: Default::default(), + cm: self.source_map.clone(), + comments: Some(&self.comments), + wr: JsWriter::new(Default::default(), "\n", &mut buf, Some(&mut sm)), + }; + + emitter.emit_program(&program)? + }; + + let mut src = String::from_utf8(buf).expect("non-utf8?"); + if let Some(f) = self.filename.as_deref() { + let srcmap = self + .source_map + .build_source_map_from(&sm, self.orig_srcmap.as_ref()); + + register_source_map(f.to_string(), srcmap.clone()); - let res = BASE64_STANDARD.encode(buf); - src += "\n\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,"; - src += &res; - } + let mut buf = vec![]; + srcmap.to_writer(&mut buf).ok(); - Ok(src) + let res = BASE64_STANDARD.encode(buf); + src += "\n\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,"; + src += &res; + } + + Ok(src) + }) }) } } + +#[cfg(test)] +mod tests { + use crate::parser::{CodeParser, CompileOptions}; + use crate::testing::uuid::reset_test_uuid; + + #[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(CompileOptions { + debug: false, + namespace: None, + as_function: false, + })?; + + assert_eq!( + code, + r#""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(); + console.log(this); + } + static #_2 = _initClass(); +} +const _default = _TestClass; +"# + ); + + Ok(()) + } + + #[test] + pub fn compile_optional_imports_correctly() { + reset_test_uuid(); + + let code = r#" +import Redis from 'ioredis' with { optional: true }; +import { parse as urlParse } from 'url'; + +const RedisCluster = Redis ? Redis.Cluster : undefined; +const parseHosts = (params, dsn) => {}; + +/** + * @memberOf Jymfony.Component.Cache.Adapter + */ +export default class RedisAdapter {} +"#; + let program = code.parse_program(None).unwrap(); + let code = program + .compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + }) + .unwrap(); + + assert_eq!( + code, + r#""use strict"; +function _interop_require_default(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; +} +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, /** + * @memberOf Jymfony.Component.Cache.Adapter + */ "default", { + enumerable: true, + get: function() { + return _default; + } +}); +var _initClass, _RedisAdapter, _dec, __jymfony_JObject; +const _r = function() { + try { + return require("ioredis"); + } catch { + return void 0; + } +}(); +const Redis = void 0 !== _r ? _interop_require_default(_r, true).default : void 0; +const RedisCluster = Redis ? Redis.Cluster : undefined; +const parseHosts = (params, dsn)=>{}; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0); +class RedisAdapter extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = { c: [_RedisAdapter, _initClass] } = _apply_decs_2203_r(this, [], [ + _dec + ], __jymfony_JObject); + static #_2 = _initClass(); +} +const _default = _RedisAdapter; +"# + ); + + let code = r#" +import Redis, { Cluster as RedisCluster } from 'ioredis' with { optional: true }; + +class conn { + constructor() { + this._cluster = new RedisCluster(); + this._redis = Redis; + } +} +"#; + let program = code.parse_program(None).unwrap(); + let code = program + .compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + }) + .unwrap(); + + assert_eq!( + code, + r#""use strict"; +function _interop_require_default(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; +} +var _dec, _initClass, __jymfony_JObject; +const _r = function() { + try { + return require("ioredis"); + } catch { + return void 0; + } +}(); +const Redis = void 0 !== _r ? _interop_require_default(_r, true).default : void 0; +const RedisCluster = _r === null || _r === void 0 ? void 0 : _r.Cluster; +let _conn; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000001", 0); +class conn extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = { c: [_conn, _initClass] } = _apply_decs_2203_r(this, [], [ + _dec + ], __jymfony_JObject); + constructor(){ + super(); + this._cluster = new RedisCluster(); + this._redis = Redis; + } + static #_2 = _initClass(); +} +"# + ); + } + + #[test] + pub fn should_compile_field_initialization() -> anyhow::Result<()> { + reset_test_uuid(); + + let program = r#" +const TestCase = Jymfony.Component.Testing.Framework.TestCase; + +export default class ClassLoaderTest extends TestCase { + /** + * @type {Jymfony.Component.Autoloader.ClassLoader} + */ + _classLoader; +} +"# + .parse_program(None)?; + + let compiled = program.compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + })?; + + assert_eq!( + compiled, + r#""use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "default", { + enumerable: true, + get: function() { + return _default; + } +}); +var _initClass, _ClassLoaderTest, _dec, _TestCase, _dec1, /** + * @type {Jymfony.Component.Autoloader.ClassLoader} + */ _init__classLoader; +const TestCase = Jymfony.Component.Testing.Framework.TestCase; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0); +class ClassLoaderTest extends (_TestCase = TestCase) { + static #_ = { e: [_init__classLoader], c: [_ClassLoaderTest, _initClass] } = _apply_decs_2203_r(this, [ + [ + _dec1, + 0, + "_classLoader" + ] + ], [ + _dec + ], _TestCase); + 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; +"# + ); + + Ok(()) + } + + #[test] + pub fn should_compile_imports_correctly() -> anyhow::Result<()> { + reset_test_uuid(); + + let program = r#" +import JsonFileLoaderTest from './JsonFileLoaderTest'; +const YamlFileLoader = Jymfony.Component.Validator.Mapping.Loader.YamlFileLoader; + +export default class YamlFileLoaderTest extends JsonFileLoaderTest { +} +"# + .parse_program(None)?; + + let compiled = program.compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + })?; + + assert_eq!( + compiled, + r#""use strict"; +function _interop_require_default(obj) { + return obj && obj.__esModule ? obj : { + default: obj + }; +} +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, "default", { + enumerable: true, + get: function() { + return _default; + } +}); +function _JsonFileLoaderTest() { + const data = /*#__PURE__*/ _interop_require_default(require("./JsonFileLoaderTest")); + _JsonFileLoaderTest = function() { + return data; + }; + return data; +} +var _initClass, _YamlFileLoaderTest, _dec, _JsonFileLoaderTest1; +const YamlFileLoader = Jymfony.Component.Validator.Mapping.Loader.YamlFileLoader; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0); +class YamlFileLoaderTest extends (_JsonFileLoaderTest1 = _JsonFileLoaderTest().default) { + static #_ = { c: [_YamlFileLoaderTest, _initClass] } = _apply_decs_2203_r(this, [], [ + _dec + ], _JsonFileLoaderTest1); + static #_2 = _initClass(); +} +const _default = _YamlFileLoaderTest; +"# + ); + + Ok(()) + } + + #[test] + pub fn should_compile_decorated_class() -> anyhow::Result<()> { + reset_test_uuid(); + + let program = r#" +const Annotation = Jymfony.Component.Autoloader.Decorator.Annotation; + +/** + * @memberOf Foo.Decorators + */ +export default +@Annotation() +class TestAnnotation { + __construct(value) { + this._value = value; + } + + get value() { + return this._value; + } +} +"# + .parse_program(None)?; + + let compiled = program.compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + })?; + + assert_eq!( + compiled, + r#""use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, /** + * @memberOf Foo.Decorators + */ "default", { + enumerable: true, + get: function() { + return _default; + } +}); +var _initClass, _TestAnnotation, _dec, _dec1, __jymfony_JObject, _dec2, _dec3, _initProto; +const Annotation = Jymfony.Component.Autoloader.Decorator.Annotation; +_dec = Annotation(), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", void 0), _dec2 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 0), _dec3 = __jymfony_reflect("00000000-0000-0000-0000-000000000000", 1); +class TestAnnotation extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = { e: [_initProto], c: [_TestAnnotation, _initClass] } = _apply_decs_2203_r(this, [ + [ + _dec2, + 2, + "__construct" + ], + [ + _dec3, + 3, + "value" + ] + ], [ + _dec, + _dec1 + ], __jymfony_JObject); + constructor(...args){ + super(...args); + _initProto(this); + } + __construct(value) { + this._value = value; + } + get value() { + return this._value; + } + static #_2 = _initClass(); +} +const _default = _TestAnnotation; +"# + ); + + let program = r#" +const BufferingLogger = Jymfony.Component.Debug.BufferingLogger; +const ErrorHandler = Jymfony.Component.Debug.ErrorHandler; +const Timeout = Jymfony.Component.Debug.Timeout; +const UnhandledRejectionException = Jymfony.Component.Debug.Exception.UnhandledRejectionException; + +/** + * @memberOf Jymfony.Component.Debug + */ +export default class Debug { + static enable() { + __jymfony.autoload.debug = true; + + process.on('unhandledRejection', (reason, p) => { + throw new UnhandledRejectionException(p, reason instanceof Error ? reason : undefined); + }); + + __jymfony.ManagedProxy.enableDebug(); + Timeout.enable(); + ErrorHandler.register(new ErrorHandler(new BufferingLogger(), true)); + } +} +"# + .parse_program(None)?; + + let compiled = program.compile(CompileOptions { + debug: false, + namespace: None, + as_function: false, + })?; + + assert_eq!( + compiled, + r#""use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); +Object.defineProperty(exports, /** + * @memberOf Jymfony.Component.Debug + */ "default", { + enumerable: true, + get: function() { + return _default; + } +}); +var _initClass, _Debug, _dec, __jymfony_JObject, _dec1, _initStatic; +const BufferingLogger = Jymfony.Component.Debug.BufferingLogger; +const ErrorHandler = Jymfony.Component.Debug.ErrorHandler; +const Timeout = Jymfony.Component.Debug.Timeout; +const UnhandledRejectionException = Jymfony.Component.Debug.Exception.UnhandledRejectionException; +_dec = __jymfony_reflect("00000000-0000-0000-0000-000000000001", void 0), _dec1 = __jymfony_reflect("00000000-0000-0000-0000-000000000001", 0); +class Debug extends (__jymfony_JObject = __jymfony.JObject) { + static #_ = (()=>{ + ({ e: [_initStatic], c: [_Debug, _initClass] } = _apply_decs_2203_r(this, [ + [ + _dec1, + 8, + "enable" + ] + ], [ + _dec + ], __jymfony_JObject)); + _initStatic(this); + })(); + static enable() { + __jymfony.autoload.debug = true; + process.on('unhandledRejection', (reason, p)=>{ + throw new UnhandledRejectionException(p, reason instanceof Error ? reason : undefined); + }); + __jymfony.ManagedProxy.enableDebug(); + Timeout.enable(); + ErrorHandler.register(new ErrorHandler(new BufferingLogger(), true)); + } + static #_2 = _initClass(); +} +const _default = _Debug; +"# + ); + + Ok(()) + } +} diff --git a/src/parser/sourcemap.rs b/src/parser/sourcemap.rs index f2fbc2a..0a545d5 100644 --- a/src/parser/sourcemap.rs +++ b/src/parser/sourcemap.rs @@ -8,13 +8,20 @@ use url::Url; fn read_inline_sourcemap(data_url: Option<&str>) -> Result, Error> { match data_url { Some(data_url) => { - let url = Url::parse(data_url) - .with_context(|| format!("failed to parse inline source map url\n{}", data_url))?; + let url = Url::parse(data_url).with_context(|| { + format!( + "failed to parse_program inline source map url\n{}", + data_url + ) + })?; let idx = match url.path().find("base64,") { Some(v) => v, None => { - bail!("failed to parse inline source map: not base64: {:?}", url) + bail!( + "failed to parse_program inline source map: not base64: {:?}", + url + ) } }; @@ -29,7 +36,7 @@ fn read_inline_sourcemap(data_url: Option<&str>) -> Result { - bail!("failed to parse inline source map: `sourceMappingURL` not found") + bail!("failed to parse_program inline source map: `sourceMappingURL` not found") } } } diff --git a/src/parser/transformers/class_define_fields.rs b/src/parser/transformers/class_define_fields.rs new file mode 100644 index 0000000..e6add0f --- /dev/null +++ b/src/parser/transformers/class_define_fields.rs @@ -0,0 +1,171 @@ +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_jobject.rs b/src/parser/transformers/class_jobject.rs index a07143c..4e44638 100644 --- a/src/parser/transformers/class_jobject.rs +++ b/src/parser/transformers/class_jobject.rs @@ -10,16 +10,6 @@ lazy_static! { let obj_expr = ident("__jymfony"); let prop = ident("JObject"); - MemberExpr { - span: DUMMY_SP, - obj: Box::new(Expr::Ident(obj_expr)), - prop: MemberProp::Ident(prop), - } - }; - 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)), @@ -45,161 +35,24 @@ impl VisitMut for ClassJObject { } n.super_class = Some(Box::new(Expr::Member(JOBJECT_ACCESSOR.clone()))); - let mut stmts = vec![]; - let mut initializers = vec![]; - - for mut member in n.body.drain(..) { - match &mut member { - ClassMember::Constructor(constructor) => { - if let Some(block) = &mut constructor.body { - let call_expr = CallExpr { - span: DUMMY_SP, - callee: Callee::Super(Super::dummy()), - args: vec![], - type_args: None, - }; - - let call_super = Stmt::Expr(ExprStmt { - span: DUMMY_SP, - expr: Box::new(Expr::Call(call_expr)), - }); - - block.stmts.insert(0, call_super); - } - } - ClassMember::ClassProp(prop) => { - 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 { + for mut member in n.body.iter_mut() { + if let ClassMember::Constructor(constructor) = &mut member { + if let Some(block) = &mut constructor.body { + let call_expr = CallExpr { 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::EqEqEq, - left: Box::new(undefined()), - 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())), - }], + callee: Callee::Super(Super::dummy()), + args: vec![], 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 { + let call_super = Stmt::Expr(ExprStmt { span: DUMMY_SP, - obj: Box::new(Expr::This(ThisExpr::dummy())), - prop: prop_name, - }))), - right: value.unwrap_or_else(|| Box::new(undefined())), - }); - - block_stmts.push(Stmt::Expr(ExprStmt { - span: p.span, - expr: Box::new(e), - })); - } + expr: Box::new(Expr::Call(call_expr)), + }); - 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, - })); + block.stmts.insert(0, call_super); + } + }; } } } diff --git a/src/parser/transformers/class_reflection_decorators.rs b/src/parser/transformers/class_reflection_decorators.rs index e8e289f..a1b7bf8 100644 --- a/src/parser/transformers/class_reflection_decorators.rs +++ b/src/parser/transformers/class_reflection_decorators.rs @@ -1,12 +1,12 @@ use crate::generate_uuid; use crate::parser::util::ident; use crate::reflection::{register_class, ReflectionData}; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::rc::Rc; use swc_common::comments::{CommentKind, Comments}; use swc_common::{Span, Spanned, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::ExprFactory; +use swc_ecma_utils::{undefined, ExprFactory}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; pub fn class_reflection_decorators<'a, C: Comments + 'a>( @@ -35,7 +35,7 @@ impl<'a, C: Comments> ClassReflectionDecorators<'a, C> { .flatten() .rev() .find_map(|cmt| { - if cmt.kind == CommentKind::Block && cmt.text.starts_with("*") { + if cmt.kind == CommentKind::Block && cmt.text.starts_with('*') { Some(format!("/*{}*/", cmt.text)) } else { None @@ -45,7 +45,7 @@ impl<'a, C: Comments> ClassReflectionDecorators<'a, C> { fn process_class(&self, n: &mut Class, name: Ident) { let id = generate_uuid(); - let mut docblock = HashMap::new(); + let mut docblock = FxHashMap::default(); if n.span != DUMMY_SP { docblock.insert(n.span, self.get_element_docblock(n.span)); } @@ -55,7 +55,15 @@ impl<'a, C: Comments> ClassReflectionDecorators<'a, C> { expr: Box::new(Expr::Call(CallExpr { span: DUMMY_SP, callee: ident("__jymfony_reflect").as_callee(), - args: vec![id.to_string().as_arg()], + args: vec![ + id.to_string().as_arg(), + n.body + .iter() + .enumerate() + .find(|(_, m)| matches!(m, ClassMember::Constructor(_))) + .map(|(idx, _)| idx.as_arg()) + .unwrap_or_else(|| undefined(DUMMY_SP).as_arg()), + ], type_args: None, })), }); @@ -126,7 +134,9 @@ impl VisitMut for ClassReflectionDecorators<'_, C> { } fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) { - let Some(ident) = n.ident.clone() else { panic!("anonymous_expr transformer must be called before class_reflection_decorator"); }; + 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); n.visit_mut_children_with(self); } diff --git a/src/parser/transformers/decorator_2022_03.rs b/src/parser/transformers/decorator_2022_03.rs index d24016d..1d83b0a 100644 --- a/src/parser/transformers/decorator_2022_03.rs +++ b/src/parser/transformers/decorator_2022_03.rs @@ -3,16 +3,17 @@ use std::{ mem::{take, transmute}, }; -use crate::parser::util::{ident, undefined}; +use crate::parser::util::ident; use rustc_hash::FxHashMap; use swc_atoms::JsWord; use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP}; use swc_ecma_ast::Expr::Bin; use swc_ecma_ast::*; +use swc_ecma_transforms_base::helper_expr; use swc_ecma_utils::{ alias_ident_for, constructor::inject_after_super, default_constructor, prepend_stmt, - private_ident, prop_name_to_expr_value, quote_ident, replace_ident, ExprFactory, IdentExt, - IdentRenamer, + private_ident, prop_name_to_expr_value, quote_ident, replace_ident, undefined, ExprFactory, + IdentExt, IdentRenamer, }; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; @@ -228,7 +229,7 @@ impl Decorator202203 { span: DUMMY_SP, op: BinaryOp::NotEqEq, left: Box::new(Expr::Ident(init.clone())), - right: Box::new(undefined()), + right: undefined(DUMMY_SP), })), cons: Box::new(Stmt::Expr(ExprStmt { span: DUMMY_SP, @@ -246,7 +247,7 @@ impl Decorator202203 { .as_callee(), args: vec![ if is_constructor && self.state.super_class.is_some() { - undefined().as_arg() + undefined(DUMMY_SP).as_arg() } else { ThisExpr::dummy().as_arg() }, @@ -266,7 +267,7 @@ impl Decorator202203 { ClassMember::Constructor(..) => { vec![ Some(CLASS.as_arg()), - Some(undefined().as_arg()), + Some(undefined(DUMMY_SP).as_arg()), Some(0_usize.as_arg()), ] } @@ -315,7 +316,7 @@ impl Decorator202203 { dec, Some(PARAM.as_arg()), name.map(|n| n.sym.as_arg()) - .or_else(|| Some(undefined().as_arg())), + .or_else(|| Some(undefined(DUMMY_SP).as_arg())), Some(i.as_arg()), Some(if p.pat.is_rest() { 1_usize } else { 0_usize }.as_arg()), func, @@ -927,21 +928,11 @@ impl Decorator202203 { ); } - let identity = Box::new(Expr::Arrow(ArrowExpr { - span: DUMMY_SP, - params: vec![Pat::Ident(ident("x").into())], - body: Box::new(BlockStmtOrExpr::Expr(Box::new(Expr::Ident(ident("x"))))), - is_async: false, - is_generator: false, - type_params: None, - return_type: None, - })); - let class = Box::new(Class { span: DUMMY_SP, decorators: Vec::new(), body: c.class.body.take(), - super_class: Some(identity), + super_class: Some(Box::new(helper_expr!(identity))), is_abstract: Default::default(), type_params: Default::default(), super_type_params: Default::default(), @@ -1778,16 +1769,6 @@ impl VisitMut for Decorator202203 { s.visit_mut_children_with(self); } - ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { - span: _, - decl: DefaultDecl::Class(c), - })) => { - if !c.class.decorators.is_empty() { - self.handle_class_expr(&mut c.class, c.ident.as_ref()); - } - - s.visit_mut_children_with(self); - } _ => { s.visit_mut_children_with(self); } @@ -1800,7 +1781,64 @@ impl VisitMut for Decorator202203 { let mut new = Vec::with_capacity(n.len()); for mut n in n.take() { + if let ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { + span, + decl: DefaultDecl::Class(c), + })) = &mut n + { + if !c.class.decorators.is_empty() { + let new_class_name = self.handle_class_expr(&mut c.class, c.ident.as_ref()); + c.visit_mut_children_with(self); + + if !self.extra_lets.is_empty() { + new.push( + Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + decls: self.extra_lets.take(), + declare: false, + }))) + .into(), + ) + } + if !self.pre_class_inits.is_empty() { + new.push( + Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Expr::from_exprs(self.pre_class_inits.take()), + }) + .into(), + ) + } + + let c = c.take(); + let stmt = if let Some(i) = c.ident { + Stmt::Decl(Decl::Class(ClassDecl { + ident: i, + class: c.class, + declare: false, + })) + } else { + Stmt::Expr(ExprStmt { + span: *span, + expr: Box::new(Expr::Class(c)), + }) + }; + + new.push(ModuleItem::Stmt(stmt)); + new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr( + ExportDefaultExpr { + span: *span, + expr: Box::new(Expr::Ident(new_class_name)), + }, + ))); + + continue; + } + } + n.visit_mut_with(self); + if !self.extra_lets.is_empty() { new.push( Stmt::Decl(Decl::Var(Box::new(VarDecl { @@ -1821,6 +1859,7 @@ impl VisitMut for Decorator202203 { .into(), ) } + new.push(n.take()); } @@ -1866,24 +1905,18 @@ impl VisitMut for Decorator202203 { for mut n in n.body.take() { n.visit_mut_with(self); if !self.extra_lets.is_empty() { - new.push( - Stmt::Decl(Decl::Var(Box::new(VarDecl { - span: DUMMY_SP, - kind: VarDeclKind::Let, - decls: self.extra_lets.take(), - declare: false, - }))) - .into(), - ) + new.push(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + decls: self.extra_lets.take(), + declare: false, + })))) } if !self.pre_class_inits.is_empty() { - new.push( - Stmt::Expr(ExprStmt { - span: DUMMY_SP, - expr: Expr::from_exprs(self.pre_class_inits.take()), - }) - .into(), - ) + new.push(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Expr::from_exprs(self.pre_class_inits.take()), + })) } new.push(n.take()); } diff --git a/src/parser/transformers/mod.rs b/src/parser/transformers/mod.rs index d494367..69bf9c3 100644 --- a/src/parser/transformers/mod.rs +++ b/src/parser/transformers/mod.rs @@ -1,15 +1,21 @@ mod anonymous_expr; +mod class_define_fields; mod class_jobject; mod class_reflection_decorators; mod decorator_2022_03; +mod optional_import; mod remove_assert_calls; mod resolve_self_identifiers; 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 optional_import::optional_import; pub(crate) use remove_assert_calls::remove_assert_calls; pub(crate) use resolve_self_identifiers::resolve_self_identifiers; pub(crate) use static_blocks::static_blocks; +pub(crate) use wrap_in_function::wrap_in_function; diff --git a/src/parser/transformers/optional_import.rs b/src/parser/transformers/optional_import.rs new file mode 100644 index 0000000..6a6f42b --- /dev/null +++ b/src/parser/transformers/optional_import.rs @@ -0,0 +1,301 @@ +use crate::parser::util::ident; +use swc_atoms::JsWord; +use swc_common::util::take::Take; +use swc_common::{Mark, Span, DUMMY_SP}; +use swc_ecma_ast::*; +use swc_ecma_transforms_base::enable_helper; +use swc_ecma_utils::{private_ident, quote_ident, undefined, ExprFactory}; +use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; + +pub fn optional_import(unresolved_mark: Mark) -> impl VisitMut + Fold { + as_folder(OptionalImport { + unresolved_mark, + ..Default::default() + }) +} + +#[derive(Default)] +struct OptionalImport { + unresolved_mark: Mark, + optional_imports: Vec, +} + +impl OptionalImport { + pub(crate) fn make_require_call( + &self, + unresolved_mark: Mark, + src: JsWord, + src_span: Span, + ) -> Expr { + Expr::Call(CallExpr { + span: DUMMY_SP, + callee: quote_ident!(DUMMY_SP.apply_mark(unresolved_mark), "require").as_callee(), + args: vec![Lit::Str(Str { + span: src_span, + raw: None, + value: src, + }) + .as_arg()], + + type_args: Default::default(), + }) + } +} + +impl OptionalImport { + fn wrap_in_try(expr: Expr) -> Stmt { + Stmt::Try(Box::new(TryStmt { + span: DUMMY_SP, + block: BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(expr)), + })], + }, + handler: Some(CatchClause { + span: DUMMY_SP, + param: None, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(undefined(DUMMY_SP)), + })], + }, + }), + finalizer: None, + })) + } +} + +impl VisitMut for OptionalImport { + noop_visit_mut_type!(); + + fn visit_mut_module_item(&mut self, n: &mut ModuleItem) { + n.visit_mut_children_with(self); + + let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = n else { + return; + }; + let Some(w) = &i.with else { + return; + }; + + let optional = w.props.iter().find(|p| { + p.as_prop() + .and_then(|p| p.as_key_value()) + .map(|kv| { + kv.key.as_ident().is_some_and(|i| i.sym == "optional") + && kv.value.as_lit().is_some_and(|l| { + let Lit::Bool(b) = l else { + return false; + }; + b.value + }) + }) + .unwrap_or(false) + }); + + if optional.is_some() { + self.optional_imports.push(n.take()); + } + } + + fn visit_mut_module_items(&mut self, n: &mut Vec) { + n.visit_mut_children_with(self); + + let el = n + .iter() + .enumerate() + .find(|(_, item)| matches!(item, ModuleItem::Stmt(_))); + + let index = if let Some((idx, _)) = el { + idx + } else { + n.len() + }; + + n.splice( + index..index, + self.optional_imports + .take() + .into_iter() + .flat_map(|item| { + let decl = item.expect_module_decl(); + let import = decl.expect_import(); + + let mut stmts = vec![]; + + let require_call = self.make_require_call( + self.unresolved_mark, + import.src.value, + import.src.span, + ); + if import.specifiers.is_empty() { + stmts.push(Self::wrap_in_try(require_call)); + } else { + let req = private_ident!("_r"); + let call_req = Self::wrap_in_try(require_call); + + stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(req.clone().into()), + init: Some(Box::new(Expr::Call( + Expr::Fn(FnExpr { + ident: None, + function: Box::new(Function { + params: vec![], + decorators: vec![], + span: Default::default(), + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![call_req], + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + }) + .as_iife(), + ))), + definite: false, + }], + })))); + + for spec in import.specifiers.into_iter() { + match spec { + ImportSpecifier::Namespace(ImportStarAsSpecifier { + local, + span, + }) => { + let mark = enable_helper!(interop_require_wildcard); + let span = span.apply_mark(mark); + + let call_expr = + Expr::from(quote_ident!(span, "_interop_require_wildcard")) + .as_call( + span, + vec![req.clone().as_arg(), true.as_arg()], + ); + + let ternary = Expr::Cond(CondExpr { + span, + test: Box::new(Expr::Bin(BinExpr { + span, + op: BinaryOp::EqEqEq, + left: undefined(DUMMY_SP), + right: Box::new(Expr::Ident(req.clone())), + })), + cons: Box::new(call_expr), + alt: undefined(DUMMY_SP), + }); + + stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(local.into()), + init: Some(Box::new(ternary)), + definite: false, + }], + })))); + } + ImportSpecifier::Default(ImportDefaultSpecifier { + local, + span, + }) => { + let mark = enable_helper!(interop_require_default); + let span = span.apply_mark(mark); + + let call_expr = + Expr::from(quote_ident!(span, "_interop_require_default")) + .as_call( + span, + vec![req.clone().as_arg(), true.as_arg()], + ); + + let ternary = Expr::Cond(CondExpr { + span, + test: Box::new(Expr::Bin(BinExpr { + span, + op: BinaryOp::NotEqEq, + left: undefined(DUMMY_SP), + right: Box::new(Expr::Ident(req.clone())), + })), + cons: Box::new(Expr::Member(MemberExpr { + span, + obj: Box::new(call_expr), + prop: MemberProp::Ident(ident("default")), + })), + alt: undefined(DUMMY_SP), + }); + + stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(local.into()), + init: Some(Box::new(ternary)), + definite: false, + }], + })))); + } + ImportSpecifier::Named(ImportNamedSpecifier { + local, + imported, + span, + .. + }) => { + let prop = match imported { + None => MemberProp::Ident(local.clone()), + Some(ModuleExportName::Ident(i)) => MemberProp::Ident(i), + Some(ModuleExportName::Str(s)) => { + MemberProp::Computed(ComputedPropName { + span, + expr: Box::new(Expr::Lit(Lit::Str(s))), + }) + } + }; + + let access = Expr::OptChain(OptChainExpr { + span: DUMMY_SP, + optional: true, + base: Box::new(OptChainBase::Member(MemberExpr { + span, + obj: Box::new(Expr::Ident(req.clone())), + prop, + })), + }); + + stmts.push(Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(local.into()), + init: Some(Box::new(access)), + definite: false, + }], + })))); + } + } + } + } + + stmts + }) + .map(ModuleItem::Stmt), + ); + } +} diff --git a/src/parser/transformers/wrap_in_function.rs b/src/parser/transformers/wrap_in_function.rs new file mode 100644 index 0000000..029a4e7 --- /dev/null +++ b/src/parser/transformers/wrap_in_function.rs @@ -0,0 +1,136 @@ +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_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; + +lazy_static! {} + +pub fn wrap_in_function() -> impl VisitMut + Fold { + as_folder(WrapInFunction::default()) +} + +#[derive(Default)] +struct WrapInFunction {} + +impl VisitMut for WrapInFunction { + noop_visit_mut_type!(); + + fn visit_mut_module(&mut self, n: &mut Module) { + n.visit_mut_children_with(self); + + let stmts = n.body.take(); + debug_assert!( + stmts.iter().all(|m| m.is_stmt()), + "must be called after commonjs transformation" + ); + + let wrapped = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Paren(ParenExpr { + span: DUMMY_SP, + expr: Box::new(Expr::Fn(FnExpr { + ident: None, + function: Box::new(Function { + params: vec![ + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("exports").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("require").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("module").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("__filename").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("__dirname").into()), + }, + ], + decorators: vec![], + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: stmts.into_iter().map(|m| m.expect_stmt()).collect(), + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + })), + })), + }); + + n.body = vec![ModuleItem::Stmt(wrapped)]; + } + + fn visit_mut_script(&mut self, n: &mut Script) { + n.visit_mut_children_with(self); + + let body = n.body.take(); + let wrapped = Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Paren(ParenExpr { + span: DUMMY_SP, + expr: Box::new(Expr::Fn(FnExpr { + ident: None, + function: Box::new(Function { + params: vec![ + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("exports").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("require").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("module").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("__filename").into()), + }, + Param { + span: DUMMY_SP, + decorators: vec![], + pat: Pat::Ident(ident("__dirname").into()), + }, + ], + decorators: vec![], + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: body, + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + })), + })), + }); + + n.body = vec![wrapped]; + } +} diff --git a/src/parser/util.rs b/src/parser/util.rs index fdea819..fb03b86 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -4,7 +4,3 @@ use swc_ecma_ast::*; pub(crate) fn ident(word: &str) -> Ident { Ident::new(word.into(), DUMMY_SP) } - -pub(crate) fn undefined() -> Expr { - Expr::Ident(ident("undefined")) -} diff --git a/src/reflection/mod.rs b/src/reflection/mod.rs index 282de08..42328d9 100644 --- a/src/reflection/mod.rs +++ b/src/reflection/mod.rs @@ -1,6 +1,6 @@ #[cfg(not(test))] use lazy_static::lazy_static; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::sync::{Arc, RwLock}; use swc_common::Span; use swc_ecma_ast::{Class, Ident}; @@ -8,12 +8,12 @@ use uuid::Uuid; #[cfg(test)] thread_local! { - static CLASS_REGISTRY: RwLock>> = RwLock::new(Default::default()); + static CLASS_REGISTRY: RwLock>> = RwLock::new(Default::default()); } #[cfg(not(test))] lazy_static! { - static ref CLASS_REGISTRY: RwLock>> = + static ref CLASS_REGISTRY: RwLock>> = RwLock::new(Default::default()); } @@ -22,7 +22,7 @@ pub struct ReflectionData { pub name: Ident, pub filename: Option, pub namespace: Option, - pub docblock: HashMap>, + pub docblock: FxHashMap>, } impl ReflectionData { @@ -31,7 +31,7 @@ impl ReflectionData { name: Ident, filename: Option<&str>, namespace: Option<&str>, - docblock: HashMap>, + docblock: FxHashMap>, ) -> Self { Self { class: class.clone(), @@ -49,7 +49,7 @@ pub(crate) fn register_class(class_id: &Uuid, data: ReflectionData) { let mut registry = CLASS_REGISTRY.write().unwrap(); debug_assert!(registry.get(class_id).is_none()); - registry.insert(class_id.clone(), Arc::new(data)); + registry.insert(*class_id, Arc::new(data)); } #[cfg(test)] @@ -58,12 +58,12 @@ pub(crate) fn register_class(class_id: &Uuid, data: ReflectionData) { let mut registry = lock.write().unwrap(); debug_assert!(registry.get(class_id).is_none()); - registry.insert(class_id.clone(), Arc::new(data)); + registry.insert(*class_id, Arc::new(data)); }); } } -pub(crate) fn get_reflection_data<'a>(class_id: &Uuid) -> Option> { +pub(crate) fn get_reflection_data(class_id: &Uuid) -> Option> { #[cfg(not(test))] let data = { let registry = CLASS_REGISTRY.read().unwrap(); diff --git a/src/stack/mod.rs b/src/stack/mod.rs index 5e8530c..16bfe80 100644 --- a/src/stack/mod.rs +++ b/src/stack/mod.rs @@ -1,7 +1,7 @@ mod trace; use lazy_static::lazy_static; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::sync::RwLock; pub(crate) use trace::remap_stack_trace; @@ -11,8 +11,8 @@ unsafe impl Send for InternalSourceMap {} unsafe impl Sync for InternalSourceMap {} lazy_static! { - static ref FILE_MAPPINGS: RwLock> = - RwLock::new(HashMap::new()); + static ref FILE_MAPPINGS: RwLock> = + RwLock::new(Default::default()); } #[derive(Debug)] diff --git a/src/wasm/compile.rs b/src/wasm/compile.rs index cf4dce5..4ac8ff6 100644 --- a/src/wasm/compile.rs +++ b/src/wasm/compile.rs @@ -1,4 +1,4 @@ -use crate::parser::{parse, CompileOptions}; +use crate::parser::{CodeParser, CompileOptions}; use wasm_bindgen::prelude::*; #[wasm_bindgen(typescript_custom_section)] @@ -6,6 +6,7 @@ const ITEXT_STYLE: &'static str = r#" interface CompileOptions { debug?: boolean; namespace?: string; + asFunction?: boolean; } "#; @@ -19,6 +20,9 @@ extern "C" { #[wasm_bindgen(structural, method, getter)] fn namespace(this: &WasmCompileOptions) -> Option; + + #[wasm_bindgen(structural, method, getter, js_name = "asFunction")] + fn as_function(this: &WasmCompileOptions) -> Option; } #[wasm_bindgen(js_name = compile)] @@ -29,13 +33,25 @@ pub fn compile( ) -> Result { let debug = opts.as_ref().and_then(|c| c.debug()).unwrap_or_default(); let namespace = opts.as_ref().and_then(|c| c.namespace()); + let as_function = opts + .as_ref() + .and_then(|c| c.as_function()) + .unwrap_or_default(); - let program = match parse(source, filename.as_deref()) { + let program = match source.parse_program(filename.as_deref()) { Ok(p) => p, Err(e) => { - return Err(JsError::new(&e.to_string())); + return Err(JsError::new(&format!( + "{} while parsing {}", + e, + filename.as_deref().unwrap_or("") + ))); } }; - Ok(program.compile(CompileOptions { debug, namespace })?) + Ok(program.compile(CompileOptions { + debug, + namespace, + as_function, + })?) } diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs index 4a7d2ca..90cf159 100644 --- a/src/wasm/mod.rs +++ b/src/wasm/mod.rs @@ -55,7 +55,7 @@ pub fn start() { }, ); - Error::set_stack_trace_limit(0); + Error::set_stack_trace_limit(255); Error::set_prepare_stack_trace(a.as_ref().unchecked_ref()); a.forget(); } diff --git a/src/wasm/stack_trace.rs b/src/wasm/stack_trace.rs index f187de8..7054f68 100644 --- a/src/wasm/stack_trace.rs +++ b/src/wasm/stack_trace.rs @@ -1,6 +1,7 @@ use crate::stack::remap_stack_trace; use crate::Frame; use js_sys::*; +use std::fmt::{Debug, Formatter}; use std::iter::Iterator; use wasm_bindgen::prelude::*; @@ -116,6 +117,12 @@ extern "C" { pub fn to_string(this: &CallSite) -> String; } +impl Debug for CallSite { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.to_string()) + } +} + impl From<&CallSite> for Frame { fn from(value: &CallSite) -> Self { Self { diff --git a/tests/wasm/reflection.js b/tests/wasm/reflection.js index 3b0c81d..341a8a1 100644 --- a/tests/wasm/reflection.js +++ b/tests/wasm/reflection.js @@ -27,6 +27,10 @@ export default /** class docblock */ class x { static staticPublicField; publicField; accessor publicAccessor; + + /** constructor docblock */ + constructor(@type(String) constructorParam1) { + } /** * computed method docblock @@ -65,8 +69,13 @@ return x[Symbol.metadata].act[Symbol.parameters][0].type; expect(t).toStrictEqual(String); const data = getReflectionData(exports['default']); + const construct = data.members.find((o) => o.name === 'constructor'); const member = data.members.find((o) => o.name === 'publicMethod'); + expect(construct).not.toBeUndefined(); + expect(construct.docblock).toEqual('/** constructor docblock */'); + expect(construct.parameters).toHaveLength(1); + expect(member).not.toBeUndefined(); expect(member.docblock).toEqual( '/**\n * public method docblock\n */', diff --git a/tests/wasm/stack.js b/tests/wasm/stack.js index 1e8a1ce..f6c1031 100644 --- a/tests/wasm/stack.js +++ b/tests/wasm/stack.js @@ -1,5 +1,5 @@ const { compile } = require('../..'); -const { runInNewContext } = require('node:vm'); +const { runInThisContext } = require('node:vm'); describe('Error stack', () => { it('should handle error stack correctly', () => { @@ -16,20 +16,12 @@ new x(); new x(true); `; - const compiled = compile(program, 'x.js'); + const compiled = compile(program, 'x.js', { asFunction: true }); try { - runInNewContext( - compiled, - { Symbol, __jymfony, __jymfony_reflect, _apply_decs_2203_r }, - { filename: 'x.js' }, - ); + runInThisContext(compiled, { filename: 'x.js' })(); throw new Error('FAIL'); } catch (e) { - expect(e.stack).toContain(`x.js:11 - throw new Error('Has to be thrown'); - ^ - -Has to be thrown + expect(e.stack).toContain(`Has to be thrown at new x (x.js:5:19) at x.js:11:1`); @@ -49,50 +41,16 @@ new x(true); `; const compiled = compile(program, 'x.ts'); - const recompiled = compile(compiled, 'x.ts'); + const recompiled = compile(compiled, 'x.ts', { asFunction: true }); try { - runInNewContext(recompiled, { Symbol }, { filename: 'x.ts' }); + runInThisContext(recompiled, { filename: 'x.ts' })(); throw new Error('FAIL'); } catch (e) { - expect(e.stack).toContain(`x.ts:3 - throw new Error('Has to be thrown'); - ^ - -Has to be thrown + expect(e.stack).toContain(`Has to be thrown at new x (x.ts:4:15) at x.ts:9:1`); } }); - - it('should compile exports correctly', () => { - const program = ` -export { x, y, z as ɵZ }; -`; - - const compiled = compile(program, null); - expect(compiled).toEqual(`"use strict"; -Object.defineProperty(exports, "__esModule", { - value: true -}); -function _export(target, all) { - for(var name in all)Object.defineProperty(target, name, { - enumerable: true, - get: all[name] - }); -} -_export(exports, { - x: function() { - return x; - }, - y: function() { - return y; - }, - ɵZ: function() { - return z; - } -}); -`); - }); });