From d56e2ee5f5fa8d60ff500f35f3b7f9bc227ebce6 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 27 Oct 2024 00:27:41 +0400 Subject: [PATCH 1/8] rename imports --- packages/build/__tests__/{second.test.ts => imports.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/build/__tests__/{second.test.ts => imports.test.ts} (100%) diff --git a/packages/build/__tests__/second.test.ts b/packages/build/__tests__/imports.test.ts similarity index 100% rename from packages/build/__tests__/second.test.ts rename to packages/build/__tests__/imports.test.ts From dfdfea78d647f492f814cc2b9774d342864af684 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 27 Oct 2024 00:28:10 +0400 Subject: [PATCH 2/8] allow plugins to take main options for better context, if needed --- packages/build/src/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index c806f9a..dcded68 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -1,8 +1,8 @@ import esbuild from 'esbuild'; export interface HyperwebBuildOptions extends esbuild.BuildOptions { - // Add any Hyperweb-specific options here - customPlugins?: esbuild.Plugin[]; + // Allow plugins to be either direct Plugin objects or functions that receive options + customPlugins?: (esbuild.Plugin | ((options: HyperwebBuildOptions) => esbuild.Plugin))[]; } export const defaultOptions: HyperwebBuildOptions = { @@ -24,11 +24,17 @@ export const HyperwebBuild = { async build(options: Partial = {}): Promise { const mergedOptions: HyperwebBuildOptions = { ...defaultOptions, ...options }; - // Apply custom plugins if any + // Apply custom plugins if any, passing the merged options if (mergedOptions.customPlugins) { mergedOptions.plugins = [ ...(mergedOptions.plugins || []), - ...mergedOptions.customPlugins + ...mergedOptions.customPlugins.map(plugin => { + // If plugin is a function that accepts options, call it with options + if (typeof plugin === 'function') { + return plugin(mergedOptions); + } + return plugin; + }) ]; delete mergedOptions.customPlugins; } From 3305ae9b149eda5f630dfa6db3efd3d27537221a Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 27 Oct 2024 00:29:37 +0400 Subject: [PATCH 3/8] add babel plugin for decorator detection --- __fixtures__/decorators/src/index.ts | 7 + __output__/decorators/bundle.js | 6 + __output__/decorators/bundle.js.map | 7 + __output__/decorators/decorators.json | 18 +++ packages/build/__tests__/decorators.test.ts | 41 ++++++ packages/build/package.json | 4 + packages/build/src/decorators.ts | 150 ++++++++++++++++++++ yarn.lock | 86 ++++++++++- 8 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 __fixtures__/decorators/src/index.ts create mode 100644 __output__/decorators/bundle.js create mode 100644 __output__/decorators/bundle.js.map create mode 100644 __output__/decorators/decorators.json create mode 100644 packages/build/__tests__/decorators.test.ts create mode 100644 packages/build/src/decorators.ts diff --git a/__fixtures__/decorators/src/index.ts b/__fixtures__/decorators/src/index.ts new file mode 100644 index 0000000..765a501 --- /dev/null +++ b/__fixtures__/decorators/src/index.ts @@ -0,0 +1,7 @@ +class MyClass { + @log('debug') + @performance + async fetchData() { + // ... method implementation + } +} \ No newline at end of file diff --git a/__output__/decorators/bundle.js b/__output__/decorators/bundle.js new file mode 100644 index 0000000..0960ae3 --- /dev/null +++ b/__output__/decorators/bundle.js @@ -0,0 +1,6 @@ +// ../../__fixtures__/decorators/src/index.ts +var MyClass = class { + async fetchData() { + } +}; +//# sourceMappingURL=bundle.js.map diff --git a/__output__/decorators/bundle.js.map b/__output__/decorators/bundle.js.map new file mode 100644 index 0000000..d82bc70 --- /dev/null +++ b/__output__/decorators/bundle.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../__fixtures__/decorators/src/index.ts"], + "sourcesContent": ["class MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], + "mappings": ";AAAA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", + "names": [] +} diff --git a/__output__/decorators/decorators.json b/__output__/decorators/decorators.json new file mode 100644 index 0000000..5f9fcfe --- /dev/null +++ b/__output__/decorators/decorators.json @@ -0,0 +1,18 @@ +{ + "../../__fixtures__/decorators/src/index.ts": [ + { + "name": "log", + "args": [ + "debug" + ], + "targetName": "fetchData", + "targetType": "method" + }, + { + "name": "performance", + "args": [], + "targetName": "fetchData", + "targetType": "method" + } + ] +} \ No newline at end of file diff --git a/packages/build/__tests__/decorators.test.ts b/packages/build/__tests__/decorators.test.ts new file mode 100644 index 0000000..05de813 --- /dev/null +++ b/packages/build/__tests__/decorators.test.ts @@ -0,0 +1,41 @@ +import fs from 'fs/promises'; +import { join, resolve } from 'path'; + +import { createDecoratorExtractorPlugin } from '../src/decorators'; +import { HyperwebBuild, HyperwebBuildOptions } from '../src/index'; + +const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'decorators')); +const outputDir = resolve(join(__dirname, '/../../../__output__/', 'decorators')); + +describe('HyperwebBuild', () => { + it('builds the fixture project successfully', async () => { + const outfile = join(outputDir, 'bundle.js'); + + const options: Partial = { + entryPoints: [join(fixtureDir, 'src/index.ts')], + outfile, + external: ['otherpackage', '~somepackage'], + customPlugins: [ + createDecoratorExtractorPlugin({ + outputPath: join(outputDir, 'decorators.json'), + include: [/\.ts$/], // Only process TypeScript files + exclude: [/node_modules/, /\.test\.ts$/] // Skip node_modules and test files + }) + ] + }; + + await HyperwebBuild.build(options); + + // Check if the output file exists + const outfileExists = await fs.access(outfile) + .then(() => true) + .catch(() => false); + + expect(outfileExists).toBe(true); + + // Optionally, you can read the contents of the file and perform additional checks + // const bundleContent = await fs.readFile(outfile, 'utf-8'); + // expect(bundleContent).toContain('function greet'); + // expect(bundleContent).toContain('function farewell'); + }); +}); \ No newline at end of file diff --git a/packages/build/package.json b/packages/build/package.json index 8a26362..1ecf0ef 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -31,6 +31,10 @@ }, "keywords": [], "dependencies": { + "@babel/generator": "7.22.9", + "@babel/parser": "^7.22.7", + "@babel/traverse": "7.22.8", + "@babel/types": "7.22.5", "esbuild": "^0.23.1" } } diff --git a/packages/build/src/decorators.ts b/packages/build/src/decorators.ts new file mode 100644 index 0000000..af518b7 --- /dev/null +++ b/packages/build/src/decorators.ts @@ -0,0 +1,150 @@ +import generate from '@babel/generator'; +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; +import * as t from '@babel/types'; +import { Plugin } from 'esbuild'; +import * as path from 'path'; + +import { HyperwebBuildOptions } from './index'; + +interface DecoratorInfo { + name: string; + args: any[]; + targetName: string; + targetType: 'method' | 'function'; + location?: { + file: string; + line: number; + column: number; + }; +} + +interface DecoratorExtractorOptions { + outputPath?: string; // Where to save the decorator metadata + include?: RegExp[]; // Patterns for files to process + exclude?: RegExp[]; // Patterns for files to ignore +} + +const decoratorMetadata: Record = {}; + + +function normalizeFilePath(filePath: string, rootDir?: string): string { + const projectRoot = rootDir || process.cwd(); + const relativePath = path.relative(projectRoot, filePath); + return relativePath + .replace(/\\/g, '/') + .replace(/^\.\//, ''); + } + +export const createDecoratorExtractorPlugin = ( + pluginOptions: DecoratorExtractorOptions = {}, + hyperwebOptions?: HyperwebBuildOptions +): Plugin => ({ + name: 'decorator-extractor', + + setup(build) { + + // Set up the file filter + const filter = { + include: pluginOptions.include || [/\.[jt]sx?$/], + exclude: pluginOptions.exclude || [/node_modules/], + }; + + build.onLoad({ filter: new RegExp(filter.include.map(r => r.source).join('|')) }, async (args) => { + // Skip excluded files + if (filter.exclude.some(pattern => pattern.test(args.path))) { + return null; + } + + // Read the file + const source = await require('fs').promises.readFile(args.path, 'utf8'); + + // Parse the code with babel + const ast = parser.parse(source, { + sourceType: 'module', + plugins: ['typescript', 'decorators-legacy'], + }); + + // Track if we made any modifications + let modified = false; + + // Traverse the AST + traverse(ast, { + Decorator(path) { + const decorator = path.node; + const parent = path.parent; + + // Only process method or function decorators + if (!t.isClassMethod(parent as any) && !t.isFunctionDeclaration(parent as any)) { + return; + } + + const rootDir = hyperwebOptions?.absWorkingDir || process.cwd(); + const normalizedPath = normalizeFilePath(args.path, rootDir); + + // Get decorator information + const decoratorInfo: DecoratorInfo = { + // @ts-ignore + name: t.isIdentifier(decorator.expression) + ? decorator.expression.name + // @ts-ignore + : t.isCallExpression(decorator.expression) + ? (decorator.expression.callee as t.Identifier).name + : 'unknown', + // @ts-ignore + args: t.isCallExpression(decorator.expression) + ? decorator.expression.arguments.map(arg => + // @ts-ignore + t.isLiteral(arg) ? (arg as any).value : null) + : [], + // @ts-ignore + targetName: t.isClassMethod(parent) + ? (parent.key as t.Identifier).name + : (parent as t.FunctionDeclaration).id?.name || 'anonymous', + // @ts-ignore + targetType: t.isClassMethod(parent) ? 'method' : 'function', + + // location: { + // file: normalizedPath, + // line: decorator.loc?.start.line || 0, + // column: decorator.loc?.start.column || 0, + // } + }; + + // Store the metadata + if (!decoratorMetadata[normalizedPath]) { + decoratorMetadata[normalizedPath] = []; + } + decoratorMetadata[normalizedPath].push(decoratorInfo); + + // Remove the decorator + path.remove(); + modified = true; + } + }); + + // If we made modifications, generate new code + if (modified) { + const output = generate(ast, {}, source); + + return { + contents: output.code, + loader: args.path.endsWith('.ts') ? 'ts' : 'js', + }; + } + + return null; + }); + + build.onEnd(async () => { + if (pluginOptions.outputPath) { + // Save the metadata to the specified file + await require('fs').promises.writeFile( + pluginOptions.outputPath, + JSON.stringify(decoratorMetadata, null, 2), + 'utf8' + ); + } + }); + } +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 514498c..9d766c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,15 @@ "@babel/highlight" "^7.25.7" picocolors "^1.0.0" +"@babel/code-frame@^7.22.5": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.0.tgz#9374b5cd068d128dac0b94ff482594273b1c2815" + integrity sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g== + dependencies: + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.7.tgz#b8479fe0018ef0ac87b6b7a5c6916fcd67ae2c9c" @@ -74,6 +83,16 @@ "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" +"@babel/generator@7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== + dependencies: + "@babel/types" "^7.22.5" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + "@babel/generator@^7.18.10", "@babel/generator@^7.23.6", "@babel/generator@^7.25.7", "@babel/generator@^7.7.2": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" @@ -84,6 +103,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.22.7": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.0.tgz#505cc7c90d92513f458a477e5ef0703e7c91b8d7" + integrity sha512-/AIkAmInnWwgEAJGQr9vY0c66Mj6kjkE2ZPB1PurTRaRAh3U+J45sAQMjQDJdh4WbR3l0x5xkimXBKyBXXAu2w== + dependencies: + "@babel/parser" "^7.26.0" + "@babel/types" "^7.26.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972" @@ -144,14 +174,14 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20": +"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== dependencies: "@babel/types" "^7.24.7" -"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.23.0": +"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.22.5", "@babel/helper-function-name@^7.23.0": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== @@ -250,11 +280,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== +"@babel/helper-string-parser@^7.22.5", "@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== +"@babel/helper-validator-identifier@^7.22.5", "@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + "@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.25.7": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" @@ -299,6 +339,13 @@ dependencies: "@babel/types" "^7.25.7" +"@babel/parser@^7.22.7", "@babel/parser@^7.26.0": + version "7.26.1" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.1.tgz#44e02499960df2cdce2c456372a3e8e0c3c5c975" + integrity sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw== + dependencies: + "@babel/types" "^7.26.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.25.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.7.tgz#c5f755e911dfac7ef6957300c0f9c4a8c18c06f4" @@ -1026,6 +1073,22 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@7.22.8": + version "7.22.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/generator" "^7.22.7" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.7" + "@babel/types" "^7.22.5" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.6.tgz#b53526a2367a0dd6edc423637f3d2d0f2521abc5" @@ -1064,6 +1127,15 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" +"@babel/types@7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + "@babel/types@7.23.6": version "7.23.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.6.tgz#be33fdb151e1f5a56877d704492c240fc71c7ccd" @@ -1082,6 +1154,14 @@ "@babel/helper-validator-identifier" "^7.25.7" to-fast-properties "^2.0.0" +"@babel/types@^7.22.5", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1861,7 +1941,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== From ba2c7dad2388bae201a05f95bef6469cc84404ff Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 27 Oct 2024 02:56:02 +0400 Subject: [PATCH 4/8] class and object --- __fixtures__/decorators/src/class.ts | 37 +++++++++++++++++++ __fixtures__/decorators/src/index.ts | 2 ++ __fixtures__/decorators/src/object.ts | 30 ++++++++++++++++ __output__/decorators/bundle.js | 52 +++++++++++++++++++++++++++ __output__/decorators/bundle.js.map | 6 ++-- __output__/decorators/decorators.json | 14 ++++++++ 6 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 __fixtures__/decorators/src/class.ts create mode 100644 __fixtures__/decorators/src/object.ts diff --git a/__fixtures__/decorators/src/class.ts b/__fixtures__/decorators/src/class.ts new file mode 100644 index 0000000..3158483 --- /dev/null +++ b/__fixtures__/decorators/src/class.ts @@ -0,0 +1,37 @@ +import { BigNumber } from "jsd-std"; + +// Permission decorators +export const admin = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor; +export const creator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor; + +export interface State { + count: BigNumber; +} + +export class Counter { + private state: State; + + constructor(initialState: State) { + this.state.count = initialState; + } + + // Public by default (no decorator needed) + public getCount(): BigNumber { + return this.state.count; + } + + // Only admin and creator can increment + @admin + public increment(amount: BigNumber): void { + this.state.count = this.state.count.add(amount); + } + + // Only creator can decrement + @creator + public decrement(amount: BigNumber): void { + if (this.state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + this.state.count = this.state.count.sub(amount); + } +} \ No newline at end of file diff --git a/__fixtures__/decorators/src/index.ts b/__fixtures__/decorators/src/index.ts index 765a501..6a15ab2 100644 --- a/__fixtures__/decorators/src/index.ts +++ b/__fixtures__/decorators/src/index.ts @@ -1,3 +1,5 @@ +export * from './class'; +export * from './object'; class MyClass { @log('debug') @performance diff --git a/__fixtures__/decorators/src/object.ts b/__fixtures__/decorators/src/object.ts new file mode 100644 index 0000000..3a6a830 --- /dev/null +++ b/__fixtures__/decorators/src/object.ts @@ -0,0 +1,30 @@ +import { BigNumber } from "jsd-std"; + +interface State { + count: BigNumber; +} + +// Core contract logic +export const start = (initialCount: BigNumber) => { + let state: State = { + count: initialCount + }; + + // HOW TO EVEN DO DECORATORS + return { + getCount: () => state.count, + + increment: (amount: BigNumber) => { + state.count = state.count.add(amount); + return state.count; + }, + + decrement: (amount: BigNumber) => { + if (state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + state.count = state.count.sub(amount); + return state.count; + } + }; +}; \ No newline at end of file diff --git a/__output__/decorators/bundle.js b/__output__/decorators/bundle.js index 0960ae3..de53c3a 100644 --- a/__output__/decorators/bundle.js +++ b/__output__/decorators/bundle.js @@ -1,6 +1,58 @@ +// ../../__fixtures__/decorators/src/class.ts +var admin = (target, propertyKey, descriptor) => descriptor; +var creator = (target, propertyKey, descriptor) => descriptor; +var Counter = class { + state; + constructor(initialState) { + this.state.count = initialState; + } + // Public by default (no decorator needed) + getCount() { + return this.state.count; + } + // Only admin and creator can increment + increment(amount) { + this.state.count = this.state.count.add(amount); + } + // Only creator can decrement + decrement(amount) { + if (this.state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + this.state.count = this.state.count.sub(amount); + } +}; + +// ../../__fixtures__/decorators/src/object.ts +var start = (initialCount) => { + let state = { + count: initialCount + }; + return { + getCount: () => state.count, + increment: (amount) => { + state.count = state.count.add(amount); + return state.count; + }, + decrement: (amount) => { + if (state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + state.count = state.count.sub(amount); + return state.count; + } + }; +}; + // ../../__fixtures__/decorators/src/index.ts var MyClass = class { async fetchData() { } }; +export { + Counter, + admin, + creator, + start +}; //# sourceMappingURL=bundle.js.map diff --git a/__output__/decorators/bundle.js.map b/__output__/decorators/bundle.js.map index d82bc70..57389c9 100644 --- a/__output__/decorators/bundle.js.map +++ b/__output__/decorators/bundle.js.map @@ -1,7 +1,7 @@ { "version": 3, - "sources": ["../../__fixtures__/decorators/src/index.ts"], - "sourcesContent": ["class MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], - "mappings": ";AAAA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", + "sources": ["../../__fixtures__/decorators/src/class.ts", "../../__fixtures__/decorators/src/object.ts", "../../__fixtures__/decorators/src/index.ts"], + "sourcesContent": ["import { BigNumber } from \"jsd-std\";\n\n// Permission decorators\nexport const admin = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor;\nexport const creator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor;\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], + "mappings": ";AAGO,IAAM,QAAQ,CAAC,QAAa,aAAqB,eAAmC;AACpF,IAAM,UAAU,CAAC,QAAa,aAAqB,eAAmC;AAItF,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACxBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", "names": [] } diff --git a/__output__/decorators/decorators.json b/__output__/decorators/decorators.json index 5f9fcfe..84081df 100644 --- a/__output__/decorators/decorators.json +++ b/__output__/decorators/decorators.json @@ -14,5 +14,19 @@ "targetName": "fetchData", "targetType": "method" } + ], + "../../__fixtures__/decorators/src/class.ts": [ + { + "name": "admin", + "args": [], + "targetName": "increment", + "targetType": "method" + }, + { + "name": "creator", + "args": [], + "targetName": "decrement", + "targetType": "method" + } ] } \ No newline at end of file From 72c38d21d2be29d2f33ead069998a0a633f36df3 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Sun, 27 Oct 2024 03:32:46 +0400 Subject: [PATCH 5/8] import decorators --- __fixtures__/decorators/src/class.ts | 9 +++------ __output__/decorators/bundle.js | 4 ---- __output__/decorators/bundle.js.map | 4 ++-- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/__fixtures__/decorators/src/class.ts b/__fixtures__/decorators/src/class.ts index 3158483..3bf9b8f 100644 --- a/__fixtures__/decorators/src/class.ts +++ b/__fixtures__/decorators/src/class.ts @@ -1,8 +1,5 @@ -import { BigNumber } from "jsd-std"; - -// Permission decorators -export const admin = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor; -export const creator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor; +import { admin, creator } from '@hyperweb/decorators'; +import { BigNumber } from 'jsd-std'; export interface State { count: BigNumber; @@ -30,7 +27,7 @@ export class Counter { @creator public decrement(amount: BigNumber): void { if (this.state.count.lt(amount)) { - throw new Error("Count cannot be negative"); + throw new Error('Count cannot be negative'); } this.state.count = this.state.count.sub(amount); } diff --git a/__output__/decorators/bundle.js b/__output__/decorators/bundle.js index de53c3a..82740a0 100644 --- a/__output__/decorators/bundle.js +++ b/__output__/decorators/bundle.js @@ -1,6 +1,4 @@ // ../../__fixtures__/decorators/src/class.ts -var admin = (target, propertyKey, descriptor) => descriptor; -var creator = (target, propertyKey, descriptor) => descriptor; var Counter = class { state; constructor(initialState) { @@ -51,8 +49,6 @@ var MyClass = class { }; export { Counter, - admin, - creator, start }; //# sourceMappingURL=bundle.js.map diff --git a/__output__/decorators/bundle.js.map b/__output__/decorators/bundle.js.map index 57389c9..5558753 100644 --- a/__output__/decorators/bundle.js.map +++ b/__output__/decorators/bundle.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../__fixtures__/decorators/src/class.ts", "../../__fixtures__/decorators/src/object.ts", "../../__fixtures__/decorators/src/index.ts"], - "sourcesContent": ["import { BigNumber } from \"jsd-std\";\n\n// Permission decorators\nexport const admin = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor;\nexport const creator = (target: any, propertyKey: string, descriptor: PropertyDescriptor) => descriptor;\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], - "mappings": ";AAGO,IAAM,QAAQ,CAAC,QAAa,aAAqB,eAAmC;AACpF,IAAM,UAAU,CAAC,QAAa,aAAqB,eAAmC;AAItF,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACxBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", + "sourcesContent": ["import { admin, creator } from '@hyperweb/decorators';\nimport { BigNumber } from 'jsd-std';\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error('Count cannot be negative');\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], + "mappings": ";AAKO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACrBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", "names": [] } From fc4fe2fc3d12f188b5ffdaf95371ca3a7022a885 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Wed, 30 Oct 2024 10:57:19 -0700 Subject: [PATCH 6/8] updates --- __fixtures__/decorators/src/class.ts | 7 ++++-- __fixtures__/decorators/src/index.ts | 7 ++++-- __output__/decorators/bundle.js | 3 +++ __output__/decorators/bundle.js.map | 4 ++-- __output__/decorators/decorators.json | 32 ++++++++++++++++++--------- packages/build/src/decorators.ts | 4 ++-- 6 files changed, 39 insertions(+), 18 deletions(-) diff --git a/__fixtures__/decorators/src/class.ts b/__fixtures__/decorators/src/class.ts index 3bf9b8f..483cc07 100644 --- a/__fixtures__/decorators/src/class.ts +++ b/__fixtures__/decorators/src/class.ts @@ -1,4 +1,4 @@ -import { admin, creator } from '@hyperweb/decorators'; +import { admin, creator, external, internal } from '@hyperweb/decorators'; import { BigNumber } from 'jsd-std'; export interface State { @@ -13,11 +13,14 @@ export class Counter { } // Public by default (no decorator needed) + @external + @admin public getCount(): BigNumber { return this.state.count; } - + // Only admin and creator can increment + @internal @admin public increment(amount: BigNumber): void { this.state.count = this.state.count.add(amount); diff --git a/__fixtures__/decorators/src/index.ts b/__fixtures__/decorators/src/index.ts index 6a15ab2..8a3bce7 100644 --- a/__fixtures__/decorators/src/index.ts +++ b/__fixtures__/decorators/src/index.ts @@ -1,9 +1,12 @@ export * from './class'; export * from './object'; class MyClass { - @log('debug') + @permission('debug', 'level') @performance async fetchData() { // ... method implementation } -} \ No newline at end of file +} + +export default MyClass; +export {MyClass}; \ No newline at end of file diff --git a/__output__/decorators/bundle.js b/__output__/decorators/bundle.js index 82740a0..60d0183 100644 --- a/__output__/decorators/bundle.js +++ b/__output__/decorators/bundle.js @@ -47,8 +47,11 @@ var MyClass = class { async fetchData() { } }; +var src_default = MyClass; export { Counter, + MyClass, + src_default as default, start }; //# sourceMappingURL=bundle.js.map diff --git a/__output__/decorators/bundle.js.map b/__output__/decorators/bundle.js.map index 5558753..5d35b25 100644 --- a/__output__/decorators/bundle.js.map +++ b/__output__/decorators/bundle.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../__fixtures__/decorators/src/class.ts", "../../__fixtures__/decorators/src/object.ts", "../../__fixtures__/decorators/src/index.ts"], - "sourcesContent": ["import { admin, creator } from '@hyperweb/decorators';\nimport { BigNumber } from 'jsd-std';\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error('Count cannot be negative');\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}"], - "mappings": ";AAKO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACrBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;", + "sourcesContent": ["import { admin, creator, external, internal } from '@hyperweb/decorators';\nimport { BigNumber } from 'jsd-std';\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error('Count cannot be negative');\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}\nexport default MyClass;\nexport { MyClass };"], + "mappings": ";AAKO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACrBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;AACA,IAAO,cAAQ;", "names": [] } diff --git a/__output__/decorators/decorators.json b/__output__/decorators/decorators.json index 84081df..428186b 100644 --- a/__output__/decorators/decorators.json +++ b/__output__/decorators/decorators.json @@ -1,32 +1,44 @@ { "../../__fixtures__/decorators/src/index.ts": [ { - "name": "log", + "name": "permission", "args": [ - "debug" + "debug", + "level" ], - "targetName": "fetchData", - "targetType": "method" + "targetName": "fetchData" }, { "name": "performance", "args": [], - "targetName": "fetchData", - "targetType": "method" + "targetName": "fetchData" } ], "../../__fixtures__/decorators/src/class.ts": [ + { + "name": "external", + "args": [], + "targetName": "getCount" + }, + { + "name": "admin", + "args": [], + "targetName": "getCount" + }, + { + "name": "internal", + "args": [], + "targetName": "increment" + }, { "name": "admin", "args": [], - "targetName": "increment", - "targetType": "method" + "targetName": "increment" }, { "name": "creator", "args": [], - "targetName": "decrement", - "targetType": "method" + "targetName": "decrement" } ] } \ No newline at end of file diff --git a/packages/build/src/decorators.ts b/packages/build/src/decorators.ts index af518b7..a515e94 100644 --- a/packages/build/src/decorators.ts +++ b/packages/build/src/decorators.ts @@ -11,7 +11,7 @@ interface DecoratorInfo { name: string; args: any[]; targetName: string; - targetType: 'method' | 'function'; + targetType?: 'method' | 'function'; location?: { file: string; line: number; @@ -102,7 +102,7 @@ export const createDecoratorExtractorPlugin = ( ? (parent.key as t.Identifier).name : (parent as t.FunctionDeclaration).id?.name || 'anonymous', // @ts-ignore - targetType: t.isClassMethod(parent) ? 'method' : 'function', + // targetType: t.isClassMethod(parent) ? 'method' : 'function', // location: { // file: normalizedPath, From ab00b3c75832d2a468d23d4c6a1fc634a4b1a146 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Fri, 15 Nov 2024 15:58:57 +0400 Subject: [PATCH 7/8] update the snapshots for decorators --- .../src => schema-data/decorators}/class.ts | 0 .../src => schema-data/decorators}/index.ts | 0 .../src => schema-data/decorators}/object.ts | 0 __output__/schema-data/decorators.bundle.js | 120 ++++++++++ .../schema-data/decorators.bundle.js.map | 7 + __output__/schema-data/decorators.schema.json | 115 ++++++++++ .../inheritance-contract.schema.json | 3 +- .../schema-data/public-methods.bundle.js.map | 2 +- .../schema-data/public-methods.schema.json | 3 +- .../schema-data/state-export.schema.json | 3 +- .../schemaExtractor.test.ts.snap | 121 ++++++++++ .../build/__tests__/schemaExtractor.test.ts | 14 ++ packages/build/src/decorators.ts | 215 +++++++----------- packages/build/src/schemaExtractor.ts | 4 +- 14 files changed, 469 insertions(+), 138 deletions(-) rename __fixtures__/{decorators/src => schema-data/decorators}/class.ts (100%) rename __fixtures__/{decorators/src => schema-data/decorators}/index.ts (100%) rename __fixtures__/{decorators/src => schema-data/decorators}/object.ts (100%) create mode 100644 __output__/schema-data/decorators.bundle.js create mode 100644 __output__/schema-data/decorators.bundle.js.map create mode 100644 __output__/schema-data/decorators.schema.json diff --git a/__fixtures__/decorators/src/class.ts b/__fixtures__/schema-data/decorators/class.ts similarity index 100% rename from __fixtures__/decorators/src/class.ts rename to __fixtures__/schema-data/decorators/class.ts diff --git a/__fixtures__/decorators/src/index.ts b/__fixtures__/schema-data/decorators/index.ts similarity index 100% rename from __fixtures__/decorators/src/index.ts rename to __fixtures__/schema-data/decorators/index.ts diff --git a/__fixtures__/decorators/src/object.ts b/__fixtures__/schema-data/decorators/object.ts similarity index 100% rename from __fixtures__/decorators/src/object.ts rename to __fixtures__/schema-data/decorators/object.ts diff --git a/__output__/schema-data/decorators.bundle.js b/__output__/schema-data/decorators.bundle.js new file mode 100644 index 0000000..9e9b086 --- /dev/null +++ b/__output__/schema-data/decorators.bundle.js @@ -0,0 +1,120 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name); +var __typeError = (msg) => { + throw TypeError(msg); +}; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)]; +var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"]; +var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn; +var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) }); +var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]); +var __runInitializers = (array, flags, self, value) => { + for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value = fns[i].call(self, value); + return value; +}; +var __decorateElement = (array, flags, name, decorators, target, extra) => { + var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16); + var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5]; + var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []); + var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() { + return __privateGet(this, extra); + }, set [name](x) { + return __privateSet(this, extra, x); + } }, name)); + k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name) : __name(target, name); + for (var i = decorators.length - 1; i >= 0; i--) { + ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers); + if (k) { + ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x }; + if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name]; + if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y; + } + it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1; + if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it); + else if (typeof it !== "object" || it === null) __typeError("Object expected"); + else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn); + } + return k || __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target; +}; +var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); +var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); +var __privateIn = (member, obj) => Object(obj) !== obj ? __typeError('Cannot use the "in" operator on this value') : member.has(obj); +var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); +var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); +var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); + +// ../../__fixtures__/schema-data/decorators/class.ts +var _decrement_dec, _increment_dec, _getCount_dec, _init; +import { admin, creator, external, internal } from "@hyperweb/decorators"; +_getCount_dec = [external, admin], _increment_dec = [internal, admin], _decrement_dec = [creator]; +var Counter = class { + constructor(initialState) { + __runInitializers(_init, 5, this); + __publicField(this, "state"); + this.state.count = initialState; + } + getCount() { + return this.state.count; + } + increment(amount) { + this.state.count = this.state.count.add(amount); + } + decrement(amount) { + if (this.state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + this.state.count = this.state.count.sub(amount); + } +}; +_init = __decoratorStart(null); +__decorateElement(_init, 1, "getCount", _getCount_dec, Counter); +__decorateElement(_init, 1, "increment", _increment_dec, Counter); +__decorateElement(_init, 1, "decrement", _decrement_dec, Counter); +__decoratorMetadata(_init, Counter); + +// ../../__fixtures__/schema-data/decorators/object.ts +var start = (initialCount) => { + let state = { + count: initialCount + }; + return { + getCount: () => state.count, + increment: (amount) => { + state.count = state.count.add(amount); + return state.count; + }, + decrement: (amount) => { + if (state.count.lt(amount)) { + throw new Error("Count cannot be negative"); + } + state.count = state.count.sub(amount); + return state.count; + } + }; +}; + +// ../../__fixtures__/schema-data/decorators/index.ts +var _fetchData_dec, _init2; +_fetchData_dec = [permission("debug", "level"), performance]; +var MyClass = class { + constructor() { + __runInitializers(_init2, 5, this); + } + async fetchData() { + } +}; +_init2 = __decoratorStart(null); +__decorateElement(_init2, 1, "fetchData", _fetchData_dec, MyClass); +__decoratorMetadata(_init2, MyClass); +var decorators_default = MyClass; +export { + Counter, + MyClass, + decorators_default as default, + start +}; +//# sourceMappingURL=decorators.bundle.js.map diff --git a/__output__/schema-data/decorators.bundle.js.map b/__output__/schema-data/decorators.bundle.js.map new file mode 100644 index 0000000..924a954 --- /dev/null +++ b/__output__/schema-data/decorators.bundle.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../__fixtures__/schema-data/decorators/class.ts", "../../__fixtures__/schema-data/decorators/object.ts", "../../__fixtures__/schema-data/decorators/index.ts"], + "sourcesContent": ["import { admin, creator, external, internal } from '@hyperweb/decorators';\nimport { BigNumber } from 'jsd-std';\n\nexport interface State {\n count: BigNumber;\n}\n\nexport class Counter {\n private state: State;\n\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n @external\n @admin\n public getCount(): BigNumber {\n return this.state.count;\n }\n \n // Only admin and creator can increment\n @internal\n @admin\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n @creator\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error('Count cannot be negative');\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n @permission('debug', 'level')\n @performance\n async fetchData() {\n // ... method implementation\n }\n}\n\nexport default MyClass;\nexport {MyClass};"], + "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA,SAAS,OAAO,SAAS,UAAU,gBAAgB;AAejD,iBAAC,UACA,QAMD,kBAAC,UACA,QAMD,kBAAC;AAtBI,IAAM,UAAN,MAAc;AAAA,EAGnB,YAAY,cAAqB;AAH5B;AACL,wBAAQ;AAGN,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA,EAKO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAKO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA,EAIO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;AA7BO;AAUL,4BAAO,YAFP,eARW;AAiBX,4BAAO,aAFP,gBAfW;AAuBX,4BAAO,aADP,gBAtBW;AAAN,2BAAM;;;ACAN,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC7BA,oBAAAA;AAGE,kBAAC,WAAW,SAAS,OAAO,GAC3B;AAFH,IAAM,UAAN,MAAc;AAAA,EAAd;AAAA,sBAAAA,QAAA;AAAA;AAAA,EAGE,MAAM,YAAY;AAAA,EAElB;AACF;AANAA,SAAA;AAGE,kBAAAA,QAAA,GAAM,aAFN,gBADI;AAAN,oBAAAA,QAAM;AAQN,IAAO,qBAAQ;", + "names": ["_init"] +} diff --git a/__output__/schema-data/decorators.schema.json b/__output__/schema-data/decorators.schema.json new file mode 100644 index 0000000..7317e7c --- /dev/null +++ b/__output__/schema-data/decorators.schema.json @@ -0,0 +1,115 @@ +{ + "state": { + "type": "object", + "properties": { + "count": { + "type": "any" + } + } + }, + "methods": [ + { + "name": "fetchData", + "parameters": [], + "returnType": { + "type": "object", + "properties": { + "then": { + "type": "any" + }, + "catch": { + "type": "any" + }, + "finally": { + "type": "any" + }, + "__@toStringTag@159": { + "type": "string" + } + } + } + } + ], + "decorators": [ + { + "name": "external", + "args": [], + "targetName": "getCount", + "targetType": "method", + "location": { + "file": "class.ts", + "line": 16, + "column": 3 + } + }, + { + "name": "admin", + "args": [], + "targetName": "getCount", + "targetType": "method", + "location": { + "file": "class.ts", + "line": 17, + "column": 3 + } + }, + { + "name": "internal", + "args": [], + "targetName": "increment", + "targetType": "method", + "location": { + "file": "class.ts", + "line": 23, + "column": 3 + } + }, + { + "name": "admin", + "args": [], + "targetName": "increment", + "targetType": "method", + "location": { + "file": "class.ts", + "line": 24, + "column": 3 + } + }, + { + "name": "creator", + "args": [], + "targetName": "decrement", + "targetType": "method", + "location": { + "file": "class.ts", + "line": 30, + "column": 3 + } + }, + { + "name": "permission", + "args": [ + "debug", + "level" + ], + "targetName": "fetchData", + "targetType": "method", + "location": { + "file": "index.ts", + "line": 4, + "column": 3 + } + }, + { + "name": "performance", + "args": [], + "targetName": "fetchData", + "targetType": "method", + "location": { + "file": "index.ts", + "line": 5, + "column": 3 + } + } + ] +} \ No newline at end of file diff --git a/__output__/schema-data/inheritance-contract.schema.json b/__output__/schema-data/inheritance-contract.schema.json index 9a155bd..f412f0e 100644 --- a/__output__/schema-data/inheritance-contract.schema.json +++ b/__output__/schema-data/inheritance-contract.schema.json @@ -22,5 +22,6 @@ "type": "any" } } - ] + ], + "decorators": [] } \ No newline at end of file diff --git a/__output__/schema-data/public-methods.bundle.js.map b/__output__/schema-data/public-methods.bundle.js.map index 6f48365..a15bb57 100644 --- a/__output__/schema-data/public-methods.bundle.js.map +++ b/__output__/schema-data/public-methods.bundle.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../__fixtures__/schema-data/public-methods/contract.ts", "../../__fixtures__/schema-data/public-methods/index.ts"], - "sourcesContent": ["import { State } from './state';\n\nexport class MyContract {\n private state: State;\n\n constructor() {\n this.state = {\n count: 0,\n startCoin: {\n denom: 'uatom',\n amount: '1000'\n },\n tokens: []\n };\n }\n\n public increment() {\n this.state.count++;\n }\n\n private reset() {\n this.state.count = 0;\n }\n\n public addToken(denom: string, amount: string) {\n this.state.tokens.push({ denom, amount });\n }\n\n public removeToken(index: number) {\n this.state.tokens.splice(index, 1);\n }\n}", "import { MyContract } from \"./contract\";\nexport type { State } from \"./state\";\n\nexport default MyContract;\n"], + "sourcesContent": ["import { State } from './state';\n\nexport class MyContract {\n private state: State;\n\n constructor() {\n this.state = {\n count: 0,\n startCoin: {\n denom: 'uatom',\n amount: '1000'\n },\n tokens: []\n };\n }\n\n public increment() {\n this.state.count++;\n }\n\n private reset() {\n this.state.count = 0;\n }\n\n public addToken(denom: string, amount: string) {\n this.state.tokens.push({ denom, amount });\n }\n\n public removeToken(index: number) {\n this.state.tokens.splice(index, 1);\n }\n}\n", "import { MyContract } from \"./contract\";\nexport type { State } from \"./state\";\n\nexport default MyContract;\n"], "mappings": ";AAEO,IAAM,aAAN,MAAiB;AAAA,EACd;AAAA,EAER,cAAc;AACZ,SAAK,QAAQ;AAAA,MACX,OAAO;AAAA,MACP,WAAW;AAAA,QACT,OAAO;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,MACA,QAAQ,CAAC;AAAA,IACX;AAAA,EACF;AAAA,EAEO,YAAY;AACjB,SAAK,MAAM;AAAA,EACb;AAAA,EAEQ,QAAQ;AACd,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA,EAEO,SAAS,OAAe,QAAgB;AAC7C,SAAK,MAAM,OAAO,KAAK,EAAE,OAAO,OAAO,CAAC;AAAA,EAC1C;AAAA,EAEO,YAAY,OAAe;AAChC,SAAK,MAAM,OAAO,OAAO,OAAO,CAAC;AAAA,EACnC;AACF;;;AC5BA,IAAO,yBAAQ;", "names": [] } diff --git a/__output__/schema-data/public-methods.schema.json b/__output__/schema-data/public-methods.schema.json index b226118..4aaa6ea 100644 --- a/__output__/schema-data/public-methods.schema.json +++ b/__output__/schema-data/public-methods.schema.json @@ -74,5 +74,6 @@ "type": "any" } } - ] + ], + "decorators": [] } \ No newline at end of file diff --git a/__output__/schema-data/state-export.schema.json b/__output__/schema-data/state-export.schema.json index ff224e2..8a669b5 100644 --- a/__output__/schema-data/state-export.schema.json +++ b/__output__/schema-data/state-export.schema.json @@ -32,5 +32,6 @@ } } }, - "methods": [] + "methods": [], + "decorators": [] } \ No newline at end of file diff --git a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap index bc3c165..f15aeb7 100644 --- a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap +++ b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap @@ -2,6 +2,7 @@ exports[`schemaExtractorPlugin should extract a basic contract with public and private methods 1`] = ` { + "decorators": [], "methods": [], "state": { "properties": { @@ -39,8 +40,127 @@ exports[`schemaExtractorPlugin should extract a basic contract with public and p } `; +exports[`schemaExtractorPlugin should extract decorators and state from source 1`] = ` +{ + "decorators": [ + { + "args": [], + "location": { + "column": 3, + "file": "class.ts", + "line": 16, + }, + "name": "external", + "targetName": "getCount", + "targetType": "method", + }, + { + "args": [], + "location": { + "column": 3, + "file": "class.ts", + "line": 17, + }, + "name": "admin", + "targetName": "getCount", + "targetType": "method", + }, + { + "args": [], + "location": { + "column": 3, + "file": "class.ts", + "line": 23, + }, + "name": "internal", + "targetName": "increment", + "targetType": "method", + }, + { + "args": [], + "location": { + "column": 3, + "file": "class.ts", + "line": 24, + }, + "name": "admin", + "targetName": "increment", + "targetType": "method", + }, + { + "args": [], + "location": { + "column": 3, + "file": "class.ts", + "line": 30, + }, + "name": "creator", + "targetName": "decrement", + "targetType": "method", + }, + { + "args": [ + "debug", + "level", + ], + "location": { + "column": 3, + "file": "index.ts", + "line": 4, + }, + "name": "permission", + "targetName": "fetchData", + "targetType": "method", + }, + { + "args": [], + "location": { + "column": 3, + "file": "index.ts", + "line": 5, + }, + "name": "performance", + "targetName": "fetchData", + "targetType": "method", + }, + ], + "methods": [ + { + "name": "fetchData", + "parameters": [], + "returnType": { + "properties": { + "__@toStringTag@159": { + "type": "string", + }, + "catch": { + "type": "any", + }, + "finally": { + "type": "any", + }, + "then": { + "type": "any", + }, + }, + "type": "object", + }, + }, + ], + "state": { + "properties": { + "count": { + "type": "any", + }, + }, + "type": "object", + }, +} +`; + exports[`schemaExtractorPlugin should extract methods and state from classes inheritance-contract 1`] = ` { + "decorators": [], "methods": [ { "name": "increment", @@ -70,6 +190,7 @@ exports[`schemaExtractorPlugin should extract methods and state from classes inh exports[`schemaExtractorPlugin should extract methods from classes public methods 1`] = ` { + "decorators": [], "methods": [ { "name": "increment", diff --git a/packages/build/__tests__/schemaExtractor.test.ts b/packages/build/__tests__/schemaExtractor.test.ts index 9fb446e..ce01ed3 100644 --- a/packages/build/__tests__/schemaExtractor.test.ts +++ b/packages/build/__tests__/schemaExtractor.test.ts @@ -12,6 +12,7 @@ const runTest = async (fixtureName: string) => { const buildOptions: Partial = { entryPoints: [join(fixtureDir, 'index.ts')], outfile: join(outputDir, `${fixtureName}.bundle.js`), + external: ['@hyperweb/decorators'], customPlugins: [ schemaExtractorPlugin({ outputPath: schemaOutputPath, @@ -71,4 +72,17 @@ describe('schemaExtractorPlugin', () => { expect(schemaData).toMatchSnapshot(); }); + + + it('should extract decorators and state from source', async () => { + const schemaData = await runTest('decorators'); + + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + + expect(schemaData).toHaveProperty('decorators'); + + expect(schemaData).toMatchSnapshot(); + }); }); diff --git a/packages/build/src/decorators.ts b/packages/build/src/decorators.ts index a515e94..a58e50b 100644 --- a/packages/build/src/decorators.ts +++ b/packages/build/src/decorators.ts @@ -1,17 +1,11 @@ -import generate from '@babel/generator'; -import * as parser from '@babel/parser'; -import traverse from '@babel/traverse'; -import * as t from '@babel/types'; -import { Plugin } from 'esbuild'; +import * as ts from 'typescript'; import * as path from 'path'; -import { HyperwebBuildOptions } from './index'; - -interface DecoratorInfo { +export interface DecoratorInfo { name: string; args: any[]; targetName: string; - targetType?: 'method' | 'function'; + targetType?: 'class' | 'method' | 'property' | 'parameter' | 'function' | 'unknown'; location?: { file: string; line: number; @@ -19,132 +13,87 @@ interface DecoratorInfo { }; } -interface DecoratorExtractorOptions { - outputPath?: string; // Where to save the decorator metadata - include?: RegExp[]; // Patterns for files to process - exclude?: RegExp[]; // Patterns for files to ignore -} - -const decoratorMetadata: Record = {}; - - -function normalizeFilePath(filePath: string, rootDir?: string): string { - const projectRoot = rootDir || process.cwd(); - const relativePath = path.relative(projectRoot, filePath); - return relativePath - .replace(/\\/g, '/') - .replace(/^\.\//, ''); - } - -export const createDecoratorExtractorPlugin = ( - pluginOptions: DecoratorExtractorOptions = {}, - hyperwebOptions?: HyperwebBuildOptions -): Plugin => ({ - name: 'decorator-extractor', - - setup(build) { - - // Set up the file filter - const filter = { - include: pluginOptions.include || [/\.[jt]sx?$/], - exclude: pluginOptions.exclude || [/node_modules/], - }; - - build.onLoad({ filter: new RegExp(filter.include.map(r => r.source).join('|')) }, async (args) => { - // Skip excluded files - if (filter.exclude.some(pattern => pattern.test(args.path))) { - return null; - } - - // Read the file - const source = await require('fs').promises.readFile(args.path, 'utf8'); - - // Parse the code with babel - const ast = parser.parse(source, { - sourceType: 'module', - plugins: ['typescript', 'decorators-legacy'], - }); - - // Track if we made any modifications - let modified = false; - - // Traverse the AST - traverse(ast, { - Decorator(path) { - const decorator = path.node; - const parent = path.parent; - - // Only process method or function decorators - if (!t.isClassMethod(parent as any) && !t.isFunctionDeclaration(parent as any)) { - return; +// Extract decorators using TypeScript AST +export function extractDecoratorsFromSourceFile( + sourceFile: ts.SourceFile, + checker: ts.TypeChecker, + schemaData: Record, + baseDir: string +) { + const decorators: DecoratorInfo[] = []; + + const visitNode = (node: ts.Node) => { + if (ts.canHaveDecorators(node)) { + const nodeDecorators = ts.getDecorators(node); + if (nodeDecorators) { + nodeDecorators.forEach((decorator) => { + const decoratorInfo = extractDecoratorInfo(decorator, node, sourceFile, baseDir); + if (decoratorInfo) { + decorators.push(decoratorInfo); } - - const rootDir = hyperwebOptions?.absWorkingDir || process.cwd(); - const normalizedPath = normalizeFilePath(args.path, rootDir); - - // Get decorator information - const decoratorInfo: DecoratorInfo = { - // @ts-ignore - name: t.isIdentifier(decorator.expression) - ? decorator.expression.name - // @ts-ignore - : t.isCallExpression(decorator.expression) - ? (decorator.expression.callee as t.Identifier).name - : 'unknown', - // @ts-ignore - args: t.isCallExpression(decorator.expression) - ? decorator.expression.arguments.map(arg => - // @ts-ignore - t.isLiteral(arg) ? (arg as any).value : null) - : [], - // @ts-ignore - targetName: t.isClassMethod(parent) - ? (parent.key as t.Identifier).name - : (parent as t.FunctionDeclaration).id?.name || 'anonymous', - // @ts-ignore - // targetType: t.isClassMethod(parent) ? 'method' : 'function', - - // location: { - // file: normalizedPath, - // line: decorator.loc?.start.line || 0, - // column: decorator.loc?.start.column || 0, - // } - }; - - // Store the metadata - if (!decoratorMetadata[normalizedPath]) { - decoratorMetadata[normalizedPath] = []; - } - decoratorMetadata[normalizedPath].push(decoratorInfo); - - // Remove the decorator - path.remove(); - modified = true; - } - }); - - // If we made modifications, generate new code - if (modified) { - const output = generate(ast, {}, source); - - return { - contents: output.code, - loader: args.path.endsWith('.ts') ? 'ts' : 'js', - }; + }); } + } - return null; - }); + ts.forEachChild(node, visitNode); + }; - build.onEnd(async () => { - if (pluginOptions.outputPath) { - // Save the metadata to the specified file - await require('fs').promises.writeFile( - pluginOptions.outputPath, - JSON.stringify(decoratorMetadata, null, 2), - 'utf8' - ); - } - }); + visitNode(sourceFile); + schemaData.decorators.push(...decorators); +} + +// Extract detailed decorator information +function extractDecoratorInfo( + decorator: ts.Decorator, + targetNode: ts.Node, + sourceFile: ts.SourceFile, + baseDir: string +): DecoratorInfo | null { + const decoratorExpression = decorator.expression; + + let decoratorName = 'unknown'; + let decoratorArgs: any[] = []; + + if (ts.isIdentifier(decoratorExpression)) { + decoratorName = decoratorExpression.text; + } else if (ts.isCallExpression(decoratorExpression)) { + if (ts.isIdentifier(decoratorExpression.expression)) { + decoratorName = decoratorExpression.expression.text; + } + decoratorArgs = decoratorExpression.arguments.map((arg) => + ts.isStringLiteral(arg) || ts.isNumericLiteral(arg) + ? arg.text + : 'complex' + ); } -}); \ No newline at end of file + + const targetName = (ts.isClassDeclaration(targetNode) && targetNode.name?.text) || + (ts.isMethodDeclaration(targetNode) && targetNode.name.getText()) || + (ts.isPropertyDeclaration(targetNode) && targetNode.name.getText()) || + (ts.isParameter(targetNode) && `parameter_${targetNode.getStart()}`) || + 'unknown'; + + const targetType = ts.isClassDeclaration(targetNode) + ? 'class' + : ts.isMethodDeclaration(targetNode) + ? 'method' + : ts.isPropertyDeclaration(targetNode) + ? 'property' + : ts.isParameter(targetNode) + ? 'parameter' + : 'unknown'; + + const { line, character } = sourceFile.getLineAndCharacterOfPosition(decorator.getStart()); + + return { + name: decoratorName, + args: decoratorArgs, + targetName: targetName || 'unknown', + targetType, + location: { + file: path.relative(baseDir, sourceFile.fileName), + line: line + 1, + column: character + 1, + }, + }; +} diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 044201f..f1e0a60 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import * as ts from 'typescript'; import { HyperwebBuildOptions } from './build'; +import { extractDecoratorsFromSourceFile } from "./decorators"; interface SchemaExtractorOptions { outputPath?: string; @@ -31,7 +32,7 @@ export const schemaExtractorPlugin = ( }); const checker = program.getTypeChecker(); - const schemaData: Record = { state: {}, methods: [] }; + const schemaData: Record = { state: {}, methods: [], decorators: [] }; // Extract state and methods from the contract's default export program.getSourceFiles().forEach((sourceFile) => { @@ -39,6 +40,7 @@ export const schemaExtractorPlugin = ( extractDefaultExport(sourceFile, checker, schemaData); extractStateInterface(sourceFile, checker, schemaData); + extractDecoratorsFromSourceFile(sourceFile, checker, schemaData, baseDir); }); const outputPath = From 94d04253b5f5131abcbb4317af2e050437c57743 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Sat, 16 Nov 2024 12:56:05 +0400 Subject: [PATCH 8/8] delete unused test files --- __output__/decorators/bundle.js | 57 --------------------- __output__/decorators/bundle.js.map | 7 --- __output__/decorators/decorators.json | 44 ---------------- packages/build/__tests__/decorators.test.ts | 41 --------------- 4 files changed, 149 deletions(-) delete mode 100644 __output__/decorators/bundle.js delete mode 100644 __output__/decorators/bundle.js.map delete mode 100644 __output__/decorators/decorators.json delete mode 100644 packages/build/__tests__/decorators.test.ts diff --git a/__output__/decorators/bundle.js b/__output__/decorators/bundle.js deleted file mode 100644 index 60d0183..0000000 --- a/__output__/decorators/bundle.js +++ /dev/null @@ -1,57 +0,0 @@ -// ../../__fixtures__/decorators/src/class.ts -var Counter = class { - state; - constructor(initialState) { - this.state.count = initialState; - } - // Public by default (no decorator needed) - getCount() { - return this.state.count; - } - // Only admin and creator can increment - increment(amount) { - this.state.count = this.state.count.add(amount); - } - // Only creator can decrement - decrement(amount) { - if (this.state.count.lt(amount)) { - throw new Error("Count cannot be negative"); - } - this.state.count = this.state.count.sub(amount); - } -}; - -// ../../__fixtures__/decorators/src/object.ts -var start = (initialCount) => { - let state = { - count: initialCount - }; - return { - getCount: () => state.count, - increment: (amount) => { - state.count = state.count.add(amount); - return state.count; - }, - decrement: (amount) => { - if (state.count.lt(amount)) { - throw new Error("Count cannot be negative"); - } - state.count = state.count.sub(amount); - return state.count; - } - }; -}; - -// ../../__fixtures__/decorators/src/index.ts -var MyClass = class { - async fetchData() { - } -}; -var src_default = MyClass; -export { - Counter, - MyClass, - src_default as default, - start -}; -//# sourceMappingURL=bundle.js.map diff --git a/__output__/decorators/bundle.js.map b/__output__/decorators/bundle.js.map deleted file mode 100644 index 5d35b25..0000000 --- a/__output__/decorators/bundle.js.map +++ /dev/null @@ -1,7 +0,0 @@ -{ - "version": 3, - "sources": ["../../__fixtures__/decorators/src/class.ts", "../../__fixtures__/decorators/src/object.ts", "../../__fixtures__/decorators/src/index.ts"], - "sourcesContent": ["import { admin, creator, external, internal } from '@hyperweb/decorators';\nimport { BigNumber } from 'jsd-std';\nexport interface State {\n count: BigNumber;\n}\nexport class Counter {\n private state: State;\n constructor(initialState: State) {\n this.state.count = initialState;\n }\n\n // Public by default (no decorator needed)\n public getCount(): BigNumber {\n return this.state.count;\n }\n\n // Only admin and creator can increment\n public increment(amount: BigNumber): void {\n this.state.count = this.state.count.add(amount);\n }\n\n // Only creator can decrement\n public decrement(amount: BigNumber): void {\n if (this.state.count.lt(amount)) {\n throw new Error('Count cannot be negative');\n }\n this.state.count = this.state.count.sub(amount);\n }\n}", "import { BigNumber } from \"jsd-std\";\n\ninterface State {\n count: BigNumber;\n}\n\n// Core contract logic\nexport const start = (initialCount: BigNumber) => {\n let state: State = {\n count: initialCount\n };\n\n // HOW TO EVEN DO DECORATORS\n return {\n getCount: () => state.count,\n \n increment: (amount: BigNumber) => {\n state.count = state.count.add(amount);\n return state.count;\n },\n \n decrement: (amount: BigNumber) => {\n if (state.count.lt(amount)) {\n throw new Error(\"Count cannot be negative\");\n }\n state.count = state.count.sub(amount);\n return state.count;\n }\n };\n};", "export * from './class';\nexport * from './object';\nclass MyClass {\n async fetchData() {\n // ... method implementation\n }\n}\nexport default MyClass;\nexport { MyClass };"], - "mappings": ";AAKO,IAAM,UAAN,MAAc;AAAA,EACX;AAAA,EACR,YAAY,cAAqB;AAC/B,SAAK,MAAM,QAAQ;AAAA,EACrB;AAAA;AAAA,EAGO,WAAsB;AAC3B,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AAAA;AAAA,EAGO,UAAU,QAAyB;AACxC,QAAI,KAAK,MAAM,MAAM,GAAG,MAAM,GAAG;AAC/B,YAAM,IAAI,MAAM,0BAA0B;AAAA,IAC5C;AACA,SAAK,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,MAAM;AAAA,EAChD;AACF;;;ACrBO,IAAM,QAAQ,CAAC,iBAA4B;AAChD,MAAI,QAAe;AAAA,IACjB,OAAO;AAAA,EACT;AAGA,SAAO;AAAA,IACL,UAAU,MAAM,MAAM;AAAA,IAEtB,WAAW,CAAC,WAAsB;AAChC,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,IAEA,WAAW,CAAC,WAAsB;AAChC,UAAI,MAAM,MAAM,GAAG,MAAM,GAAG;AAC1B,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,YAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACpC,aAAO,MAAM;AAAA,IACf;AAAA,EACF;AACF;;;AC3BA,IAAM,UAAN,MAAc;AAAA,EACZ,MAAM,YAAY;AAAA,EAElB;AACF;AACA,IAAO,cAAQ;", - "names": [] -} diff --git a/__output__/decorators/decorators.json b/__output__/decorators/decorators.json deleted file mode 100644 index 428186b..0000000 --- a/__output__/decorators/decorators.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "../../__fixtures__/decorators/src/index.ts": [ - { - "name": "permission", - "args": [ - "debug", - "level" - ], - "targetName": "fetchData" - }, - { - "name": "performance", - "args": [], - "targetName": "fetchData" - } - ], - "../../__fixtures__/decorators/src/class.ts": [ - { - "name": "external", - "args": [], - "targetName": "getCount" - }, - { - "name": "admin", - "args": [], - "targetName": "getCount" - }, - { - "name": "internal", - "args": [], - "targetName": "increment" - }, - { - "name": "admin", - "args": [], - "targetName": "increment" - }, - { - "name": "creator", - "args": [], - "targetName": "decrement" - } - ] -} \ No newline at end of file diff --git a/packages/build/__tests__/decorators.test.ts b/packages/build/__tests__/decorators.test.ts deleted file mode 100644 index 05de813..0000000 --- a/packages/build/__tests__/decorators.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import fs from 'fs/promises'; -import { join, resolve } from 'path'; - -import { createDecoratorExtractorPlugin } from '../src/decorators'; -import { HyperwebBuild, HyperwebBuildOptions } from '../src/index'; - -const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'decorators')); -const outputDir = resolve(join(__dirname, '/../../../__output__/', 'decorators')); - -describe('HyperwebBuild', () => { - it('builds the fixture project successfully', async () => { - const outfile = join(outputDir, 'bundle.js'); - - const options: Partial = { - entryPoints: [join(fixtureDir, 'src/index.ts')], - outfile, - external: ['otherpackage', '~somepackage'], - customPlugins: [ - createDecoratorExtractorPlugin({ - outputPath: join(outputDir, 'decorators.json'), - include: [/\.ts$/], // Only process TypeScript files - exclude: [/node_modules/, /\.test\.ts$/] // Skip node_modules and test files - }) - ] - }; - - await HyperwebBuild.build(options); - - // Check if the output file exists - const outfileExists = await fs.access(outfile) - .then(() => true) - .catch(() => false); - - expect(outfileExists).toBe(true); - - // Optionally, you can read the contents of the file and perform additional checks - // const bundleContent = await fs.readFile(outfile, 'utf-8'); - // expect(bundleContent).toContain('function greet'); - // expect(bundleContent).toContain('function farewell'); - }); -}); \ No newline at end of file