diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7e7d95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +/Cargo.lock +/node_modules \ No newline at end of file diff --git a/.mocha.setup.js b/.mocha.setup.js new file mode 100644 index 0000000..004578b --- /dev/null +++ b/.mocha.setup.js @@ -0,0 +1,3 @@ +// Inject jest's assertion (expect) into global scope for the Mocha +// to use same assertion between node-swc & rest. +global.expect = require("expect"); diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 0000000..745fd02 --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,12 @@ +/** + * https://github.com/swc-project/swc/pull/3009 + * + * There are 2 test runners in this repo: + * - jest is being used for unit testing node-swc packages with javascript test cases + * - mocha is being used for cargo's test requires js runtime to validate its transform. + * + * This config is for the mocha test runner invoked by cargo to resolve its global setup file. + */ +module.exports = { + require: require.resolve("./.mocha.setup.js"), +}; diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d18328e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "jymfony-compiler" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +anyhow = { version = "1", features = ["backtrace"] } +base64 = "0.21.4" +getrandom = { version = "0.2.10", features = ["js"] } +js-sys = "0.3" +lazy_static = "1.4.0" +rand = "0.8.5" +rustc-hash = "1.1.0" +serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.6" +sourcemap = "6.4.1" +swc_atoms = "0.6.0" +swc_common = { version = "0.33.0", features = ["anyhow", "sourcemap"] } +swc_ecma_ast = "0.110.0" +swc_ecma_codegen = "0.146.1" +swc_ecma_parser = "0.141.1" +swc_ecma_transforms_base = "0.134.3" +swc_ecma_transforms_compat = "0.160.4" +swc_ecma_transforms_module = "0.177.4" +swc_ecma_transforms_proposal = "0.168.6" +swc_ecma_transforms_typescript = "0.184.6" +swc_ecma_visit = "0.96.0" +swc_ecma_utils = "0.124.3" +url = "2.4" +wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } +wasm-bindgen-derive = "0.2" + +[dev-dependencies] +ansi_term = "0.12.1" +hex = "0.4.3" +sha1 = "0.10.6" +swc_ecma_transforms_testing = "0.137.4" +tempfile = "3.8.0" +testing = "0.35.0" +wasm-bindgen-test = "0.3" + +[profile.release] +opt-level = 3 diff --git a/lib/_apply_decs_2203_r.js b/lib/_apply_decs_2203_r.js new file mode 100644 index 0000000..9ae0adc --- /dev/null +++ b/lib/_apply_decs_2203_r.js @@ -0,0 +1,836 @@ +/* @minVersion 7.20.0 */ + +/** + Enums are used in this file, but not assigned to vars to avoid non-hoistable values + + CONSTRUCTOR = 0; + PUBLIC = 1; + PRIVATE = 2; + + FIELD = 0; + ACCESSOR = 1; + METHOD = 2; + GETTER = 3; + SETTER = 4; + PARAM = 5; + + STATIC = 6; + + CLASS = 20; // only used in assertValidReturnValue +*/ + +function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) { + function createAddInitializerMethod(initializers, decoratorFinishedRef) { + return function addInitializer(initializer) { + assertNotFinished(decoratorFinishedRef, "addInitializer"); + assertCallable(initializer, "An initializer"); + initializers.push(initializer); + }; + } + + function memberDec( + dec, + name, + desc, + initializers, + kind, + isStatic, + isPrivate, + metadata, + value + ) { + var kindStr; + + switch (kind) { + case 1 /* ACCESSOR */: + kindStr = "accessor"; + break; + case 2 /* METHOD */: + kindStr = "method"; + break; + case 3 /* GETTER */: + kindStr = "getter"; + break; + case 4 /* SETTER */: + kindStr = "setter"; + break; + default: + kindStr = "field"; + } + + var ctx = { + kind: kindStr, + name: isPrivate ? "#" + name : name, + static: isStatic, + private: isPrivate, + metadata: metadata, + }; + + var decoratorFinishedRef = { v: false }; + + if (kind !== 0 /* FIELD */) { + ctx.addInitializer = createAddInitializerMethod( + initializers, + decoratorFinishedRef + ); + } + + var get, set; + if (kind === 0 /* FIELD */) { + if (isPrivate) { + get = desc.get; + set = desc.set; + } else { + get = function () { + return this[name]; + }; + set = function (v) { + this[name] = v; + }; + } + } else if (kind === 2 /* METHOD */) { + get = function () { + return desc.value; + }; + } else { + // replace with values that will go through the final getter and setter + if (kind === 1 /* ACCESSOR */ || kind === 3 /* GETTER */) { + get = function () { + return desc.get.call(this); + }; + } + + if (kind === 1 /* ACCESSOR */ || kind === 4 /* SETTER */) { + set = function (v) { + desc.set.call(this, v); + }; + } + } + ctx.access = + get && set ? { get: get, set: set } : get ? { get: get } : { set: set }; + + try { + return dec(value, ctx); + } finally { + decoratorFinishedRef.v = true; + } + } + + function assertNotFinished(decoratorFinishedRef, fnName) { + if (decoratorFinishedRef.v) { + throw new Error( + "attempted to call " + fnName + " after decoration was finished" + ); + } + } + + function assertCallable(fn, hint) { + if (typeof fn !== "function") { + throw new TypeError(hint + " must be a function"); + } + } + + function assertValidReturnValue(kind, value) { + var type = typeof value; + + if (kind === 1 /* ACCESSOR */) { + if (type !== "object" || value === null) { + throw new TypeError( + "accessor decorators must return an object with get, set, or init properties or void 0" + ); + } + if (value.get !== undefined) { + assertCallable(value.get, "accessor.get"); + } + if (value.set !== undefined) { + assertCallable(value.set, "accessor.set"); + } + if (value.init !== undefined) { + assertCallable(value.init, "accessor.init"); + } + } else if (type !== "function") { + var hint; + if (kind === 0 /* FIELD */) { + hint = "field"; + } else if (kind === 20 /* CLASS */) { + hint = "class"; + } else { + hint = "method"; + } + throw new TypeError( + hint + " decorators must return a function or void 0" + ); + } + } + + function applyMemberDec( + ret, + base, + decInfo, + name, + kind, + isStatic, + isPrivate, + initializers, + metadata + ) { + var decs = decInfo[0]; + + var desc, init, value; + + if (isPrivate) { + if (kind === 0 /* FIELD */ || kind === 1 /* ACCESSOR */) { + desc = { + get: decInfo[3], + set: decInfo[4], + }; + } else if (kind === 3 /* GETTER */) { + desc = { + get: decInfo[3], + }; + } else if (kind === 4 /* SETTER */) { + desc = { + set: decInfo[3], + }; + } else { + desc = { + value: decInfo[3], + }; + } + } else if (kind !== 0 /* FIELD */) { + desc = Object.getOwnPropertyDescriptor(base, name); + } + + if (kind === 1 /* ACCESSOR */) { + value = { + get: desc.get, + set: desc.set, + }; + } else if (kind === 2 /* METHOD */) { + value = desc.value; + } else if (kind === 3 /* GETTER */) { + value = desc.get; + } else if (kind === 4 /* SETTER */) { + value = desc.set; + } + + var newValue, get, set; + + if (typeof decs === "function") { + newValue = memberDec( + decs, + name, + desc, + initializers, + kind, + isStatic, + isPrivate, + metadata, + value + ); + + if (newValue !== void 0) { + assertValidReturnValue(kind, newValue); + + if (kind === 0 /* FIELD */) { + init = newValue; + } else if (kind === 1 /* ACCESSOR */) { + init = newValue.init; + get = newValue.get || value.get; + set = newValue.set || value.set; + + value = { get: get, set: set }; + } else { + value = newValue; + } + } + } else { + for (var i = decs.length - 1; i >= 0; i--) { + var dec = decs[i]; + + newValue = memberDec( + dec, + name, + desc, + initializers, + kind, + isStatic, + isPrivate, + metadata, + value + ); + + if (newValue !== void 0) { + assertValidReturnValue(kind, newValue); + var newInit; + + if (kind === 0 /* FIELD */) { + newInit = newValue; + } else if (kind === 1 /* ACCESSOR */) { + newInit = newValue.init; + get = newValue.get || value.get; + set = newValue.set || value.set; + + value = { get: get, set: set }; + } else { + value = newValue; + } + + if (newInit !== void 0) { + if (init === void 0) { + init = newInit; + } else if (typeof init === "function") { + init = [init, newInit]; + } else { + init.push(newInit); + } + } + } + } + } + + if (kind === 0 /* FIELD */ || kind === 1 /* ACCESSOR */) { + if (init === void 0) { + // If the initializer was void 0, sub in a dummy initializer + init = function (instance, init) { + return init; + }; + } else if (typeof init !== "function") { + var ownInitializers = init; + + init = function (instance, init) { + var value = init; + + for (var i = 0; i < ownInitializers.length; i++) { + value = ownInitializers[i].call(instance, value); + } + + return value; + }; + } else { + var originalInitializer = init; + + init = function (instance, init) { + return originalInitializer.call(instance, init); + }; + } + + ret.push(init); + } + + if (kind !== 0 /* FIELD */) { + if (kind === 1 /* ACCESSOR */) { + desc.get = value.get; + desc.set = value.set; + } else if (kind === 2 /* METHOD */) { + desc.value = value; + } else if (kind === 3 /* GETTER */) { + desc.get = value; + } else if (kind === 4 /* SETTER */) { + desc.set = value; + } + + if (isPrivate) { + if (kind === 1 /* ACCESSOR */) { + ret.push(function (instance, args) { + return value.get.call(instance, args); + }); + ret.push(function (instance, args) { + return value.set.call(instance, args); + }); + } else if (kind === 2 /* METHOD */) { + ret.push(value); + } else { + ret.push(function (instance, args) { + return value.call(instance, args); + }); + } + } else { + Object.defineProperty(base, name, desc); + } + } + } + + function parameterDec( + dec, + name, + initializers, + index, + rest, + methodKind, + methodName, + methodIsPrivate, + methodIsStatic, + metadata, + value + ) { + var funcKindStr = "method"; + if (methodKind === 4 /* SETTER */) { + funcKindStr = "setter"; + } else if (methodKind === 20 /* CLASS */) { + funcKindStr = "class"; + } + + var ctx = { + kind: "parameter", + name: name, + index: index, + rest: rest > 0, + function: { + kind: funcKindStr, + name: methodName, + static: methodIsStatic, + private: methodIsPrivate, + }, + metadata: metadata, + }; + + var decoratorFinishedRef = { v: false }; + ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef); + + try { + return dec(value, ctx); + } finally { + decoratorFinishedRef.v = true; + } + } + + function applyParameterDecs( + ret, + base, + decInfo, + name, + paramIndex, + isRest, + funcInfo, + initializers, + metadata, + value + ) { + var decs = decInfo[0]; + var init; + + var funcKind = funcInfo[0]; + var isPrivate = funcInfo[2]; + var isStatic = funcKind !== 20 && funcKind >= 6; + if (isStatic) funcKind -= 6; + + var newValue; + + if (typeof decs === "function") { + newValue = parameterDec( + decs, + name, + initializers, + paramIndex, + isRest, + funcKind, + funcInfo[1], + isPrivate, + isStatic, + metadata, + value + ); + + if (newValue !== void 0) { + assertValidReturnValue(5, newValue); + init = newValue; + } + } else { + for (var i = decs.length - 1; i >= 0; i--) { + var dec = decs[i]; + + newValue = parameterDec( + dec, + name, + initializers, + paramIndex, + isRest, + funcKind, + funcInfo[1], + isPrivate, + isStatic, + metadata, + value + ); + + if (newValue !== void 0) { + assertValidReturnValue(5, newValue); + if (init === void 0) { + init = newValue; + } else if (typeof init === "function") { + init = [init, newValue]; + } else { + init.push(newValue); + } + } + } + } + + if (init === void 0) { + init = function (instance, init) { + return init; + }; + } else if (typeof init !== "function") { + var ownInitializers = init; + + init = function (instance, init) { + var value = init; + + for (var i = 0; i < ownInitializers.length; i++) { + value = ownInitializers[i].call(instance, value); + } + + return value; + }; + } else { + var originalInitializer = init; + + init = function (instance, init) { + return originalInitializer.call(instance, init); + }; + } + + ret.push(init); + } + + function applyMemberDecs(Class, decInfos, metadata) { + var ret = []; + var protoInitializers; + var staticInitializers; + + var existingProtoNonFields = new Map(); + var existingStaticNonFields = new Map(); + + for (var i = 0; i < decInfos.length; i++) { + var decInfo = decInfos[i]; + + // skip computed property names + if (!Array.isArray(decInfo)) continue; + + var kind = decInfo[1]; + var name = decInfo[2]; + var isPrivate = decInfo.length > 3; + + var isStatic = kind >= 6; /* STATIC */ + var base; + var initializers; + + if (isStatic) { + base = Class; + kind = kind - 6 /* STATIC */; + // initialize staticInitializers when we see a non-field static member + if (kind !== 0 /* FIELD */) { + staticInitializers = staticInitializers || []; + initializers = staticInitializers; + } + } else { + base = Class.prototype; + // initialize protoInitializers when we see a non-field member + if (kind !== 0 /* FIELD */) { + protoInitializers = protoInitializers || []; + initializers = protoInitializers; + } + } + + if (kind === 5 /* PARAMETER */) { + isPrivate = false; + isStatic = decInfo[5][0] >= 6; + if (isStatic) { + base = Class; + staticInitializers = staticInitializers || []; + initializers = staticInitializers; + } else { + base = Class.prototype; + protoInitializers = protoInitializers || []; + initializers = protoInitializers; + } + + var paramIndex = decInfo[3]; + var isRest = decInfo[4]; + var funcInfo = decInfo[5]; + + applyParameterDecs( + ret, + base, + decInfo, + name, + paramIndex, + isRest, + funcInfo, + initializers, + metadata + ); + } else { + if (kind !== 0 /* FIELD */ && !isPrivate) { + var existingNonFields = isStatic + ? existingStaticNonFields + : existingProtoNonFields; + + var existingKind = existingNonFields.get(name) || 0; + + if ( + existingKind === true || + (existingKind === 3 /* GETTER */ && kind !== 4) /* SETTER */ || + (existingKind === 4 /* SETTER */ && kind !== 3) /* GETTER */ + ) { + throw new Error( + "Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + + name + ); + } else if (!existingKind && kind > 2 /* METHOD */) { + existingNonFields.set(name, kind); + } else { + existingNonFields.set(name, true); + } + } + + applyMemberDec( + ret, + base, + decInfo, + name, + kind, + isStatic, + isPrivate, + initializers, + metadata + ); + } + } + + pushInitializers(ret, protoInitializers); + pushInitializers(ret, staticInitializers); + return ret; + } + + function pushInitializers(ret, initializers) { + if (initializers) { + ret.push(function (instance) { + for (var i = 0; i < initializers.length; i++) { + initializers[i].call(instance); + } + return instance; + }); + } + } + + function applyClassDecs(targetClass, classDecs, metadata) { + if (classDecs.length > 0) { + var initializers = []; + var newClass = targetClass; + var name = targetClass.name; + + for (var i = classDecs.length - 1; i >= 0; i--) { + var decoratorFinishedRef = { v: false }; + + try { + var nextNewClass = classDecs[i](newClass, { + kind: "class", + name: name, + addInitializer: createAddInitializerMethod( + initializers, + decoratorFinishedRef + ), + metadata, + }); + } finally { + decoratorFinishedRef.v = true; + } + + if (nextNewClass !== undefined) { + assertValidReturnValue(20 /* CLASS */, nextNewClass); + newClass = nextNewClass; + } + } + + return [ + defineMetadata(newClass, metadata), + function () { + for (var i = 0; i < initializers.length; i++) { + initializers[i].call(newClass); + } + }, + ]; + } + // The transformer will not emit assignment when there are no class decorators, + // so we don't have to return an empty array here. + } + + function defineMetadata(Class, metadata) { + return Object.defineProperty( + Class, + Symbol.metadata || Symbol.for("Symbol.metadata"), + { configurable: false, enumerable: false, value: metadata } + ); + } + + /** + Basic usage: + + applyDecs( + Class, + [ + // member decorators + [ + dec, // dec or array of decs + 0, // kind of value being decorated + 'prop', // name of public prop on class containing the value being decorated, + '#p', // the name of the private property (if is private, void 0 otherwise), + ] + ], + [ + // class decorators + dec1, dec2 + ] + ) + ``` + + Fully transpiled example: + + ```js + @dec + class Class { + @dec + a = 123; + + @dec + #a = 123; + + @dec + @dec2 + accessor b = 123; + + @dec + accessor #b = 123; + + @dec + c() { console.log('c'); } + + @dec + #c() { console.log('privC'); } + + @dec + get d() { console.log('d'); } + + @dec + get #d() { console.log('privD'); } + + @dec + set e(v) { console.log('e'); } + + @dec + set #e(v) { console.log('privE'); } + } + + + // becomes + let initializeInstance; + let initializeClass; + + let initA; + let initPrivA; + + let initB; + let initPrivB, getPrivB, setPrivB; + + let privC; + let privD; + let privE; + + let Class; + class _Class { + static { + let ret = applyDecs( + this, + [ + [dec, 0, 'a'], + [dec, 0, 'a', (i) => i.#a, (i, v) => i.#a = v], + [[dec, dec2], 1, 'b'], + [dec, 1, 'b', (i) => i.#privBData, (i, v) => i.#privBData = v], + [dec, 2, 'c'], + [dec, 2, 'c', () => console.log('privC')], + [dec, 3, 'd'], + [dec, 3, 'd', () => console.log('privD')], + [dec, 4, 'e'], + [dec, 4, 'e', () => console.log('privE')], + ], + [ + dec + ] + ) + + initA = ret[0]; + + initPrivA = ret[1]; + + initB = ret[2]; + + initPrivB = ret[3]; + getPrivB = ret[4]; + setPrivB = ret[5]; + + privC = ret[6]; + + privD = ret[7]; + + privE = ret[8]; + + initializeInstance = ret[9]; + + Class = ret[20] + + initializeClass = ret[11]; + } + + a = (initializeInstance(this), initA(this, 123)); + + #a = initPrivA(this, 123); + + #bData = initB(this, 123); + get b() { return this.#bData } + set b(v) { this.#bData = v } + + #privBData = initPrivB(this, 123); + get #b() { return getPrivB(this); } + set #b(v) { setPrivB(this, v); } + + c() { console.log('c'); } + + #c(...args) { return privC(this, ...args) } + + get d() { console.log('d'); } + + get #d() { return privD(this); } + + set e(v) { console.log('e'); } + + set #e(v) { privE(this, v); } + } + + initializeClass(Class); + */ + + _apply_decs_2203_r = function(targetClass, memberDecs, classDecs, parentClass) { + if (parentClass !== void 0) { + var parentMetadata = + parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")]; + } + var metadata = Object.create( + parentMetadata === void 0 ? null : parentMetadata + ); + var e = applyMemberDecs(targetClass, memberDecs, metadata); + if (!classDecs.length) defineMetadata(targetClass, metadata); + return { + e: e, + // Lazily apply class decorations so that member init locals can be properly bound. + get c() { + return applyClassDecs(targetClass, classDecs, metadata); + }, + }; + }; + + return _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass); +} + +exports._ = exports._apply_decs_2203_r = _apply_decs_2203_r; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a84d1b3 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "name": "@jymfony/compiler", + "devDependencies": { + "expect": "^27.4.2" + } +} \ No newline at end of file diff --git a/src/err.rs b/src/err.rs new file mode 100644 index 0000000..6bb5553 --- /dev/null +++ b/src/err.rs @@ -0,0 +1,68 @@ +use core::fmt::{Debug, Display, Formatter}; +use std::error::Error; +use swc_common::source_map::Pos; +use swc_common::{SourceFile, Spanned}; + +#[derive(Debug)] +pub(crate) struct SyntaxError { + msg: String, +} + +impl SyntaxError { + fn new(msg: &str) -> Self { + Self { + msg: msg.to_string(), + } + } +} + +impl Display for SyntaxError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.msg) + } +} + +impl Error for SyntaxError {} + +fn get_column_index_of_pos(text: &str, pos: usize) -> usize { + let line_start_byte_pos = get_line_start_byte_pos(text, pos); + text[line_start_byte_pos..pos].chars().count() +} + +fn get_line_start_byte_pos(text: &str, pos: usize) -> usize { + let text_bytes = text.as_bytes(); + for i in (0..pos).rev() { + if text_bytes.get(i) == Some(&(b'\n')) { + return i + 1; + } + } + + 0 +} + +impl SyntaxError { + pub(crate) fn from_parser_error( + e: &swc_ecma_parser::error::Error, + source_file: &SourceFile, + ) -> Self { + let src = source_file.src.as_str(); + let lines = src + .get(0..(e.span_lo().to_usize() + 1)) + .unwrap() + .split('\n') + .collect::>(); + let last_line = *lines.last().unwrap(); + + let line = lines.len(); + let column = get_column_index_of_pos(src, last_line.len()); + + let message = format!( + "SyntaxError: {} on line {}, column {}", + e.kind().msg().as_ref(), + line, + column + ); + + Self::new(message.as_str()).into() + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..00a6fe7 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +mod err; +mod parser; +mod stack; +mod wasm; + + +#[cfg(test)] +pub mod testing; + +pub(crate) use err::SyntaxError; +pub(crate) use stack::*; diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..25992ef --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,242 @@ +use crate::SyntaxError; +use program::Program; +use std::path::PathBuf; +use swc_common::comments::SingleThreadedComments; +use swc_common::input::StringInput; +use swc_common::sync::Lrc; +use swc_common::FileName; +use swc_ecma_ast::EsVersion; +use swc_ecma_parser::lexer::Lexer; +use swc_ecma_parser::{EsConfig, Parser, Syntax, TsConfig}; + +mod program; +mod sourcemap; +mod transformers; +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()) + } else { + Ok(Program { + source_map, + orig_srcmap, + filename: filename.map(|f| f.to_string()), + program, + comments, + is_typescript, + }) + } + } else { + let e = parse_result.unwrap_err(); + Err(SyntaxError::from_parser_error(&e, &source_file).into()) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::rc::Rc; + use swc_common::comments::SingleThreadedComments; + use swc_common::{chain, Mark}; + use swc_ecma_parser::{EsConfig, Syntax}; + use swc_ecma_transforms_base::resolver; + use swc_ecma_transforms_compat::es2022::{class_properties, static_blocks}; + use swc_ecma_visit::Fold; + use crate::parser::transformers::decorator_2022_03; + use crate::testing::exec_tr; + use super::parse; + + #[testing::fixture("tests/decorators/**/exec.js")] + pub fn exec(input: PathBuf) { + exec_inner(input); + } + + fn exec_inner(input: PathBuf) { + let code = std::fs::read_to_string(&input).unwrap(); + + exec_tr( + "decorator", + 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, + }), + |t| create_pass(t.comments.clone()), + &code, + ); + } + + fn create_pass(comments: Rc) -> 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), + decorator_2022_03(), + static_blocks(static_block_mark), + class_properties(Some(comments), Default::default()), + )) + } + + + #[test] + pub fn parse_error() { + let code = r#" +new class ext impl test {[]} +"#; + let result = parse(code.into(), Some("a.js")); + assert!(result.is_err()); + + let error = result.unwrap_err(); + assert_eq!( + error.to_string(), + "SyntaxError: Expected '{', got 'impl' on line 2, column 15" + ); + } + + #[test] + pub fn parse_should_work() { + let code = r#" +function register() { return () => {}; } +function initialize() { return () => {}; } +const secondary = () => console.log; +const logger = { + logged: (value, { kind, name }) => { + if (kind === "method") { + return function (...args) { + console.log(`starting \${name} with arguments \${args.join(", ")}`); + const ret = value.call(this, ...args); + console.log(`ending \${name}`); + return ret; + }; + } + + if (kind === "field") { + return function (initialValue) { + console.log(`initializing \${name} with value \${initialValue}`); + return initialValue; + }; + } + }, +} + +const an = class { + constructor() { + // Dummy + this.x = "test"; + } +}; +const an1 = function () {}; + +// This is a comment +export default @logger.logged class x { + @logger.logged + @register((target, prop, parameterIndex = null) => {}) + @initialize((instance, key, value) => {}) + field = 'foo'; + + @logger.logged + @initialize((instance, key, value) => {}) + accessor fieldAcc = 'foobar'; + + @logger.logged + #privateField = 'pr'; + accessor #privateAccessor = 'acc'; + + @logger.logged + @secondary('great') + test() { + const cc = @logger.logged class {} + } + + @logger.logged + @secondary('great') + get test_getter() { + return 'test'; + } + + @logger.logged + @secondary('great') + set test_setter(value) { + } + + @logger.logged + testMethod(@type(Request) firstArg) { + dump(firstArg); + } + + @logger.logged + testMethod2(@type(Request) ...[a, b,, c]) { + dump(firstArg); + } +} +"#; + + let parsed = parse(code.into(), Some("a.js")).unwrap(); + + assert!(parsed.program.is_module()); + assert!(parsed.program.as_module().unwrap().body.iter().any(|s| s + .as_module_decl() + .is_some_and(|s| s.is_export_default_decl()))); + + let _ = parsed.compile().expect("Should compile with no error"); + } +} diff --git a/src/parser/program.rs b/src/parser/program.rs new file mode 100644 index 0000000..25677c1 --- /dev/null +++ b/src/parser/program.rs @@ -0,0 +1,100 @@ +use crate::parser::transformers::{ + anonymous_expr, class_jobject, class_reflection_decorators, decorator_2022_03, +}; +use crate::stack::register_source_map; +use sourcemap::SourceMap; +use std::fmt::{Debug, Formatter}; +use std::sync::Arc; +use swc_common::comments::SingleThreadedComments; +use swc_common::{chain, BytePos, LineCol, Mark, GLOBALS}; +use swc_ecma_codegen::text_writer::JsWriter; +use swc_ecma_codegen::Emitter; +use swc_ecma_transforms_base::feature::FeatureFlag; +use swc_ecma_transforms_base::hygiene::hygiene; +use swc_ecma_transforms_base::resolver; +use swc_ecma_transforms_compat::es2022::static_blocks; +use swc_ecma_transforms_module::common_js; +use swc_ecma_transforms_typescript::strip; +use swc_ecma_visit::FoldWith; + +#[derive(Debug)] +pub struct CompileOptions { + debug: bool, +} + +pub struct Program { + pub(crate) source_map: Arc, + pub(crate) orig_srcmap: Option, + pub(crate) filename: Option, + pub(crate) program: swc_ecma_ast::Program, + pub(crate) comments: SingleThreadedComments, + pub(crate) is_typescript: bool, +} + +impl Debug for Program { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Program") + .field("filename", &self.filename) + .field("program", &self.program) + .field("comments", &self.comments) + .field("is_typescript", &self.is_typescript) + .finish_non_exhaustive() + } +} + +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 = chain!( + resolver(unresolved_mark, top_level_mark, self.is_typescript), + strip(top_level_mark), + anonymous_expr(), + class_reflection_decorators(), + class_jobject(), + decorator_2022_03(), + static_blocks(static_block_mark), + hygiene(), + common_js( + unresolved_mark, + Default::default(), + available_set, + Some(&self.comments) + ), + ); + + if !opts.debug { + // TODO + // transformers = chain!(transformers, remove_assert_calls()); + } + + let program = self.program.fold_with(&mut transformers); + 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)), + }; + + emitter.emit_program(&program)? + }; + + if let Some(f) = self.filename { + let srcmap = self + .source_map + .build_source_map_from(&sm, self.orig_srcmap.as_ref()); + register_source_map(f, srcmap); + } + + Ok(String::from_utf8(buf).expect("non-utf8?")) + }) + } +} diff --git a/src/parser/sourcemap.rs b/src/parser/sourcemap.rs new file mode 100644 index 0000000..f2fbc2a --- /dev/null +++ b/src/parser/sourcemap.rs @@ -0,0 +1,130 @@ +use anyhow::{bail, Context, Error}; +use base64::prelude::*; +use std::fs::File; +use std::path::PathBuf; +use swc_common::{FileName, SourceFile}; +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 idx = match url.path().find("base64,") { + Some(v) => v, + None => { + bail!("failed to parse inline source map: not base64: {:?}", url) + } + }; + + let content = url.path()[idx + "base64,".len()..].trim(); + let res = BASE64_STANDARD + .decode(content.as_bytes()) + .context("failed to decode base64-encoded source map")?; + + Ok(Some(sourcemap::SourceMap::from_slice(&res).context( + "failed to read input source map from inlined base64 encoded \ + string", + )?)) + } + None => { + bail!("failed to parse inline source map: `sourceMappingURL` not found") + } + } +} + +fn read_file_sourcemap( + data_url: Option<&str>, + name: &FileName, +) -> Result, Error> { + match &name { + FileName::Real(filename) => { + let dir = match filename.parent() { + Some(v) => v, + None => { + bail!("unexpected: root directory is given as a input file") + } + }; + + let map_path = match data_url { + Some(data_url) => { + let mut map_path = dir.join(data_url); + if !map_path.exists() { + // Old behavior. This check would prevent + // regressions. + // Perhaps it shouldn't be supported. Sometimes + // developers don't want to expose their source + // code. + // Map files are for internal troubleshooting + // convenience. + map_path = PathBuf::from(format!("{}.map", filename.display())); + if !map_path.exists() { + bail!( + "failed to find input source map file {:?} in \ + {:?} file", + map_path.display(), + filename.display() + ) + } + } + + Some(map_path) + } + None => { + // Old behavior. + let map_path = PathBuf::from(format!("{}.map", filename.display())); + if map_path.exists() { + Some(map_path) + } else { + None + } + } + }; + + match map_path { + Some(map_path) => { + let path = map_path.display().to_string(); + let file = File::open(&path)?; + + Ok(Some(sourcemap::SourceMap::from_reader(file).with_context( + || { + format!( + "failed to read input source map + from file at {}", + path + ) + }, + )?)) + } + None => Ok(None), + } + } + _ => Ok(None), + } +} + +pub fn get_orig_src_map(fm: &SourceFile) -> Result, Error> { + let s = "sourceMappingURL="; + let idx = fm.src.rfind(s); + + let data_url = idx.map(|idx| { + let data_idx = idx + s.len(); + if let Some(end) = fm.src[data_idx..].find('\n').map(|i| i + data_idx + 1) { + &fm.src[data_idx..end] + } else { + &fm.src[data_idx..] + } + }); + + Ok(match read_inline_sourcemap(data_url) { + Ok(r) => r, + Err(_) => { + // Load original source map if possible + match read_file_sourcemap(data_url, &fm.name) { + Ok(v) => v, + Err(_) => None, + } + } + }) +} diff --git a/src/parser/transformers/anonymous_expr.rs b/src/parser/transformers/anonymous_expr.rs new file mode 100644 index 0000000..121be60 --- /dev/null +++ b/src/parser/transformers/anonymous_expr.rs @@ -0,0 +1,38 @@ +use rand::Rng; +use swc_ecma_ast::*; +use swc_ecma_utils::private_ident; +use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut}; + +pub fn anonymous_expr() -> impl VisitMut + Fold { + as_folder(AnonymousExpr::default()) +} + +#[derive(Default)] +struct AnonymousExpr {} + +fn gen_anonymous_ident() -> Ident { + let rnd = rand::thread_rng().gen_range(0..1000000); + let ident = format!("_anonymous_xΞ{:X}", rnd); + + private_ident!(ident) +} + +impl VisitMut for AnonymousExpr { + noop_visit_mut_type!(); + + fn visit_mut_expr(&mut self, e: &mut Expr) { + match e { + Expr::Class(c) => { + if c.ident.is_none() { + c.ident = Some(gen_anonymous_ident()); + } + } + Expr::Fn(f) => { + if f.ident.is_none() { + f.ident = Some(gen_anonymous_ident()); + } + } + _ => {} + } + } +} diff --git a/src/parser/transformers/class_jobject.rs b/src/parser/transformers/class_jobject.rs new file mode 100644 index 0000000..813a228 --- /dev/null +++ b/src/parser/transformers/class_jobject.rs @@ -0,0 +1,206 @@ +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}; + +lazy_static! { + static ref JOBJECT_ACCESSOR: MemberExpr = { + 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)), + prop: MemberProp::Ident(prop), + } + }; +} + +pub fn class_jobject() -> impl VisitMut + Fold { + as_folder(ClassJObject::default()) +} + +#[derive(Default)] +struct ClassJObject {} + +impl VisitMut for ClassJObject { + noop_visit_mut_type!(); + + fn visit_mut_class(&mut self, n: &mut Class) { + if n.super_class.is_some() { + return; + } + + 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 { + 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())), + }], + type_args: None, + })), + })), + alt: None, + }); + + block_stmts.push(super_call); + block_stmts.push(if_block); + + for p in initializers.drain(..) { + let ClassMember::ClassProp(mut p) = p else { + unreachable!() + }; + 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(|| Box::new(undefined())), + }); + + 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 new file mode 100644 index 0000000..4ba7c58 --- /dev/null +++ b/src/parser/transformers/class_reflection_decorators.rs @@ -0,0 +1,63 @@ +use crate::parser::util::ident; +use swc_common::DUMMY_SP; +use swc_ecma_ast::*; +use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut}; + +pub fn class_reflection_decorators() -> impl VisitMut + Fold { + as_folder(ClassReflectionDecorators::default()) +} + +#[derive(Default)] +struct ClassReflectionDecorators {} + +fn visit_mut_class(n: &mut Class) { + let reflect_ident = Expr::Ident(ident("__jymfony_reflect")); + for member in n.body.iter_mut() { + match member { + ClassMember::Method(m) => { + m.function.decorators.push(Decorator { + span: DUMMY_SP, + expr: Box::new(reflect_ident.clone()), + }); + } + ClassMember::PrivateMethod(m) => { + m.function.decorators.push(Decorator { + span: DUMMY_SP, + expr: Box::new(reflect_ident.clone()), + }); + } + ClassMember::ClassProp(p) => { + p.decorators.push(Decorator { + span: DUMMY_SP, + expr: Box::new(reflect_ident.clone()), + }); + } + ClassMember::PrivateProp(p) => { + p.decorators.push(Decorator { + span: DUMMY_SP, + expr: Box::new(reflect_ident.clone()), + }); + } + ClassMember::AutoAccessor(a) => { + a.decorators.push(Decorator { + span: DUMMY_SP, + expr: Box::new(reflect_ident.clone()), + }); + } + _ => { // Do nothing } + } + } + } +} + +impl VisitMut for ClassReflectionDecorators { + noop_visit_mut_type!(); + + fn visit_mut_class_decl(&mut self, n: &mut ClassDecl) { + visit_mut_class(&mut n.class) + } + + fn visit_mut_class_expr(&mut self, n: &mut ClassExpr) { + visit_mut_class(&mut n.class) + } +} diff --git a/src/parser/transformers/decorator_2022_03.rs b/src/parser/transformers/decorator_2022_03.rs new file mode 100644 index 0000000..43d3ef0 --- /dev/null +++ b/src/parser/transformers/decorator_2022_03.rs @@ -0,0 +1,2064 @@ +use std::{ + iter::once, + mem::{take, transmute}, +}; + +use crate::parser::util::{ident, undefined}; +use rustc_hash::FxHashMap; +use swc_atoms::JsWord; +use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP}; +use swc_ecma_ast::*; +use swc_ecma_ast::Expr::Bin; +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, +}; +use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; + +pub fn decorator_2022_03() -> impl VisitMut + Fold { + as_folder(Decorator202203::default()) +} + +const FIELD: usize = 0; +const ACCESSOR: usize = 1; +const METHOD: usize = 2; +const GETTER: usize = 3; +const SETTER: usize = 4; +const PARAM: usize = 5; + +const STATIC: usize = 6; +const CLASS: usize = 20; + +#[derive(Default)] +struct Decorator202203 { + /// Variables without initializer. + extra_vars: Vec, + + extra_lets: Vec, + + state: ClassState, + + /// Prepended before the class + pre_class_inits: Vec>, + + rename_map: FxHashMap, + + extra_exports: Vec, +} + +#[derive(Default)] +struct ClassState { + static_lhs: Vec, + proto_lhs: Vec, + + /// If not empty, `initProto` should be injected to the constructor. + init_proto: Option, + init_proto_args: Vec>, + is_init_proto_called: bool, + + init_static: Option, + init_static_args: Vec>, + + /// Injected into static blocks. + extra_stmts: Vec, + + class_lhs: Vec>, + class_decorators: Vec>, + + super_class: Option, +} + +impl Decorator202203 { + fn process_param_decorators(&mut self, m: &mut ClassMember) { + let is_constructor = matches!(m, ClassMember::Constructor(_)); + let mut n = match m { + ClassMember::Method(m) => m.function.params.take(), + ClassMember::PrivateMethod(m) => m.function.params.take(), + ClassMember::Constructor(c) => c + .params + .iter_mut() + .map(|p| match p { + ParamOrTsParamProp::TsParamProp(p) => Param { + span: p.span, + decorators: p.decorators.take(), + pat: match &p.param { + TsParamPropParam::Assign(pat) => Pat::Assign(pat.clone()), + TsParamPropParam::Ident(ident) => Pat::Ident(ident.clone()), + }, + }, + ParamOrTsParamProp::Param(p) => { + let r = p.clone(); + p.decorators.clear(); + + r + } + }) + .collect(), + _ => return, + }; + + let mut pre_stmts = vec![]; + let mut init_calls = vec![]; + for (i, p) in n.iter_mut().enumerate() { + if p.decorators.is_empty() { + continue; + } + + let decorators = self.preserve_side_effect_of_decorators(p.decorators.take()); + let dec = merge_decorators(decorators); + + let init = private_ident!(format!("_param{}_init", i)); + self.extra_vars.push(VarDeclarator { + span: p.span, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + self.state.static_lhs.push(init.clone()); + + let name = if let Some(ident) = p.pat.as_ident() { + Some(ident.id.clone()) + } else if let Some(rest) = p.pat.as_rest().map(|r| &r.arg) { + if let Some(ident) = rest.as_ident() { + Some(ident.id.clone()) + } else { + None + } + } else { + None + }; + + let assign_right = if let Pat::Assign(assign) = &p.pat { + let r = assign.right.clone(); + let span = assign.span.clone(); + p.pat = assign.left.as_ref().clone(); + Some((r, span)) + } else { + None + }; + + let param_ident = match &mut p.pat { + Pat::Ident(i) => i.id.clone(), + Pat::Rest(r) => { + match r.arg.as_mut() { + Pat::Ident(i) => i.id.clone(), + Pat::Array(a) => { + let ident = private_ident!(format!("_rest_param{}", i)); + pre_stmts.push( + Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + declare: false, + decls: vec![ + VarDeclarator { + span: DUMMY_SP, + name: Pat::Array(a.clone()), + init: Some(Box::new(Expr::Ident(ident.clone()))), + definite: false, + } + ], + }))) + ); + + r.arg = Box::new(Pat::Ident(ident.clone().into())); + ident + } + Pat::Object(o) => { + let ident = private_ident!(format!("_rest_param{}", i)); + pre_stmts.push( + Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + declare: false, + decls: vec![ + VarDeclarator { + span: DUMMY_SP, + name: Pat::Object(o.clone()), + init: Some(Box::new(Expr::Ident(ident.clone()))), + definite: false, + } + ], + }))) + ); + + r.arg = Box::new(Pat::Ident(ident.clone().into())); + ident + } + _ => unreachable!(), + } + } + Pat::Array(a) => { + let ident = private_ident!(format!("_param{}", i)); + pre_stmts.push( + Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + declare: false, + decls: vec![ + VarDeclarator { + span: DUMMY_SP, + name: Pat::Array(a.clone()), + init: Some(Box::new(Expr::Ident(ident.clone()))), + definite: false, + } + ], + }))) + ); + + p.pat = Pat::Ident(ident.clone().into()); + ident + } + Pat::Object(o) => { + let ident = private_ident!(format!("_param{}", i)); + pre_stmts.push( + Stmt::Decl(Decl::Var(Box::new(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Let, + declare: false, + decls: vec![ + VarDeclarator { + span: DUMMY_SP, + name: Pat::Object(o.clone()), + init: Some(Box::new(Expr::Ident(ident.clone()))), + definite: false, + } + ], + }))) + ); + + p.pat = Pat::Ident(ident.clone().into()); + ident + } + Pat::Assign(..) | Pat::Invalid(..) | Pat::Expr(..) => unreachable!(), + }; + + if let Some((right, span)) = assign_right { + p.pat = Pat::Assign(AssignPat { + span, + left: Box::new(p.pat.clone()), + right, + }); + } + + init_calls.push(Stmt::If(IfStmt { + span: DUMMY_SP, + test: Box::new(Bin(BinExpr { + span: DUMMY_SP, + op: BinaryOp::NotEqEq, + left: Box::new(Expr::Ident(init.clone())), + right: Box::new(undefined()), + })), + cons: Box::new(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: AssignOp::Assign, + left: PatOrExpr::Pat(Box::new(Pat::Ident(param_ident.clone().into()))), + right: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(init)), + prop: MemberProp::Ident(ident("call")), + }).as_callee(), + args: vec![ + if is_constructor && self.state.super_class.is_some() { + undefined().as_arg() + } else { + ThisExpr::dummy().as_arg() + }, + param_ident.as_arg(), + ], + type_args: None, + })), + })), + })), + alt: None, + })); + + let func = Some( + ArrayLit { + span: DUMMY_SP, + elems: match m { + ClassMember::Constructor(..) => { + vec![ + Some(CLASS.as_arg()), + Some(undefined().as_arg()), + Some(0_usize.as_arg()), + ] + } + ClassMember::Method(cm) => { + vec![ + Some( + (match cm.kind { + MethodKind::Setter => SETTER, + MethodKind::Method => METHOD, + _ => unreachable!(), + } + if cm.is_static { STATIC } else { 0 }) + .as_arg(), + ), + Some(match &cm.key { + PropName::Ident(i) => i.sym.clone().as_arg(), + PropName::Computed(c) => c.expr.clone().as_arg(), + _ => unreachable!(), + }), + Some(0_usize.as_arg()), + ] + } + ClassMember::PrivateMethod(cm) => { + vec![ + Some( + (match cm.kind { + MethodKind::Setter => SETTER, + MethodKind::Method => METHOD, + _ => unreachable!(), + } + if cm.is_static { STATIC } else { 0 }) + .as_arg(), + ), + Some(cm.key.id.sym.clone().as_arg()), + Some(1_usize.as_arg()), + ] + } + _ => unreachable!(), + }, + } + .as_arg(), + ); + + let arg = Some( + ArrayLit { + span: DUMMY_SP, + elems: vec![ + dec, + Some(PARAM.as_arg()), + name.map(|n| n.sym.as_arg()).or_else(|| Some(undefined().as_arg())), + Some(i.as_arg()), + Some(if p.pat.is_rest() { 1_usize } else { 0_usize }.as_arg()), + func, + ], + } + .as_arg(), + ); + + self.state.init_static_args.push(arg); + } + + match m { + ClassMember::Method(m) => { + m.function.params = n; + if let Some(b) = &mut m.function.body { + b.stmts.splice(0..0, pre_stmts.drain(..)); + b.stmts.splice(0..0, init_calls.drain(..)); + } + }, + ClassMember::PrivateMethod(m) => { + m.function.params = n; + if let Some(b) = &mut m.function.body { + b.stmts.splice(0..0, pre_stmts.drain(..)); + b.stmts.splice(0..0, init_calls.drain(..)); + } + }, + ClassMember::Constructor(c) => { + if let Some(b) = &mut c.body { + b.stmts.splice(0..0, pre_stmts.drain(..)); + b.stmts.splice(0..0, init_calls.drain(..)); + } + } + _ => unreachable!(), + }; + } + + fn preserve_side_effect_of_decorators( + &mut self, + decorators: Vec, + ) -> Vec> { + decorators + .into_iter() + .map(|e| Some(self.preserve_side_effect_of_decorator(e.expr).as_arg())) + .collect() + } + + fn preserve_side_effect_of_decorator(&mut self, dec: Box) -> Box { + if dec.is_ident() || dec.is_arrow() || dec.is_fn_expr() { + return dec; + } + + let ident = private_ident!("_dec"); + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(ident.clone().into()), + init: None, + definite: false, + }); + self.pre_class_inits.push(Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: ident.clone().into(), + right: dec, + }))); + + ident.into() + } + + /// Moves `cur_inits` to `extra_stmts`. + fn consume_inits(&mut self) { + if self.state.init_proto_args.is_empty() + && self.state.init_static_args.is_empty() + && self.state.init_proto.is_none() + && self.state.init_static.is_none() + && self.state.class_decorators.is_empty() + { + return; + } + + let mut e_lhs = vec![]; + let mut combined_args = vec![ThisExpr { span: DUMMY_SP }.as_arg()]; + + for id in self + .state + .static_lhs + .drain(..) + .chain(self.state.proto_lhs.drain(..)) + { + e_lhs.push(Some(id.into())); + } + + if let Some(init) = self.state.init_proto.clone() { + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + e_lhs.push(Some(init.into())); + } + + if let Some(init) = self.state.init_static.clone() { + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + e_lhs.push(Some(init.into())); + } + + combined_args.push( + ArrayLit { + span: DUMMY_SP, + elems: self + .state + .init_static_args + .drain(..) + .chain(self.state.init_proto_args.drain(..)) + .collect(), + } + .as_arg(), + ); + + combined_args.push( + ArrayLit { + span: DUMMY_SP, + elems: self.state.class_decorators.take(), + } + .as_arg(), + ); + + if let Some(super_class) = self.state.super_class.as_ref() { + combined_args.push(super_class.clone().as_arg()); + } + + let e_pat = if e_lhs.is_empty() { + None + } else { + Some(ObjectPatProp::KeyValue(KeyValuePatProp { + key: PropName::Ident(quote_ident!("e")), + value: Box::new(Pat::Array(ArrayPat { + span: DUMMY_SP, + elems: e_lhs, + type_ann: Default::default(), + optional: false, + })), + })) + }; + + let c_pat = if self.state.class_lhs.is_empty() { + None + } else { + Some(ObjectPatProp::KeyValue(KeyValuePatProp { + key: PropName::Ident(quote_ident!("c")), + value: Box::new(Pat::Array(ArrayPat { + span: DUMMY_SP, + elems: self.state.class_lhs.take(), + type_ann: Default::default(), + optional: false, + })), + })) + }; + + let expr = Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(Box::new(Pat::Object(ObjectPat { + span: DUMMY_SP, + props: e_pat.into_iter().chain(c_pat).collect(), + optional: false, + type_ann: None, + }))), + right: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: Expr::Ident(ident("_apply_decs_2203_r")).as_callee(), + args: combined_args, + type_args: Default::default(), + })), + })); + + self.state.extra_stmts.push(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr, + })); + + if let Some(init) = self.state.init_static.take() { + self.state.extra_stmts.push(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.as_callee(), + args: vec![ThisExpr { span: DUMMY_SP }.as_arg()], + type_args: Default::default(), + })), + })); + } + } + + /// Returns (name, initilaizer_name) + fn initializer_name(&mut self, name: &mut PropName, prefix: &str) -> (Box, Ident) { + match name { + PropName::Ident(i) => ( + Box::new(Expr::Lit(Lit::Str(Str { + span: i.span.with_ctxt(SyntaxContext::empty()), + value: i.sym.clone(), + raw: None, + }))), + Ident::new(format!("_{prefix}_{}", i.sym).into(), i.span.private()), + ), + PropName::Computed(c) if c.expr.is_ident() => match &*c.expr { + Expr::Ident(i) => ( + Box::new(Expr::Ident(i.clone())), + Ident::new(format!("_{prefix}_{}", i.sym).into(), i.span.private()), + ), + _ => { + unreachable!() + } + }, + _ => { + let ident = private_ident!("_computedKey"); + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(ident.clone().into()), + init: None, + definite: false, + }); + + self.pre_class_inits.push(Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(ident.clone().into()), + right: Box::new(prop_name_to_expr_value(name.take())), + }))); + *name = PropName::Computed(ComputedPropName { + span: DUMMY_SP, + expr: ident.clone().into(), + }); + + let init = Ident::new( + format!("_{prefix}_computedKey").into(), + ident.span.private(), + ); + + (Box::new(Expr::Ident(ident)), init) + } + } + } + + fn ensure_constructor<'a>(&mut self, c: &'a mut Class) -> &'a mut Constructor { + for member in c.body.iter_mut() { + if let ClassMember::Constructor(constructor) = member { + return unsafe { + // Safety: We need polonius + transmute::<&mut Constructor, &'a mut Constructor>(constructor) + }; + } + } + + c.body + .insert(0, default_constructor(c.super_class.is_some()).into()); + + for member in c.body.iter_mut() { + if let ClassMember::Constructor(constructor) = member { + return constructor; + } + } + + unreachable!() + } + + fn ensure_identity_constructor<'a>(&mut self, c: &'a mut Class) -> &'a mut Constructor { + for member in c.body.iter_mut() { + if let ClassMember::Constructor(constructor) = member { + return unsafe { + // Safety: We need polonius + transmute::<&mut Constructor, &'a mut Constructor>(constructor) + }; + } + } + + c.body.insert( + 0, + ClassMember::Constructor(Constructor { + span: DUMMY_SP, + key: PropName::Ident(quote_ident!("constructor")), + params: vec![], + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![], + }), + accessibility: Default::default(), + is_optional: Default::default(), + }), + ); + + for member in c.body.iter_mut() { + if let ClassMember::Constructor(constructor) = member { + return constructor; + } + } + + unreachable!() + } + + fn handle_super_class(&mut self, class: &mut Class) { + if let Some(super_class) = class.super_class.take() { + let id = alias_ident_for(&super_class, "_super"); + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(id.clone().into()), + init: None, + definite: false, + }); + + class.super_class = Some(Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: AssignOp::Assign, + left: PatOrExpr::Pat(Box::new(Pat::Ident(id.clone().into()))), + right: super_class, + }))); + + self.state.super_class = Some(id); + } + } + + fn handle_class_expr(&mut self, class: &mut Class, ident: Option<&Ident>) -> Ident { + debug_assert!( + !class.decorators.is_empty(), + "handle_class_decorator should be called only when decorators are present" + ); + + let init_class = private_ident!("_initClass"); + + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(init_class.clone().into()), + init: None, + definite: false, + }); + + let new_class_name = ident.as_ref().map_or_else( + || private_ident!("_class"), + |i| private_ident!(format!("_{}", i.sym)), + ); + + if let Some(ident) = ident { + replace_ident(&mut class.body, ident.to_id(), &new_class_name); + } + + self.state + .class_lhs + .push(Some(new_class_name.clone().into())); + self.state.class_lhs.push(Some(init_class.clone().into())); + + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(new_class_name.clone().into()), + init: None, + definite: false, + }); + + let decorators = self.preserve_side_effect_of_decorators(class.decorators.take()); + self.state.class_decorators.extend(decorators); + self.handle_super_class(class); + + { + let call_stmt = CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: vec![], + type_args: Default::default(), + } + .into_stmt(); + + class.body.push(ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![call_stmt], + }, + })); + } + + new_class_name + } + + fn handle_class_decl(&mut self, c: &mut ClassDecl) -> Option { + let old_state = take(&mut self.state); + + if !c.class.decorators.is_empty() { + let decorators = self.preserve_side_effect_of_decorators(c.class.decorators.take()); + + let init_class = private_ident!("_initClass"); + + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(init_class.clone().into()), + init: None, + definite: false, + }); + + let preserved_class_name = c.ident.clone().private(); + let new_class_name = private_ident!(format!("_{}", c.ident.sym)); + + self.extra_lets.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(new_class_name.clone().into()), + init: None, + definite: false, + }); + + self.rename_map + .insert(c.ident.to_id(), new_class_name.to_id()); + + self.state + .class_lhs + .push(Some(new_class_name.clone().into())); + self.state.class_lhs.push(Some(init_class.clone().into())); + + self.state.class_decorators.extend(decorators); + self.handle_super_class(&mut c.class); + + let mut body = c.class.body.take(); + + let has_static_member = body.iter().any(|m| match m { + ClassMember::Method(m) => m.is_static, + ClassMember::PrivateMethod(m) => m.is_static, + ClassMember::ClassProp(ClassProp { is_static, .. }) + | ClassMember::PrivateProp(PrivateProp { is_static, .. }) => *is_static, + ClassMember::StaticBlock(_) => true, + _ => false, + }); + + if has_static_member { + let mut last_static_block = None; + + self.process_decorators_of_class_members(&mut body); + + // Move static blocks into property initializers + for m in body.iter_mut() { + match m { + ClassMember::ClassProp(ClassProp { value, .. }) + | ClassMember::PrivateProp(PrivateProp { value, .. }) => { + if let Some(value) = value { + if let Some(last_static_block) = last_static_block.take() { + **value = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: vec![ + Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: ArrowExpr { + span: DUMMY_SP, + params: vec![], + body: Box::new(BlockStmtOrExpr::BlockStmt( + BlockStmt { + span: DUMMY_SP, + stmts: last_static_block, + }, + )), + is_async: false, + is_generator: false, + type_params: Default::default(), + return_type: Default::default(), + } + .as_callee(), + args: vec![], + type_args: Default::default(), + })), + value.take(), + ], + }) + } + } + } + ClassMember::StaticBlock(s) => match &mut last_static_block { + None => { + last_static_block = Some(s.body.stmts.take()); + } + Some(v) => { + v.append(&mut s.body.stmts); + } + }, + _ => {} + } + } + + // Drop static blocks + body.retain(|m| { + !matches!(m, ClassMember::StaticBlock(..) | ClassMember::Empty(..)) + }); + + for m in body.iter_mut() { + match m { + ClassMember::ClassProp(..) | ClassMember::PrivateProp(..) => { + replace_ident(m, c.ident.to_id(), &new_class_name); + } + + _ => {} + } + } + + let mut inner_class = ClassDecl { + ident: c.ident.clone(), + declare: Default::default(), + class: Box::new(Class { + span: DUMMY_SP, + decorators: vec![], + body, + super_class: c.class.super_class.take(), + is_abstract: Default::default(), + type_params: Default::default(), + super_type_params: Default::default(), + implements: Default::default(), + }), + }; + + inner_class.class.visit_mut_with(self); + + for m in inner_class.class.body.iter_mut() { + let mut should_move = false; + + match m { + ClassMember::PrivateProp(p) => { + if p.is_static { + should_move = true; + p.is_static = false; + } + } + ClassMember::PrivateMethod(p) => { + if p.is_static { + should_move = true; + p.is_static = false; + } + } + _ => (), + } + + if should_move { + c.class.body.push(m.take()) + } + } + + c.class.body.insert( + 0, + ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Decl(Decl::Class(inner_class))], + }, + }), + ); + + replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); + + { + let constructor = self.ensure_identity_constructor(&mut c.class); + + let super_call = CallExpr { + span: DUMMY_SP, + callee: Callee::Super(Super { span: DUMMY_SP }), + args: vec![c.ident.clone().as_arg()], + type_args: Default::default(), + } + .into(); + let static_call = last_static_block.map(|last| { + CallExpr { + span: DUMMY_SP, + callee: ArrowExpr { + span: DUMMY_SP, + params: vec![], + body: Box::new(BlockStmtOrExpr::BlockStmt(BlockStmt { + span: DUMMY_SP, + stmts: last, + })), + is_async: false, + is_generator: false, + type_params: Default::default(), + return_type: Default::default(), + } + .as_callee(), + args: vec![], + type_args: Default::default(), + } + .into() + }); + + let init_class_call = CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: Vec::new(), + type_args: Default::default(), + } + .into(); + + constructor.body.as_mut().unwrap().stmts.insert( + 0, + SeqExpr { + span: DUMMY_SP, + exprs: once(super_call) + .chain(static_call) + .chain(once(init_class_call)) + .collect(), + } + .into_stmt(), + ); + } + + 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), + is_abstract: Default::default(), + type_params: Default::default(), + super_type_params: Default::default(), + implements: Default::default(), + }); + + self.state = old_state; + + return Some( + NewExpr { + span: DUMMY_SP, + callee: ClassExpr { ident: None, class }.into(), + args: Some(vec![]), + type_args: Default::default(), + } + .into_stmt(), + ); + } else { + for m in body.iter_mut() { + if let ClassMember::Constructor(..) = m { + c.class.body.push(m.take()); + } + } + + body.visit_mut_with(self); + + c.visit_mut_with(self); + + c.ident = preserved_class_name.clone(); + replace_ident(&mut c.class, c.ident.to_id(), &preserved_class_name); + + c.class.body.extend(body); + + c.class.body.push(ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: vec![CallExpr { + span: DUMMY_SP, + callee: init_class.as_callee(), + args: vec![], + type_args: Default::default(), + } + .into_stmt()], + }, + })); + + self.state = old_state; + + return Some(Stmt::Decl(Decl::Class(c.take()))); + } + } + + self.state = old_state; + + None + } + + fn process_decorators(&mut self, decorators: &mut [Decorator]) { + decorators.iter_mut().for_each(|dec| { + let e = self.preserve_side_effect_of_decorator(dec.expr.take()); + + dec.expr = e; + }) + } + + fn process_prop_name(&mut self, name: &mut PropName) { + match name { + PropName::Ident(..) => {} + PropName::Computed(c) if c.expr.is_ident() => {} + _ => { + let ident = private_ident!("_computedKey"); + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(ident.clone().into()), + init: None, + definite: false, + }); + + self.pre_class_inits.push(Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(ident.clone().into()), + right: Box::new(prop_name_to_expr_value(name.take())), + }))); + *name = PropName::Computed(ComputedPropName { + span: DUMMY_SP, + expr: ident.into(), + }); + } + } + } + + fn process_decorators_of_class_members(&mut self, members: &mut [ClassMember]) { + for cm in members { + match cm { + ClassMember::Method(m) => { + self.process_decorators(&mut m.function.decorators); + self.process_prop_name(&mut m.key); + } + ClassMember::PrivateMethod(m) => { + self.process_decorators(&mut m.function.decorators); + } + ClassMember::ClassProp(m) => { + self.process_decorators(&mut m.decorators); + self.process_prop_name(&mut m.key); + } + ClassMember::PrivateProp(m) => { + self.process_decorators(&mut m.decorators); + } + ClassMember::AutoAccessor(m) => { + self.process_decorators(&mut m.decorators); + } + + _ => {} + } + + self.process_param_decorators(cm); + } + } +} + +impl VisitMut for Decorator202203 { + noop_visit_mut_type!(); + + fn visit_mut_class(&mut self, n: &mut Class) { + let old_stmts = self.state.extra_stmts.take(); + + n.visit_mut_children_with(self); + + if !self.state.is_init_proto_called { + if let Some(init_proto) = self.state.init_proto.clone() { + let c = self.ensure_constructor(n); + + inject_after_super( + c, + vec![Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init_proto.as_callee(), + args: vec![ThisExpr { span: DUMMY_SP }.as_arg()], + type_args: Default::default(), + }))], + ) + } + } + + self.consume_inits(); + + if !self.state.extra_stmts.is_empty() { + n.body.insert( + 0, + ClassMember::StaticBlock(StaticBlock { + span: DUMMY_SP, + body: BlockStmt { + span: DUMMY_SP, + stmts: self.state.extra_stmts.take(), + }, + }), + ); + } + + self.state.init_proto = None; + self.state.is_init_proto_called = false; + + self.state.extra_stmts = old_stmts; + } + + fn visit_mut_class_member(&mut self, n: &mut ClassMember) { + n.visit_mut_children_with(self); + + if let ClassMember::PrivateMethod(p) = n { + if p.function.decorators.is_empty() { + return; + } + + let decorators = self.preserve_side_effect_of_decorators(p.function.decorators.take()); + let dec = merge_decorators(decorators); + + let init = private_ident!(format!("_call_{}", p.key.id.sym)); + + self.extra_vars.push(VarDeclarator { + span: p.span, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + if p.is_static { + self.state + .init_static + .get_or_insert_with(|| private_ident!("_initStatic")); + } else { + self.state + .init_proto + .get_or_insert_with(|| private_ident!("_initProto")); + } + + let caller = FnExpr { + ident: None, + function: p.function.clone(), + }; + + let arg = Some( + ArrayLit { + span: DUMMY_SP, + elems: vec![ + dec, + Some( + (match p.kind { + MethodKind::Method => METHOD, + MethodKind::Setter => SETTER, + MethodKind::Getter => GETTER, + } + if p.is_static { STATIC } else { 0 }) + .as_arg(), + ), + Some(p.key.id.sym.clone().as_arg()), + Some(caller.as_arg()), + ], + } + .as_arg(), + ); + if p.is_static { + self.state.init_static_args.push(arg); + } else { + self.state.init_proto_args.push(arg); + } + + if p.is_static { + self.state.static_lhs.push(init.clone()); + } else { + self.state.proto_lhs.push(init.clone()); + } + + match p.kind { + MethodKind::Method => { + let call_stmt = Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Ident(init))), + }); + + p.kind = MethodKind::Getter; + p.function.body = Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![call_stmt], + }); + } + MethodKind::Getter => { + let call_stmt = Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.as_callee(), + args: vec![ThisExpr { span: DUMMY_SP }.as_arg()], + type_args: Default::default(), + }))), + }); + + p.function.body = Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![call_stmt], + }); + } + MethodKind::Setter => { + let call_stmt = Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.as_callee(), + args: vec![ + ThisExpr { span: DUMMY_SP }.as_arg(), + p.function.params[0].pat.clone().expect_ident().id.as_arg(), + ], + type_args: Default::default(), + }))), + }); + + p.function.body = Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![call_stmt], + }); + } + } + } + } + + fn visit_mut_class_members(&mut self, members: &mut Vec) { + let mut new = Vec::with_capacity(members.len()); + + self.process_decorators_of_class_members(members); + + for mut m in members.take() { + match m { + ClassMember::AutoAccessor(mut accessor) => { + let name; + let init; + let field_name_like: JsWord; + let private_field = PrivateProp { + span: DUMMY_SP, + key: match &mut accessor.key { + Key::Private(k) => { + name = Box::new(Expr::Lit(Lit::Str(Str { + span: DUMMY_SP, + value: k.id.sym.clone(), + raw: None, + }))); + init = private_ident!(format!("_init_{}", k.id.sym)); + field_name_like = format!("__{}", k.id.sym).into(); + + PrivateName { + span: k.span, + id: Ident::new(format!("__{}", k.id.sym).into(), k.id.span), + } + } + Key::Public(k) => { + (name, init) = self.initializer_name(k, "init"); + field_name_like = format!("__{}", init.sym) + .replacen("init", "private", 1) + .into(); + + PrivateName { + span: init.span.with_ctxt(SyntaxContext::empty()), + id: Ident::new( + field_name_like.clone(), + init.span.with_ctxt(SyntaxContext::empty()), + ), + } + } + }, + value: if accessor.decorators.is_empty() { + accessor.value + } else { + let init_proto = + if self.state.is_init_proto_called || accessor.is_static { + None + } else { + self.state.is_init_proto_called = true; + + let init_proto = self + .state + .init_proto + .get_or_insert_with(|| private_ident!("_initProto")) + .clone(); + + Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init_proto.clone().as_callee(), + args: vec![ThisExpr { span: DUMMY_SP }.as_arg()], + type_args: Default::default(), + }))) + }; + + let init_call = Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.clone().as_callee(), + args: once(ThisExpr { span: DUMMY_SP }.as_arg()) + .chain(accessor.value.take().map(|v| v.as_arg())) + .collect(), + type_args: Default::default(), + })); + + Some(Expr::from_exprs( + init_proto.into_iter().chain(once(init_call)).collect(), + )) + }, + type_ann: None, + is_static: accessor.is_static, + decorators: Default::default(), + accessibility: Default::default(), + is_optional: false, + is_override: false, + readonly: false, + definite: false, + }; + + let mut getter_function = Box::new(Function { + params: Default::default(), + decorators: Default::default(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: ThisExpr { span: DUMMY_SP }.into(), + prop: MemberProp::PrivateName(private_field.key.clone()), + }))), + })], + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }); + let mut setter_function = { + let param = private_ident!("_v"); + + Box::new(Function { + params: vec![Param { + span: DUMMY_SP, + decorators: Default::default(), + pat: param.clone().into(), + }], + decorators: Default::default(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: swc_ecma_ast::PatOrExpr::Expr(Box::new( + Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: ThisExpr { span: DUMMY_SP }.into(), + prop: MemberProp::PrivateName( + private_field.key.clone(), + ), + }), + )), + right: param.clone().into(), + })), + })], + }), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }) + }; + + if !accessor.decorators.is_empty() { + let decorators = + self.preserve_side_effect_of_decorators(accessor.decorators.take()); + let dec = merge_decorators(decorators); + + self.extra_vars.push(VarDeclarator { + span: accessor.span, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + let (getter_var, setter_var) = match &accessor.key { + Key::Private(_) => ( + Some(private_ident!(format!("_get_{}", field_name_like))), + Some(private_ident!(format!("_set_{}", field_name_like))), + ), + Key::Public(_) => Default::default(), + }; + + let initialize_init = { + ArrayLit { + span: DUMMY_SP, + elems: match &accessor.key { + Key::Private(_) => { + let data = vec![ + dec, + Some(if accessor.is_static { + (ACCESSOR + STATIC).as_arg() + } else { + ACCESSOR.as_arg() + }), + Some(name.as_arg()), + Some( + FnExpr { + ident: None, + function: getter_function, + } + .as_arg(), + ), + Some( + FnExpr { + ident: None, + function: setter_function, + } + .as_arg(), + ), + ]; + + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(getter_var.clone().unwrap().into()), + init: None, + definite: false, + }); + self.extra_vars.push(VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident(setter_var.clone().unwrap().into()), + init: None, + definite: false, + }); + + getter_function = Box::new(Function { + params: vec![], + decorators: Default::default(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: getter_var + .clone() + .unwrap() + .as_callee(), + args: vec![ + ThisExpr { span: DUMMY_SP }.as_arg() + ], + type_args: Default::default(), + }))), + })], + }), + is_generator: false, + is_async: false, + type_params: Default::default(), + return_type: Default::default(), + }); + + let param = private_ident!("_v"); + + setter_function = Box::new(Function { + params: vec![Param { + span: DUMMY_SP, + decorators: Default::default(), + pat: param.clone().into(), + }], + decorators: Default::default(), + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: setter_var + .clone() + .unwrap() + .as_callee(), + args: vec![ + ThisExpr { span: DUMMY_SP }.as_arg(), + param.as_arg(), + ], + type_args: Default::default(), + })), + })], + }), + is_generator: false, + is_async: false, + type_params: Default::default(), + return_type: Default::default(), + }); + + data + } + Key::Public(_) => { + vec![ + dec, + Some(if accessor.is_static { + (ACCESSOR + STATIC).as_arg() + } else { + ACCESSOR.as_arg() + }), + Some(name.as_arg()), + ] + } + }, + } + .as_arg() + }; + + if accessor.is_static { + self.state.static_lhs.push(init); + self.state.init_static_args.push(Some(initialize_init)); + self.state + .static_lhs + .extend(getter_var.into_iter().chain(setter_var)); + } else { + self.state.proto_lhs.push(init); + self.state.init_proto_args.push(Some(initialize_init)); + self.state + .proto_lhs + .extend(getter_var.into_iter().chain(setter_var)); + } + + if accessor.is_static { + self.state + .init_static + .get_or_insert_with(|| private_ident!("_initStatic")); + } else { + self.state + .init_proto + .get_or_insert_with(|| private_ident!("_initProto")); + } + } + + match accessor.key { + Key::Private(key) => { + let getter = PrivateMethod { + span: DUMMY_SP, + key: key.clone(), + function: getter_function, + kind: MethodKind::Getter, + is_static: accessor.is_static, + accessibility: None, + is_abstract: false, + is_optional: false, + is_override: false, + }; + let setter = PrivateMethod { + span: DUMMY_SP, + key: key.clone(), + function: setter_function, + kind: MethodKind::Setter, + is_static: accessor.is_static, + accessibility: None, + is_abstract: false, + is_optional: false, + is_override: false, + }; + + new.push(ClassMember::PrivateProp(private_field)); + new.push(ClassMember::PrivateMethod(getter)); + new.push(ClassMember::PrivateMethod(setter)); + } + Key::Public(key) => { + let getter = ClassMethod { + span: DUMMY_SP, + key: key.clone(), + function: getter_function, + kind: MethodKind::Getter, + is_static: accessor.is_static, + accessibility: None, + is_abstract: false, + is_optional: false, + is_override: false, + }; + let setter = ClassMethod { + span: DUMMY_SP, + key: key.clone(), + function: setter_function, + kind: MethodKind::Setter, + is_static: accessor.is_static, + accessibility: None, + is_abstract: false, + is_optional: false, + is_override: false, + }; + + new.push(ClassMember::PrivateProp(private_field)); + new.push(ClassMember::Method(getter)); + new.push(ClassMember::Method(setter)); + } + } + + continue; + } + + ClassMember::Method(..) | ClassMember::PrivateMethod(..) => { + m.visit_mut_with(self); + } + + _ => {} + } + + new.push(m); + } + + for mut m in new.take() { + match &mut m { + ClassMember::Method(..) | ClassMember::PrivateMethod(..) => {} + + _ => { + if !m.span().is_dummy() { + m.visit_mut_with(self); + } + } + } + + new.push(m); + } + + *members = new; + } + + fn visit_mut_class_method(&mut self, n: &mut ClassMethod) { + n.visit_mut_children_with(self); + + if n.function.decorators.is_empty() { + return; + } + + let decorators = self.preserve_side_effect_of_decorators(n.function.decorators.take()); + let dec = merge_decorators(decorators); + + let (name, _init) = self.initializer_name(&mut n.key, "call"); + + if n.is_static { + self.state + .init_static + .get_or_insert_with(|| private_ident!("_initStatic")); + } else { + self.state + .init_proto + .get_or_insert_with(|| private_ident!("_initProto")); + } + + let arg = Some( + ArrayLit { + span: DUMMY_SP, + elems: vec![ + dec, + Some( + match (n.is_static, n.kind) { + (true, MethodKind::Method) => METHOD + STATIC, + (false, MethodKind::Method) => METHOD, + (true, MethodKind::Setter) => SETTER + STATIC, + (false, MethodKind::Setter) => SETTER, + (true, MethodKind::Getter) => GETTER + STATIC, + (false, MethodKind::Getter) => GETTER, + } + .as_arg(), + ), + Some(name.as_arg()), + ], + } + .as_arg(), + ); + if n.is_static { + self.state.init_static_args.push(arg); + } else { + self.state.init_proto_args.push(arg); + } + } + + fn visit_mut_class_prop(&mut self, p: &mut ClassProp) { + p.visit_mut_children_with(self); + + if p.decorators.is_empty() { + return; + } + + let decorators = self.preserve_side_effect_of_decorators(p.decorators.take()); + let dec = merge_decorators(decorators); + + let (name, init) = self.initializer_name(&mut p.key, "init"); + + self.extra_vars.push(VarDeclarator { + span: p.span, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + p.value = Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.clone().as_callee(), + args: once(ThisExpr { span: DUMMY_SP }.as_arg()) + .chain(p.value.take().map(|v| v.as_arg())) + .collect(), + type_args: Default::default(), + }))); + + let initialize_init = { + Some( + ArrayLit { + span: DUMMY_SP, + elems: vec![ + dec, + Some(if p.is_static { + (FIELD + STATIC).as_arg() + } else { + FIELD.as_arg() + }), + Some(name.as_arg()), + ], + } + .as_arg(), + ) + }; + + if p.is_static { + self.state.static_lhs.push(init); + self.state.init_static_args.push(initialize_init); + } else { + self.state.proto_lhs.push(init); + self.state.init_proto_args.push(initialize_init); + } + } + + fn visit_mut_expr(&mut self, e: &mut Expr) { + if let Expr::Class(c) = e { + if !c.class.decorators.is_empty() { + let new = self.handle_class_expr(&mut c.class, c.ident.as_ref()); + + c.visit_mut_with(self); + + *e = Expr::Seq(SeqExpr { + span: DUMMY_SP, + exprs: vec![Box::new(e.take()), Box::new(Expr::Ident(new))], + }); + + return; + } + } + + e.visit_mut_children_with(self); + } + + fn visit_mut_module_item(&mut self, s: &mut ModuleItem) { + match s { + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + span, + decl: Decl::Class(c), + })) => { + let ident = c.ident.clone(); + let span = *span; + let new_stmt = self.handle_class_decl(c); + + if let Some(new_stmt) = new_stmt { + *s = ModuleItem::Stmt(new_stmt); + self.extra_exports + .push(ExportSpecifier::Named(ExportNamedSpecifier { + span, + orig: ModuleExportName::Ident(ident), + exported: None, + is_type_only: false, + })); + return; + } + + s.visit_mut_children_with(self); + } + ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { + span: _, + decl: DefaultDecl::Class(c), + })) => { + self.handle_class_expr(&mut c.class, c.ident.as_ref()); + s.visit_mut_children_with(self); + } + _ => { + s.visit_mut_children_with(self); + } + } + } + + fn visit_mut_module_items(&mut self, n: &mut Vec) { + let old_extra_lets = self.extra_lets.take(); + + let mut new = Vec::with_capacity(n.len()); + + for mut n in n.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(), + ) + } + 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(n.take()); + } + + if !self.extra_vars.is_empty() { + prepend_stmt( + &mut new, + VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + decls: self.extra_vars.take(), + declare: false, + } + .into(), + ); + } + + if !self.extra_exports.is_empty() { + new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed( + NamedExport { + span: DUMMY_SP, + specifiers: self.extra_exports.take(), + src: None, + type_only: false, + with: None, + }, + ))); + } + + *n = new; + + if !self.rename_map.is_empty() { + n.visit_mut_with(&mut IdentRenamer::new(&self.rename_map)); + } + + self.extra_lets = old_extra_lets; + } + + fn visit_mut_private_prop(&mut self, p: &mut PrivateProp) { + p.visit_mut_children_with(self); + + if p.decorators.is_empty() { + return; + } + + let decorators = self.preserve_side_effect_of_decorators(p.decorators.take()); + let dec = merge_decorators(decorators); + + let init = private_ident!(format!("_init_{}", p.key.id.sym)); + + self.extra_vars.push(VarDeclarator { + span: p.span, + name: Pat::Ident(init.clone().into()), + init: None, + definite: false, + }); + + p.value = Some(Box::new(Expr::Call(CallExpr { + span: DUMMY_SP, + callee: init.clone().as_callee(), + args: once(ThisExpr { span: DUMMY_SP }.as_arg()) + .chain(p.value.take().map(|v| v.as_arg())) + .collect(), + type_args: Default::default(), + }))); + + let initialize_init = { + let access_expr = Box::new(Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::This(ThisExpr { span: DUMMY_SP })), + prop: MemberProp::PrivateName(p.key.clone()), + })); + + let getter = Box::new(Function { + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Return(ReturnStmt { + span: DUMMY_SP, + arg: Some(access_expr.clone()), + })], + }), + is_async: false, + is_generator: false, + decorators: Default::default(), + params: Default::default(), + type_params: Default::default(), + return_type: Default::default(), + }); + let settter_arg = private_ident!("value"); + let setter = Box::new(Function { + span: DUMMY_SP, + body: Some(BlockStmt { + span: DUMMY_SP, + stmts: vec![Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Box::new(Expr::Assign(AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Expr(access_expr), + right: Box::new(Expr::Ident(settter_arg.clone())), + })), + })], + }), + is_async: false, + is_generator: false, + decorators: Default::default(), + params: vec![Param { + span: DUMMY_SP, + decorators: Default::default(), + pat: Pat::Ident(settter_arg.into()), + }], + type_params: Default::default(), + return_type: Default::default(), + }); + + ArrayLit { + span: DUMMY_SP, + elems: vec![ + dec, + Some(if p.is_static { + (FIELD + STATIC).as_arg() + } else { + FIELD.as_arg() + }), + Some((&*p.key.id.sym).as_arg()), + Some( + FnExpr { + ident: None, + function: getter, + } + .as_arg(), + ), + Some( + FnExpr { + ident: None, + function: setter, + } + .as_arg(), + ), + ], + } + .as_arg() + }; + + if p.is_static { + self.state.static_lhs.push(init); + self.state.init_static_args.push(Some(initialize_init)); + } else { + self.state.proto_lhs.push(init); + self.state.init_proto_args.push(Some(initialize_init)); + } + } + + fn visit_mut_stmt(&mut self, s: &mut Stmt) { + if let Stmt::Decl(Decl::Class(c)) = s { + let new_stmt = self.handle_class_decl(c); + + if let Some(new_stmt) = new_stmt { + *s = new_stmt; + return; + } + } + + s.visit_mut_children_with(self); + } + + fn visit_mut_stmts(&mut self, n: &mut Vec) { + let old_state = take(&mut self.state); + let old_pre_class_inits = self.pre_class_inits.take(); + let old_extra_lets = self.extra_lets.take(); + let old_extra_vars = self.extra_vars.take(); + + let mut new = Vec::with_capacity(n.len()); + + for mut n in n.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, + })))) + } + if !self.pre_class_inits.is_empty() { + new.push(Stmt::Expr(ExprStmt { + span: DUMMY_SP, + expr: Expr::from_exprs(self.pre_class_inits.take()), + })) + } + new.push(n.take()); + } + + if !self.extra_vars.is_empty() { + prepend_stmt( + &mut new, + VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + decls: self.extra_vars.take(), + declare: false, + } + .into(), + ); + } + + *n = new; + + self.extra_vars = old_extra_vars; + self.extra_lets = old_extra_lets; + self.pre_class_inits = old_pre_class_inits; + self.state = old_state; + } +} + +fn merge_decorators(decorators: Vec>) -> Option { + if decorators.len() == 1 { + return decorators.into_iter().next().unwrap(); + } + + Some( + ArrayLit { + span: DUMMY_SP, + elems: decorators, + } + .as_arg(), + ) +} diff --git a/src/parser/transformers/mod.rs b/src/parser/transformers/mod.rs new file mode 100644 index 0000000..88ae1a1 --- /dev/null +++ b/src/parser/transformers/mod.rs @@ -0,0 +1,9 @@ +mod anonymous_expr; +mod class_jobject; +mod class_reflection_decorators; +mod decorator_2022_03; + +pub(crate) use anonymous_expr::anonymous_expr; +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; diff --git a/src/parser/util.rs b/src/parser/util.rs new file mode 100644 index 0000000..fdea819 --- /dev/null +++ b/src/parser/util.rs @@ -0,0 +1,10 @@ +use swc_common::DUMMY_SP; +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/stack/mod.rs b/src/stack/mod.rs new file mode 100644 index 0000000..2fe60c4 --- /dev/null +++ b/src/stack/mod.rs @@ -0,0 +1,37 @@ +mod trace; + +use lazy_static::lazy_static; +use std::collections::HashMap; +use std::sync::RwLock; + +pub(crate) use trace::remap_stack_trace; + +struct InternalSourceMap(sourcemap::SourceMap); +unsafe impl Send for InternalSourceMap {} +unsafe impl Sync for InternalSourceMap {} + +lazy_static! { + static ref FILE_MAPPINGS: RwLock> = + RwLock::new(HashMap::new()); +} + +pub(crate) struct Frame { + pub filename: Option, + pub line_no: u32, + pub col_no: u32, + pub function_name: Option, + pub method_name: Option, + pub type_name: Option, + pub is_native: bool, + pub is_top_level: bool, + pub is_constructor: bool, + pub is_async: bool, + pub is_promise_all: bool, + pub promise_index: usize, + pub string_repr: String, +} + +pub fn register_source_map(filename: String, srcmap: sourcemap::SourceMap) { + let mut mappings = FILE_MAPPINGS.write().unwrap(); + mappings.insert(filename, InternalSourceMap(srcmap)); +} diff --git a/src/stack/trace.rs b/src/stack/trace.rs new file mode 100644 index 0000000..f5d03d8 --- /dev/null +++ b/src/stack/trace.rs @@ -0,0 +1,121 @@ +use super::{Frame, FILE_MAPPINGS}; + +/// Prepares stack trace using V8 stack trace API. +pub(crate) fn remap_stack_trace( + error_message: &str, + stack: Box<[Frame]>, + previous: Option, +) -> String { + let mut processed = false; + let mappings = FILE_MAPPINGS.read().unwrap(); + let new_stack = stack + .iter() + .map(|frame| { + let file_name = frame.filename.as_deref().unwrap_or_default(); + if frame.is_native { + return frame.string_repr.clone(); + } + + let Some(source_map) = mappings.get(file_name) else { + return frame.string_repr.clone(); + }; + + let Some(token) = source_map.0.lookup_token(frame.line_no, frame.col_no) else { + return frame.string_repr.clone(); + }; + + let file_location = format!( + "{}:{}:{}", + file_name, + token.get_src_line(), + token.get_src_col() + ); + let mut function_name = frame.function_name.as_ref().cloned(); + if let Some(sv) = source_map.0.get_source_view(token.get_src_id()) { + function_name = source_map + .0 + .get_original_function_name( + frame.line_no, + frame.col_no, + function_name.unwrap_or_default().as_str(), + sv, + ) + .map(|f| f.to_string()); + } + + if let Some(fun) = &function_name { + if fun.starts_with("_anonymous_xΞ") { + function_name = None; + } + } + + let is_top_level = frame.is_top_level; + let is_constructor = frame.is_constructor; + let is_method_call = !(is_top_level || is_constructor); + + let generate_function_call = || { + let mut call = "".to_string(); + + if is_method_call { + if let Some(function_name) = &function_name { + if let Some(type_name) = &frame.type_name { + if !function_name.starts_with(type_name) { + call += type_name; + call += "."; + } + } + + call += function_name; + + if let Some(method_name) = &frame.method_name { + if !function_name.ends_with(method_name) { + call += " [as "; + call += method_name; + call += "]"; + } + } + } else { + if let Some(type_name) = &frame.type_name { + call += type_name; + call += "."; + } + + call += frame.method_name.as_deref().unwrap_or(""); + } + } else if is_constructor { + call += "new "; + call += &function_name.unwrap_or("".to_string()); + } else if let Some(function_name) = &function_name { + call += function_name; + } else { + call += &file_location; + return call; + } + + call += " ("; + call += &file_location; + call += ")"; + + call + }; + + processed = true; + format!( + "{}{}{}", + if frame.is_async { "async " } else { "" }, + if frame.is_promise_all { + format!("Promise.all (index {})", frame.promise_index) + } else { + "".to_string() + }, + generate_function_call() + ) + }) + .collect::>(); + + if previous.is_some() && !processed { + previous.unwrap() + } else { + String::from(error_message) + "\n\n at " + new_stack.join("\n at ").as_str() + } +} diff --git a/src/testing/inject_helpers.rs b/src/testing/inject_helpers.rs new file mode 100644 index 0000000..27ff9e3 --- /dev/null +++ b/src/testing/inject_helpers.rs @@ -0,0 +1,109 @@ +use std::path::Path; +use swc_common::{DUMMY_SP, Mark}; +use swc_ecma_ast::*; +use swc_ecma_utils::{ExprFactory, prepend_stmts, quote_ident}; +use swc_ecma_visit::{as_folder, Fold, noop_visit_mut_type, VisitMut, VisitMutWith}; + +pub fn inject_helpers(global_mark: Mark) -> impl Fold + VisitMut { + as_folder(InjectHelpers { + global_mark, + }) +} + +struct InjectHelpers { + global_mark: Mark, +} + +impl InjectHelpers { + fn build_helper_path(name: &str) -> String { + Path::new(env!("CARGO_MANIFEST_DIR")) + .join("lib") + .join(&format!("_{}.js", name)) + .to_string_lossy() + .to_string() + } + + fn build_import(&self, name: &str, mark: Mark) -> ModuleItem { + let s = ImportSpecifier::Named(ImportNamedSpecifier { + span: DUMMY_SP, + local: Ident::new( + format!("_{}", name).into(), + DUMMY_SP.apply_mark(mark), + ), + imported: Some(quote_ident!("_").into()), + is_type_only: false, + }); + + let src: Str = Self::build_helper_path(name).into(); + + ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { + span: DUMMY_SP, + specifiers: vec![s], + src: Box::new(src), + with: Default::default(), + type_only: Default::default(), + })) + } + + fn build_require(&self, name: &str, mark: Mark) -> Stmt { + let c = CallExpr { + span: DUMMY_SP, + callee: Expr::Ident(Ident { + span: DUMMY_SP.apply_mark(self.global_mark), + sym: "require".into(), + optional: false, + }) + .as_callee(), + args: vec![Str { + span: DUMMY_SP, + value: Self::build_helper_path(name).into(), + raw: None, + } + .as_arg()], + type_args: None, + }; + let decl = Decl::Var( + VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Var, + declare: false, + decls: vec![VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident( + Ident::new(format!("_{}", name).into(), DUMMY_SP.apply_mark(mark)).into(), + ), + init: Some(c.into()), + definite: false, + }], + } + .into(), + ); + Stmt::Decl(decl) + } +} + +impl VisitMut for InjectHelpers { + noop_visit_mut_type!(); + + fn visit_mut_script(&mut self, script: &mut Script) { + let helpers = vec![self.build_require("apply_decs_2203_r", self.global_mark)]; + let helpers_is_empty = helpers.is_empty(); + + prepend_stmts(&mut script.body, helpers.into_iter()); + + if !helpers_is_empty { + script.visit_mut_children_with(self); + } + } + + fn visit_mut_module(&mut self, n: &mut Module) { + let helpers = vec![self.build_import("apply_decs_2203_r", self.global_mark)]; + let helpers_is_empty = helpers.is_empty(); + + prepend_stmts(&mut n.body, helpers.into_iter()); + + if !helpers_is_empty { + n.visit_mut_children_with(self); + } + } +} diff --git a/src/testing/mod.rs b/src/testing/mod.rs new file mode 100644 index 0000000..e1f2af0 --- /dev/null +++ b/src/testing/mod.rs @@ -0,0 +1,152 @@ +mod inject_helpers; + +use std::{env, fs}; +use std::fs::{create_dir_all, OpenOptions}; +use std::io::Write; +use std::path::Path; +use std::process::Command; +use ansi_term::Color; +use sha1::{Digest, Sha1}; +use swc_common::{Mark}; +use swc_ecma_parser::Syntax; +use swc_ecma_transforms_base::{ + fixer, + hygiene, +}; +use swc_ecma_transforms_testing::{HygieneVisualizer, Tester}; +use swc_ecma_visit::{Fold, FoldWith}; +use tempfile::tempdir_in; +use testing::find_executable; +pub use inject_helpers::inject_helpers; + +fn make_tr(op: F, tester: &mut Tester<'_>) -> impl Fold + where + F: FnOnce(&mut Tester<'_>) -> P, + P: Fold, +{ + op(tester) +} + +fn calc_hash(s: &str) -> String { + let mut hasher = Sha1::default(); + hasher.update(s.as_bytes()); + let sum = hasher.finalize(); + + hex::encode(sum) +} + +/// Execute `jest` after transpiling `input` using `tr`. +pub fn exec_tr(test_name: &str, syntax: Syntax, tr: F, input: &str) + where + F: FnOnce(&mut Tester<'_>) -> P, + P: Fold, +{ + Tester::run(|tester| { + let tr = make_tr(tr, tester); + + let module = tester.apply_transform( + tr, + "input.js", + syntax, + &format!( + "it('should work', async function () {{ + {} + }})", + input + ), + )?; + match ::std::env::var("PRINT_HYGIENE") { + Ok(ref s) if s == "1" => { + let hygiene_src = tester.print( + &module.clone().fold_with(&mut HygieneVisualizer), + &tester.comments.clone(), + ); + println!("----- Hygiene -----\n{}", hygiene_src); + } + _ => {} + } + + let mut module = module + .fold_with(&mut hygiene::hygiene()) + .fold_with(&mut fixer::fixer(Some(&tester.comments))); + + let src_without_helpers = tester.print(&module, &tester.comments.clone()); + module = module.fold_with(&mut inject_helpers(Mark::fresh(Mark::root()))); + + let src = tester.print(&module, &tester.comments.clone()); + + println!( + "\t>>>>> {} <<<<<\n{}\n\t>>>>> {} <<<<<\n{}", + Color::Green.paint("Orig"), + input, + Color::Green.paint("Code"), + src_without_helpers + ); + + exec_with_node_test_runner(test_name, &src) + }) +} + +fn exec_with_node_test_runner(test_name: &str, src: &str) -> Result<(), ()> { + let root = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("target") + .join("testing") + .join(test_name); + + create_dir_all(&root).expect("failed to create parent directory for temp directory"); + + let hash = calc_hash(src); + let success_cache = root.join(format!("{}.success", hash)); + + if env::var("CACHE_TEST").unwrap_or_default() == "1" { + println!("Trying cache as `CACHE_TEST` is `1`"); + + if success_cache.exists() { + println!("Cache: success"); + return Ok(()); + } + } + + let tmp_dir = tempdir_in(&root).expect("failed to create a temp directory"); + create_dir_all(&tmp_dir).unwrap(); + + let path = tmp_dir.path().join(format!("{}.test.mjs", test_name)); + + let mut tmp = OpenOptions::new() + .create(true) + .write(true) + .open(&path) + .expect("failed to create a temp file"); + write!(tmp, "{}", src).expect("failed to write to temp file"); + tmp.flush().unwrap(); + + let test_runner_path = find_executable("mocha").expect("failed to find `mocha` from path"); + + let mut base_cmd = if cfg!(target_os = "windows") { + let mut c = Command::new("cmd"); + c.arg("/C").arg(&test_runner_path); + c + } else { + Command::new(&test_runner_path) + }; + + let output = base_cmd + .arg(&format!("{}", path.display())) + .arg("--color") + .current_dir(root) + .output() + .expect("failed to run mocha"); + + println!(">>>>> {} <<<<<", Color::Red.paint("Stdout")); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!(">>>>> {} <<<<<", Color::Red.paint("Stderr")); + println!("{}", String::from_utf8_lossy(&output.stderr)); + + if output.status.success() { + fs::write(&success_cache, "").unwrap(); + return Ok(()); + } + let dir_name = path.display().to_string(); + ::std::mem::forget(tmp_dir); + panic!("Execution failed: {dir_name}") +} \ No newline at end of file diff --git a/src/wasm/mod.rs b/src/wasm/mod.rs new file mode 100644 index 0000000..26f32cc --- /dev/null +++ b/src/wasm/mod.rs @@ -0,0 +1,156 @@ +extern crate alloc; + +use crate::stack::remap_stack_trace; +use crate::Frame; +use js_sys::*; +use std::iter::Iterator; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Error")] + pub type Error; + + #[wasm_bindgen(method, getter)] + pub fn name(this: &Error) -> String; + + #[wasm_bindgen(method, getter)] + pub fn message(this: &Error) -> JsValue; + + #[wasm_bindgen(method, getter)] + pub fn stack(this: &Error) -> Option; + + #[wasm_bindgen(static_method_of = Error, getter = prepareStackTrace)] + pub fn prepare_stack_trace() -> Option; + #[wasm_bindgen(static_method_of = Error, setter = prepareStackTrace)] + pub fn set_prepare_stack_trace(closure: &Function); + + #[wasm_bindgen(method, js_name = toString)] + pub fn to_string(this: &Error) -> String; +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "NodeJS.CallSite")] + pub type CallSite; + + /// Value of "this" + #[wasm_bindgen(method, js_name = getThis)] + pub fn get_this(this: &CallSite) -> JsValue; + + /// Type of "this" as a string. + /// This is the name of the function stored in the constructor field of + /// "this", if available. Otherwise the object's [[Class]] internal + /// property. + #[wasm_bindgen(method, js_name = getTypeName)] + pub fn get_type_name(this: &CallSite) -> Option; + + /// Current function + #[wasm_bindgen(method, js_name = getFunction)] + pub fn get_function(this: &CallSite) -> Option; + + /// Name of the current function, typically its name property. + /// If a name property is not available an attempt will be made to try + /// to infer a name from the function's context. + #[wasm_bindgen(method, js_name = getFunctionName)] + pub fn get_function_name(this: &CallSite) -> Option; + + /// Name of the property [of "this" or one of its prototypes] that holds + /// the current function + #[wasm_bindgen(method, js_name = getMethodName)] + pub fn get_method_name(this: &CallSite) -> Option; + + /// Name of the script [if this function was defined in a script] + #[wasm_bindgen(method, js_name = getFileName)] + pub fn get_file_name(this: &CallSite) -> Option; + + /// Current line number [if this function was defined in a script] + #[wasm_bindgen(method, js_name = getLineNumber)] + pub fn get_line_number(this: &CallSite) -> Option; + + /// Current column number [if this function was defined in a script] + #[wasm_bindgen(method, js_name = getColumnNumber)] + pub fn get_column_number(this: &CallSite) -> Option; + + /// A call site object representing the location where eval was called + /// [if this function was created using a call to eval] + #[wasm_bindgen(method, js_name = getEvalOrigin)] + pub fn get_eval_origin(this: &CallSite) -> Option; + + /// Is this a toplevel invocation, that is, is "this" the global object? + #[wasm_bindgen(method, js_name = isToplevel)] + pub fn is_top_level(this: &CallSite) -> bool; + + /// Does this call take place in code defined by a call to eval? + #[wasm_bindgen(method, js_name = isEval)] + pub fn is_eval(this: &CallSite) -> bool; + + /// Is this call in native V8 code? + #[wasm_bindgen(method, js_name = isNative)] + pub fn is_native(this: &CallSite) -> bool; + + /// Is this a constructor call? + #[wasm_bindgen(method, js_name = isConstructor)] + pub fn is_constructor(this: &CallSite) -> bool; + + /// Is this an async call (i.e. await, Promise.all(), or Promise.any())? + #[wasm_bindgen(method, js_name = isAsync)] + pub fn is_async(this: &CallSite) -> bool; + + /// Is this an async call to Promise.all()? + #[wasm_bindgen(method, js_name = isPromiseAll)] + pub fn is_promise_all(this: &CallSite) -> bool; + + /// Is this an async call to Promise.any()? + #[wasm_bindgen(method, js_name = isPromiseAny)] + pub fn is_promise_any(this: &CallSite) -> bool; + + /// Returns the index of the promise element that was followed in + /// Promise.all() or Promise.any() for async stack traces, or null if the + /// CallSite is not an async Promise.all() or Promise.any() call. + #[wasm_bindgen(method, js_name = getPromiseIndex)] + pub fn get_promise_index(this: &CallSite) -> Option; + + #[wasm_bindgen(method, js_name = toString)] + pub fn to_string(this: &CallSite) -> String; +} + +impl From<&CallSite> for Frame { + fn from(value: &CallSite) -> Self { + Self { + filename: value.get_file_name(), + line_no: value.get_line_number().unwrap_or_default() as u32, + col_no: value.get_column_number().unwrap_or_default() as u32, + function_name: value.get_function_name(), + method_name: value.get_method_name(), + type_name: value.get_type_name(), + is_native: value.is_native(), + is_top_level: value.is_top_level(), + is_constructor: value.is_constructor(), + is_async: value.is_async(), + is_promise_all: value.is_promise_all(), + promise_index: value.get_promise_index().unwrap_or_default(), + string_repr: value.to_string(), + } + } +} + +#[wasm_bindgen(js_name = prepareStackTrace)] +pub fn prepare_stack_trace( + error: Error, + stack: Box<[CallSite]>, + previous: Option, +) -> String { + let message: JsValue = error.message(); + let message: String = message + .dyn_ref::() + .cloned() + .unwrap_or_else(|| JsString::from("")) + .into(); + + let stack = stack + .into_iter() + .map(|cs| Frame::from(cs)) + .collect::>(); + remap_stack_trace(&message, stack.into_boxed_slice(), previous) +} diff --git a/tests/decorators/2022-03-metadata/class/exec.js b/tests/decorators/2022-03-metadata/class/exec.js new file mode 100644 index 0000000..267e022 --- /dev/null +++ b/tests/decorators/2022-03-metadata/class/exec.js @@ -0,0 +1,11 @@ +function dec(_, ctx) { + ctx.metadata.foo = 3; +} + +Symbol.metadata = Symbol(); + +@dec +class A {} + +expect(A[Symbol.metadata]).toEqual({ foo: 3 }); +expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null); diff --git a/tests/decorators/2022-03-metadata/element/exec.js b/tests/decorators/2022-03-metadata/element/exec.js new file mode 100644 index 0000000..c70d840 --- /dev/null +++ b/tests/decorators/2022-03-metadata/element/exec.js @@ -0,0 +1,14 @@ +function dec(_, ctx) { + console.error(ctx); + ctx.metadata.foo = 3; +} + +Symbol.metadata = Symbol(); + +class A { + @dec + foo; +} + +expect(A[Symbol.metadata]).toEqual({ foo: 3 }); +expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null); diff --git a/tests/decorators/2022-03-metadata/no-decorators/exec.js b/tests/decorators/2022-03-metadata/no-decorators/exec.js new file mode 100644 index 0000000..9f2d6b4 --- /dev/null +++ b/tests/decorators/2022-03-metadata/no-decorators/exec.js @@ -0,0 +1,5 @@ +Symbol.metadata = Symbol(); + +class A {} + +expect(A.hasOwnProperty(Symbol.metadata)).toBe(false); diff --git a/tests/decorators/2022-03-metadata/options.json b/tests/decorators/2022-03-metadata/options.json new file mode 100644 index 0000000..4e9da04 --- /dev/null +++ b/tests/decorators/2022-03-metadata/options.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + ["proposal-decorators", { "version": "2022-03" }], + "proposal-class-properties", + "proposal-private-methods", + "proposal-class-static-block" + ] +} diff --git a/tests/decorators/2022-03-metadata/subclass-no-super-decorators/exec.js b/tests/decorators/2022-03-metadata/subclass-no-super-decorators/exec.js new file mode 100644 index 0000000..3949427 --- /dev/null +++ b/tests/decorators/2022-03-metadata/subclass-no-super-decorators/exec.js @@ -0,0 +1,15 @@ +function dec(v) { + return (_, ctx) => { + ctx.metadata.foo = v; + }; +} + +Symbol.metadata = Symbol(); + +class B {} + +@dec(3) +class A extends B {} + +expect(A[Symbol.metadata]).toEqual({ foo: 3 }); +expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null); diff --git a/tests/decorators/2022-03-metadata/subclass-super-decorators/exec.js b/tests/decorators/2022-03-metadata/subclass-super-decorators/exec.js new file mode 100644 index 0000000..c77dbb2 --- /dev/null +++ b/tests/decorators/2022-03-metadata/subclass-super-decorators/exec.js @@ -0,0 +1,18 @@ +function dec(v) { + return (_, ctx) => { + ctx.metadata.foo = v; + }; +} + +Symbol.metadata = Symbol(); + +@dec(2) +class B {} + +@dec(3) +class A extends B {} + +expect(A[Symbol.metadata]).toEqual({ foo: 3 }); +expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(B[Symbol.metadata]); +expect(B[Symbol.metadata]).toEqual({ foo: 2 }); +expect(Object.getPrototypeOf(B[Symbol.metadata])).toBe(null); diff --git a/tests/decorators/2022-03-parameter/constructor/exec.js b/tests/decorators/2022-03-parameter/constructor/exec.js new file mode 100644 index 0000000..4cda948 --- /dev/null +++ b/tests/decorators/2022-03-parameter/constructor/exec.js @@ -0,0 +1,20 @@ +function dec(_, ctx) { + expect(ctx.function.kind).toEqual('class'); + expect(ctx.function.name).toBeUndefined(); + ctx.metadata[Symbol.parameters] = {}; + ctx.metadata[Symbol.parameters][ctx.index] = { + rest: ctx.rest, + name: ctx.name, + foo: 5, + }; +} + +Symbol.metadata = Symbol(); +Symbol.parameters = Symbol(); + +class A { + constructor(@dec a) {} +} + +expect(A[Symbol.metadata][Symbol.parameters]).toEqual({ 0: { rest: false, name: 'a', foo: 5 } }); +expect(Object.getPrototypeOf(A[Symbol.metadata])).toBe(null); diff --git a/tests/decorators/2022-03-parameter/method-computed-key-do-not-call-twice/exec.js b/tests/decorators/2022-03-parameter/method-computed-key-do-not-call-twice/exec.js new file mode 100644 index 0000000..db7fa42 --- /dev/null +++ b/tests/decorators/2022-03-parameter/method-computed-key-do-not-call-twice/exec.js @@ -0,0 +1,27 @@ +let calls = 0; + +function generateMethodName() { + return 'testMethod' + (++calls); +} + +function dec(_, ctx) { + expect(ctx.function.kind).toEqual('method'); + expect(ctx.function.name).toEqual('testMethod1'); + ctx.metadata[ctx.function.name] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters][ctx.index] = { + rest: ctx.rest, + name: ctx.name, + foo: 5, + }; +} + +Symbol.metadata = Symbol(); +Symbol.parameters = Symbol(); + +class A { + [generateMethodName()](@dec a = 'x') {} +} + +expect(calls).toBe(1); +expect(A[Symbol.metadata].testMethod1[Symbol.parameters]).toEqual({ 0: { rest: false, name: undefined, foo: 5 } }); diff --git a/tests/decorators/2022-03-parameter/method-with-computed-key/exec.js b/tests/decorators/2022-03-parameter/method-with-computed-key/exec.js new file mode 100644 index 0000000..d42f256 --- /dev/null +++ b/tests/decorators/2022-03-parameter/method-with-computed-key/exec.js @@ -0,0 +1,22 @@ +const sym = Symbol(); + +function dec(_, ctx) { + expect(ctx.function.kind).toEqual('method'); + expect(ctx.function.name).toEqual(sym); + ctx.metadata[ctx.function.name] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters][ctx.index] = { + rest: ctx.rest, + name: ctx.name, + foo: 5, + }; +} + +Symbol.metadata = Symbol(); +Symbol.parameters = Symbol(); + +class A { + [sym](@dec [a]) {} +} + +expect(A[Symbol.metadata][sym][Symbol.parameters]).toEqual({ 0: { rest: false, name: undefined, foo: 5 } }); diff --git a/tests/decorators/2022-03-parameter/method/exec.js b/tests/decorators/2022-03-parameter/method/exec.js new file mode 100644 index 0000000..94d88dd --- /dev/null +++ b/tests/decorators/2022-03-parameter/method/exec.js @@ -0,0 +1,20 @@ +function dec(_, ctx) { + expect(ctx.function.kind).toEqual('method'); + expect(ctx.function.name).toEqual('testMethod'); + ctx.metadata[ctx.function.name] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters] = {}; + ctx.metadata[ctx.function.name][Symbol.parameters][ctx.index] = { + rest: ctx.rest, + name: ctx.name, + foo: 5, + }; +} + +Symbol.metadata = Symbol(); +Symbol.parameters = Symbol(); + +class A { + testMethod(@dec ...a) {} +} + +expect(A[Symbol.metadata].testMethod[Symbol.parameters]).toEqual({ 0: { rest: true, name: 'a', foo: 5 } }); diff --git a/tests/decorators/2022-03-parameter/options.json b/tests/decorators/2022-03-parameter/options.json new file mode 100644 index 0000000..4e9da04 --- /dev/null +++ b/tests/decorators/2022-03-parameter/options.json @@ -0,0 +1,8 @@ +{ + "plugins": [ + ["proposal-decorators", { "version": "2022-03" }], + "proposal-class-properties", + "proposal-private-methods", + "proposal-class-static-block" + ] +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..ef5f176 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,313 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.12.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== + dependencies: + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" + +"@babel/helper-validator-identifier@^7.22.20": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" + integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== + +"@babel/highlight@^7.22.13": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" + integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#412e0725ef41cde73bfa03e0e833eaff41e0fd63" + integrity sha512-gPQuzaPR5h/djlAv2apEG1HVOyj1IUs7GpfMZixU0/0KXT3pm64ylHuMUI1/Akh+sq/iikxg6Z2j+fcMDXaaTQ== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.2.tgz#edc8e421991a3b4df875036d381fc0a5a982f549" + integrity sha512-kv43F9eb3Lhj+lr/Hn6OcLCs/sSM8bt+fIaP11rCYngfV6NVjzWXJ17owQtDQTL9tQ8WSLUrGsSJ6rJz0F1w1A== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/node@*": + version "20.8.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa" + integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ== + dependencies: + undici-types "~5.25.1" + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/yargs-parser@*": + version "21.0.1" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.1.tgz#07773d7160494d56aa882d7531aac7319ea67c3b" + integrity sha512-axdPBuLuEJt0c4yI5OZssC19K2Mq1uKdrfZBzuxLvaztgqUtFYZUNw7lETExPYJR9jdEoIg4mb7RQKRQzOkeGQ== + +"@types/yargs@^16.0.0": + version "16.0.6" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.6.tgz#cc0c63684d68d23498cf0b5f32aa4c3fb437c638" + integrity sha512-oTP7/Q13GSPrgcwEwdlnkoZSQ1Hg9THe644qq8PG6hhJzjZ3qj1JjEFPIwWV/IXVs5XGIVqtkNOS9kh63WIJ+A== + dependencies: + "@types/yargs-parser" "*" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +expect@^27.4.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" + integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw== + dependencies: + "@jest/types" "^27.5.1" + jest-get-type "^27.5.1" + jest-matcher-utils "^27.5.1" + jest-message-util "^27.5.1" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-matcher-utils@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-message-util@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" + integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^27.5.1" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^27.5.1" + slash "^3.0.0" + stack-utils "^2.0.3" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +stack-utils@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" + integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== + dependencies: + escape-string-regexp "^2.0.0" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +undici-types@~5.25.1: + version "5.25.3" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" + integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==