From 9d5be29495b29b683aa8e60c7a98dda2a95d8d1e Mon Sep 17 00:00:00 2001 From: kanno <812137533@qq.com> Date: Sat, 5 Oct 2024 21:23:38 +0800 Subject: [PATCH 1/2] feat: support check experimental decorators --- package-lock.json | 4 +- package.json | 1 + src/index.ts | 10 +- src/options.ts | 34 + test/__snapshots__/index.ts.snap | 1371 +++++++++++++++++++++++++++++ test/fixtures/decorators/index.ts | 9 + test/index.ts | 9 + 7 files changed, 1432 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/decorators/index.ts diff --git a/package-lock.json b/package-lock.json index 7c19406..57ad3ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.11.2", "license": "MIT", "dependencies": { + "@dual-bundle/import-meta-resolve": "^4.1.0", "@fastify/deepmerge": "^2.0.0", "@rollup/pluginutils": "^5.1.0", "get-tsconfig": "^4.8.0", @@ -43,7 +44,7 @@ "vite": "^5.4.2" }, "engines": { - "node": ">=12" + "node": ">=16" }, "peerDependencies": { "@swc/core": ">=1.2.165", @@ -648,7 +649,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", - "dev": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" diff --git a/package.json b/package.json index d125741..16c3602 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "author": "", "license": "MIT", "dependencies": { + "@dual-bundle/import-meta-resolve": "^4.1.0", "@fastify/deepmerge": "^2.0.0", "@rollup/pluginutils": "^5.1.0", "get-tsconfig": "^4.8.0", diff --git a/src/index.ts b/src/index.ts index 57b42e4..0323d03 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import type { Plugin as RollupPlugin } from 'rollup'; - import fs from 'node:fs'; import process from 'node:process'; import { extname, resolve, dirname, join } from 'node:path'; @@ -15,7 +14,7 @@ import { } from '@swc/core'; import createDeepMerge from '@fastify/deepmerge'; -import { getOptions } from './options'; +import { getOptions, getEnableExperimentalDecorators } from './options'; import type { Plugin as VitePlugin } from 'vite'; @@ -116,6 +115,8 @@ function swc(options: PluginOptions = {}): RollupPlugin { ? {} : getOptions(this, dirname(id), options.tsconfig); + const enableExperimentalDecorators = getEnableExperimentalDecorators(this, isTypeScript, dirname(id), options.tsconfig); + // TODO: SWC is about to add "preserve" jsx // https://github.com/swc-project/swc/pull/5661 // Respect "preserve" after swc adds the support @@ -127,7 +128,7 @@ function swc(options: PluginOptions = {}): RollupPlugin { parser: { syntax: isTypeScript ? 'typescript' : 'ecmascript', [isTypeScript ? 'tsx' : 'jsx']: isTypeScript ? isTsx : isJsx, - decorators: tsconfigOptions.experimentalDecorators + decorators: enableExperimentalDecorators || tsconfigOptions.experimentalDecorators }, transform: { decoratorMetadata: tsconfigOptions.emitDecoratorMetadata, @@ -139,7 +140,8 @@ function swc(options: PluginOptions = {}): RollupPlugin { pragma: tsconfigOptions.jsxFactory, pragmaFrag: tsconfigOptions.jsxFragmentFactory, development: tsconfigOptions.jsx === 'react-jsxdev' ? true : undefined - } + }, + decoratorVersion: enableExperimentalDecorators ? '2022-03' : '2021-12' }, target: tsconfigOptions.target?.toLowerCase() as JscTarget | undefined, baseUrl: tsconfigOptions.baseUrl, diff --git a/src/options.ts b/src/options.ts index d4cca5c..9abdb84 100644 --- a/src/options.ts +++ b/src/options.ts @@ -1,11 +1,16 @@ import { getTsconfig, parseTsconfig } from 'get-tsconfig'; +import { resolve } from '@dual-bundle/import-meta-resolve'; import path from 'node:path'; +import fs from 'node:fs'; import type { TsConfigJson } from 'get-tsconfig'; import type { TransformPluginContext } from 'rollup'; +import { fileURLToPath } from 'node:url'; const cache = new Map(); +const tsExperimentCache = new Map(); + export const getOptions = ( ctx: TransformPluginContext, cwd: string, @@ -55,3 +60,32 @@ export const getOptions = ( cache.set(cacheKey, compilerOptions); return compilerOptions; }; + +export const getEnableExperimentalDecorators = ( + ctx: TransformPluginContext, isTypeScript: boolean, cwd: string, key?: string | boolean +) => { + if (!isTypeScript) return false; + const cacheKey = `${cwd}:${key ?? 'undefined'}`; + if (tsExperimentCache.has(cacheKey)) { + return tsExperimentCache.get(cacheKey) ?? false; + } + try { + // @ts-expect-error -- It's required to using 'import.mtea.url' but i don't want to change the tsconfig. + const tsPath = resolve('typescript/package.json', import.meta.url); + const { version } = JSON.parse(fs.readFileSync(fileURLToPath(tsPath), 'utf-8')); + const [major] = version.split('.'); + // Only enable experimental decorators for TypeScript 5+ + if (+major >= 5) { + tsExperimentCache.set(cacheKey, true); + return true; + } + tsExperimentCache.set(cacheKey, false); + return false; + } catch { + ctx.warn({ + message: 'Failed to find TypeScript. Please check if TypeScript has been installed.', + pluginCode: 'SWC_TYPESCRIPT_NOT_EXISTS' + }); + return false; + } +}; diff --git a/test/__snapshots__/index.ts.snap b/test/__snapshots__/index.ts.snap index 025658d..6e94cd4 100644 --- a/test/__snapshots__/index.ts.snap +++ b/test/__snapshots__/index.ts.snap @@ -1,5 +1,462 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`rollup-plugin-swc3 swc (rollup 2) detect decorator for typescript5 1`] = ` +"function _array_like_to_array(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; + return arr2; +} +function _array_with_holes(arr) { + if (Array.isArray(arr)) return arr; +} +function _class_call_check(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError(\\"Cannot call a class as a function\\"); + } +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +function _iterable_to_array_limit(arr, i) { + var _i = arr == null ? null : typeof Symbol !== \\"undefined\\" && arr[Symbol.iterator] || arr[\\"@@iterator\\"]; + if (_i == null) return; + var _arr = []; + var _n = true; + var _d = false; + var _s, _e; + try { + for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally{ + try { + if (!_n && _i[\\"return\\"] != null) _i[\\"return\\"](); + } finally{ + if (_d) throw _e; + } + } + return _arr; +} +function _non_iterable_rest() { + throw new TypeError(\\"Invalid attempt to destructure non-iterable instance.\\\\\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\"); +} +function _sliced_to_array(arr, i) { + return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); +} +function _unsupported_iterable_to_array(o, minLen) { + if (!o) return; + if (typeof o === \\"string\\") return _array_like_to_array(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === \\"Object\\" && o.constructor) n = o.constructor.name; + if (n === \\"Map\\" || n === \\"Set\\") return Array.from(n); + if (n === \\"Arguments\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); +} +function applyDecs2203RFactory() { + 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: + kindStr = \\"accessor\\"; + break; + case 2: + kindStr = \\"method\\"; + break; + case 3: + kindStr = \\"getter\\"; + break; + case 4: + kindStr = \\"setter\\"; + break; + default: + kindStr = \\"field\\"; + } + var ctx = { + kind: kindStr, + name: isPrivate ? \\"#\\" + name : name, + static: isStatic, + private: isPrivate, + metadata: metadata + }; + var decoratorFinishedRef = { + v: false + }; + ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef); + var get, set; + if (kind === 0) { + if (isPrivate) { + get = desc.get; + set = desc.set; + } else { + get = function() { + return this[name]; + }; + set = function(v) { + this[name] = v; + }; + } + } else if (kind === 2) { + get = function() { + return desc.value; + }; + } else { + if (kind === 1 || kind === 3) { + get = function() { + return desc.get.call(this); + }; + } + if (kind === 1 || kind === 4) { + 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) { + 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) { + hint = \\"field\\"; + } else if (kind === 10) { + 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 || kind === 1) { + desc = { + get: decInfo[3], + set: decInfo[4] + }; + } else if (kind === 3) { + desc = { + get: decInfo[3] + }; + } else if (kind === 4) { + desc = { + set: decInfo[3] + }; + } else { + desc = { + value: decInfo[3] + }; + } + } else if (kind !== 0) { + desc = Object.getOwnPropertyDescriptor(base, name); + } + if (kind === 1) { + value = { + get: desc.get, + set: desc.set + }; + } else if (kind === 2) { + value = desc.value; + } else if (kind === 3) { + value = desc.get; + } else if (kind === 4) { + 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) { + init = newValue; + } else if (kind === 1) { + 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) { + newInit = newValue; + } else if (kind === 1) { + 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 || kind === 1) { + 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); + } + if (kind !== 0) { + if (kind === 1) { + desc.get = value.get; + desc.set = value.set; + } else if (kind === 2) { + desc.value = value; + } else if (kind === 3) { + desc.get = value; + } else if (kind === 4) { + desc.set = value; + } + if (isPrivate) { + if (kind === 1) { + 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) { + ret.push(value); + } else { + ret.push(function(instance, args) { + return value.call(instance, args); + }); + } + } else { + Object.defineProperty(base, name, desc); + } + } + } + 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]; + if (!Array.isArray(decInfo)) continue; + var kind = decInfo[1]; + var name = decInfo[2]; + var isPrivate = decInfo.length > 3; + var isStatic = kind >= 5; + var base; + var initializers; + if (isStatic) { + base = Class; + kind = kind - 5; + staticInitializers = staticInitializers || []; + initializers = staticInitializers; + } else { + base = Class.prototype; + protoInitializers = protoInitializers || []; + initializers = protoInitializers; + } + if (kind !== 0 && !isPrivate) { + var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields; + var existingKind = existingNonFields.get(name) || 0; + if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) { + 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) { + 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(10, nextNewClass); + newClass = nextNewClass; + } + } + return [ + defineMetadata(newClass, metadata), + function() { + for(var i = 0; i < initializers.length; i++){ + initializers[i].call(newClass); + } + } + ]; + } + } + function defineMetadata(Class, metadata) { + return Object.defineProperty(Class, Symbol.metadata || Symbol.for(\\"Symbol.metadata\\"), { + configurable: true, + enumerable: true, + value: metadata + }); + } + return function applyDecs2203R(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, + get c () { + return applyClassDecs(targetClass, classDecs, metadata); + } + }; + }; +} +function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) { + return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass); +} +var // @ts-expect-error +_init_name; +var printMemberName = function(target, memberName) { + console.log(memberName); +}; +var Person = function Person() { + _class_call_check(this, Person); + _define_property(this, \\"name\\", _init_name(this, \\"Jon\\")); +}; +var ref, ref1; +ref = _apply_decs_2203_r(Person, [ + [ + printMemberName, + 0, + \\"name\\" + ] +], []), ref1 = _sliced_to_array(ref.e, 1), _init_name = ref1[0]; +" +`; + exports[`rollup-plugin-swc3 swc (rollup 2) directive - include "use client" 1`] = ` "'use client'; function client() { @@ -658,6 +1115,463 @@ export { foo }; " `; +exports[`rollup-plugin-swc3 swc (rollup 3) detect decorator for typescript5 1`] = ` +"function _array_like_to_array(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; + return arr2; +} +function _array_with_holes(arr) { + if (Array.isArray(arr)) return arr; +} +function _class_call_check(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError(\\"Cannot call a class as a function\\"); + } +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +function _iterable_to_array_limit(arr, i) { + var _i = arr == null ? null : typeof Symbol !== \\"undefined\\" && arr[Symbol.iterator] || arr[\\"@@iterator\\"]; + if (_i == null) return; + var _arr = []; + var _n = true; + var _d = false; + var _s, _e; + try { + for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally{ + try { + if (!_n && _i[\\"return\\"] != null) _i[\\"return\\"](); + } finally{ + if (_d) throw _e; + } + } + return _arr; +} +function _non_iterable_rest() { + throw new TypeError(\\"Invalid attempt to destructure non-iterable instance.\\\\\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\"); +} +function _sliced_to_array(arr, i) { + return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); +} +function _unsupported_iterable_to_array(o, minLen) { + if (!o) return; + if (typeof o === \\"string\\") return _array_like_to_array(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === \\"Object\\" && o.constructor) n = o.constructor.name; + if (n === \\"Map\\" || n === \\"Set\\") return Array.from(n); + if (n === \\"Arguments\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); +} +function applyDecs2203RFactory() { + 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: + kindStr = \\"accessor\\"; + break; + case 2: + kindStr = \\"method\\"; + break; + case 3: + kindStr = \\"getter\\"; + break; + case 4: + kindStr = \\"setter\\"; + break; + default: + kindStr = \\"field\\"; + } + var ctx = { + kind: kindStr, + name: isPrivate ? \\"#\\" + name : name, + static: isStatic, + private: isPrivate, + metadata: metadata + }; + var decoratorFinishedRef = { + v: false + }; + ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef); + var get, set; + if (kind === 0) { + if (isPrivate) { + get = desc.get; + set = desc.set; + } else { + get = function() { + return this[name]; + }; + set = function(v) { + this[name] = v; + }; + } + } else if (kind === 2) { + get = function() { + return desc.value; + }; + } else { + if (kind === 1 || kind === 3) { + get = function() { + return desc.get.call(this); + }; + } + if (kind === 1 || kind === 4) { + 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) { + 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) { + hint = \\"field\\"; + } else if (kind === 10) { + 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 || kind === 1) { + desc = { + get: decInfo[3], + set: decInfo[4] + }; + } else if (kind === 3) { + desc = { + get: decInfo[3] + }; + } else if (kind === 4) { + desc = { + set: decInfo[3] + }; + } else { + desc = { + value: decInfo[3] + }; + } + } else if (kind !== 0) { + desc = Object.getOwnPropertyDescriptor(base, name); + } + if (kind === 1) { + value = { + get: desc.get, + set: desc.set + }; + } else if (kind === 2) { + value = desc.value; + } else if (kind === 3) { + value = desc.get; + } else if (kind === 4) { + 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) { + init = newValue; + } else if (kind === 1) { + 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) { + newInit = newValue; + } else if (kind === 1) { + 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 || kind === 1) { + 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); + } + if (kind !== 0) { + if (kind === 1) { + desc.get = value.get; + desc.set = value.set; + } else if (kind === 2) { + desc.value = value; + } else if (kind === 3) { + desc.get = value; + } else if (kind === 4) { + desc.set = value; + } + if (isPrivate) { + if (kind === 1) { + 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) { + ret.push(value); + } else { + ret.push(function(instance, args) { + return value.call(instance, args); + }); + } + } else { + Object.defineProperty(base, name, desc); + } + } + } + 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]; + if (!Array.isArray(decInfo)) continue; + var kind = decInfo[1]; + var name = decInfo[2]; + var isPrivate = decInfo.length > 3; + var isStatic = kind >= 5; + var base; + var initializers; + if (isStatic) { + base = Class; + kind = kind - 5; + staticInitializers = staticInitializers || []; + initializers = staticInitializers; + } else { + base = Class.prototype; + protoInitializers = protoInitializers || []; + initializers = protoInitializers; + } + if (kind !== 0 && !isPrivate) { + var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields; + var existingKind = existingNonFields.get(name) || 0; + if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) { + 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) { + 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(10, nextNewClass); + newClass = nextNewClass; + } + } + return [ + defineMetadata(newClass, metadata), + function() { + for(var i = 0; i < initializers.length; i++){ + initializers[i].call(newClass); + } + } + ]; + } + } + function defineMetadata(Class, metadata) { + return Object.defineProperty(Class, Symbol.metadata || Symbol.for(\\"Symbol.metadata\\"), { + configurable: true, + enumerable: true, + value: metadata + }); + } + return function applyDecs2203R(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, + get c () { + return applyClassDecs(targetClass, classDecs, metadata); + } + }; + }; +} +function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) { + return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass); +} +var // @ts-expect-error +_init_name; +var printMemberName = function(target, memberName) { + console.log(memberName); +}; +var Person = function Person() { + _class_call_check(this, Person); + _define_property(this, \\"name\\", _init_name(this, \\"Jon\\")); +}; +var ref, ref1; +ref = _apply_decs_2203_r(Person, [ + [ + printMemberName, + 0, + \\"name\\" + ] +], []), ref1 = _sliced_to_array(ref.e, 1), _init_name = ref1[0]; +" +`; + exports[`rollup-plugin-swc3 swc (rollup 3) directive - include "use client" 1`] = ` "'use client'; function client() { @@ -1314,6 +2228,463 @@ export { foo }; " `; +exports[`rollup-plugin-swc3 swc (rollup 4) detect decorator for typescript5 1`] = ` +"function _array_like_to_array(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; + return arr2; +} +function _array_with_holes(arr) { + if (Array.isArray(arr)) return arr; +} +function _class_call_check(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError(\\"Cannot call a class as a function\\"); + } +} +function _define_property(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; +} +function _iterable_to_array_limit(arr, i) { + var _i = arr == null ? null : typeof Symbol !== \\"undefined\\" && arr[Symbol.iterator] || arr[\\"@@iterator\\"]; + if (_i == null) return; + var _arr = []; + var _n = true; + var _d = false; + var _s, _e; + try { + for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ + _arr.push(_s.value); + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally{ + try { + if (!_n && _i[\\"return\\"] != null) _i[\\"return\\"](); + } finally{ + if (_d) throw _e; + } + } + return _arr; +} +function _non_iterable_rest() { + throw new TypeError(\\"Invalid attempt to destructure non-iterable instance.\\\\\\\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\\"); +} +function _sliced_to_array(arr, i) { + return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); +} +function _unsupported_iterable_to_array(o, minLen) { + if (!o) return; + if (typeof o === \\"string\\") return _array_like_to_array(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === \\"Object\\" && o.constructor) n = o.constructor.name; + if (n === \\"Map\\" || n === \\"Set\\") return Array.from(n); + if (n === \\"Arguments\\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); +} +function applyDecs2203RFactory() { + 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: + kindStr = \\"accessor\\"; + break; + case 2: + kindStr = \\"method\\"; + break; + case 3: + kindStr = \\"getter\\"; + break; + case 4: + kindStr = \\"setter\\"; + break; + default: + kindStr = \\"field\\"; + } + var ctx = { + kind: kindStr, + name: isPrivate ? \\"#\\" + name : name, + static: isStatic, + private: isPrivate, + metadata: metadata + }; + var decoratorFinishedRef = { + v: false + }; + ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef); + var get, set; + if (kind === 0) { + if (isPrivate) { + get = desc.get; + set = desc.set; + } else { + get = function() { + return this[name]; + }; + set = function(v) { + this[name] = v; + }; + } + } else if (kind === 2) { + get = function() { + return desc.value; + }; + } else { + if (kind === 1 || kind === 3) { + get = function() { + return desc.get.call(this); + }; + } + if (kind === 1 || kind === 4) { + 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) { + 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) { + hint = \\"field\\"; + } else if (kind === 10) { + 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 || kind === 1) { + desc = { + get: decInfo[3], + set: decInfo[4] + }; + } else if (kind === 3) { + desc = { + get: decInfo[3] + }; + } else if (kind === 4) { + desc = { + set: decInfo[3] + }; + } else { + desc = { + value: decInfo[3] + }; + } + } else if (kind !== 0) { + desc = Object.getOwnPropertyDescriptor(base, name); + } + if (kind === 1) { + value = { + get: desc.get, + set: desc.set + }; + } else if (kind === 2) { + value = desc.value; + } else if (kind === 3) { + value = desc.get; + } else if (kind === 4) { + 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) { + init = newValue; + } else if (kind === 1) { + 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) { + newInit = newValue; + } else if (kind === 1) { + 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 || kind === 1) { + 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); + } + if (kind !== 0) { + if (kind === 1) { + desc.get = value.get; + desc.set = value.set; + } else if (kind === 2) { + desc.value = value; + } else if (kind === 3) { + desc.get = value; + } else if (kind === 4) { + desc.set = value; + } + if (isPrivate) { + if (kind === 1) { + 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) { + ret.push(value); + } else { + ret.push(function(instance, args) { + return value.call(instance, args); + }); + } + } else { + Object.defineProperty(base, name, desc); + } + } + } + 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]; + if (!Array.isArray(decInfo)) continue; + var kind = decInfo[1]; + var name = decInfo[2]; + var isPrivate = decInfo.length > 3; + var isStatic = kind >= 5; + var base; + var initializers; + if (isStatic) { + base = Class; + kind = kind - 5; + staticInitializers = staticInitializers || []; + initializers = staticInitializers; + } else { + base = Class.prototype; + protoInitializers = protoInitializers || []; + initializers = protoInitializers; + } + if (kind !== 0 && !isPrivate) { + var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields; + var existingKind = existingNonFields.get(name) || 0; + if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) { + 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) { + 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(10, nextNewClass); + newClass = nextNewClass; + } + } + return [ + defineMetadata(newClass, metadata), + function() { + for(var i = 0; i < initializers.length; i++){ + initializers[i].call(newClass); + } + } + ]; + } + } + function defineMetadata(Class, metadata) { + return Object.defineProperty(Class, Symbol.metadata || Symbol.for(\\"Symbol.metadata\\"), { + configurable: true, + enumerable: true, + value: metadata + }); + } + return function applyDecs2203R(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, + get c () { + return applyClassDecs(targetClass, classDecs, metadata); + } + }; + }; +} +function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) { + return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass); +} +var // @ts-expect-error +_init_name; +var printMemberName = function(target, memberName) { + console.log(memberName); +}; +var Person = function Person() { + _class_call_check(this, Person); + _define_property(this, \\"name\\", _init_name(this, \\"Jon\\")); +}; +var ref, ref1; +ref = _apply_decs_2203_r(Person, [ + [ + printMemberName, + 0, + \\"name\\" + ] +], []), ref1 = _sliced_to_array(ref.e, 1), _init_name = ref1[0]; +" +`; + exports[`rollup-plugin-swc3 swc (rollup 4) directive - include "use client" 1`] = ` "'use client'; function client() { diff --git a/test/fixtures/decorators/index.ts b/test/fixtures/decorators/index.ts new file mode 100644 index 0000000..3754a4c --- /dev/null +++ b/test/fixtures/decorators/index.ts @@ -0,0 +1,9 @@ +const printMemberName = (target: any, memberName: string) => { + console.log(memberName); + }; + +class Person { + // @ts-expect-error + @printMemberName + name: string = "Jon"; +} \ No newline at end of file diff --git a/test/index.ts b/test/index.ts index c235c95..36c5907 100644 --- a/test/index.ts +++ b/test/index.ts @@ -375,6 +375,15 @@ const tests = (rollupImpl: typeof rollup2 | typeof rollup3 | typeof rollup4, iso { input: './src/index.ts', dir, otherRollupPlugins: [nodeResolve(), commonjs()] } ))[0].code.should.matchSnapshot(); }); + + it('detect decorator for typescript5', async () => { + const dir = await fixture('decorators'); + (await build( + rollupImpl, + { tsconfig: false }, + { input: './index.ts', dir } + ))[0].code.should.matchSnapshot(); + }); }; describe('rollup-plugin-swc3', () => { From 9ae8f25889bb6b9f724e4219130b361953406c09 Mon Sep 17 00:00:00 2001 From: kanno <812137533@qq.com> Date: Sun, 6 Oct 2024 10:34:02 +0800 Subject: [PATCH 2/2] chore: hoist logic & remove useless code --- src/index.ts | 4 ++-- src/options.ts | 19 ++----------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 0323d03..683b945 100644 --- a/src/index.ts +++ b/src/index.ts @@ -68,6 +68,8 @@ function swc(options: PluginOptions = {}): RollupPlugin { return null; }; + const enableExperimentalDecorators = getEnableExperimentalDecorators(); + return { name: 'swc', @@ -115,8 +117,6 @@ function swc(options: PluginOptions = {}): RollupPlugin { ? {} : getOptions(this, dirname(id), options.tsconfig); - const enableExperimentalDecorators = getEnableExperimentalDecorators(this, isTypeScript, dirname(id), options.tsconfig); - // TODO: SWC is about to add "preserve" jsx // https://github.com/swc-project/swc/pull/5661 // Respect "preserve" after swc adds the support diff --git a/src/options.ts b/src/options.ts index 9abdb84..28fbfb6 100644 --- a/src/options.ts +++ b/src/options.ts @@ -9,8 +9,6 @@ import { fileURLToPath } from 'node:url'; const cache = new Map(); -const tsExperimentCache = new Map(); - export const getOptions = ( ctx: TransformPluginContext, cwd: string, @@ -61,31 +59,18 @@ export const getOptions = ( return compilerOptions; }; -export const getEnableExperimentalDecorators = ( - ctx: TransformPluginContext, isTypeScript: boolean, cwd: string, key?: string | boolean -) => { - if (!isTypeScript) return false; - const cacheKey = `${cwd}:${key ?? 'undefined'}`; - if (tsExperimentCache.has(cacheKey)) { - return tsExperimentCache.get(cacheKey) ?? false; - } +export const getEnableExperimentalDecorators = () => { try { // @ts-expect-error -- It's required to using 'import.mtea.url' but i don't want to change the tsconfig. const tsPath = resolve('typescript/package.json', import.meta.url); const { version } = JSON.parse(fs.readFileSync(fileURLToPath(tsPath), 'utf-8')); const [major] = version.split('.'); - // Only enable experimental decorators for TypeScript 5+ + // Only check experimental decorators for TypeScript 5+ if (+major >= 5) { - tsExperimentCache.set(cacheKey, true); return true; } - tsExperimentCache.set(cacheKey, false); return false; } catch { - ctx.warn({ - message: 'Failed to find TypeScript. Please check if TypeScript has been installed.', - pluginCode: 'SWC_TYPESCRIPT_NOT_EXISTS' - }); return false; } };