From f69b6a6e7a5ef198bf9cf682553b3a7bf5ec32f6 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Sat, 9 Nov 2024 16:41:03 +0400 Subject: [PATCH 01/20] add schema extractor initially --- packages/build/src/build.ts | 42 ++++++++++ packages/build/src/index.ts | 42 +--------- packages/build/src/schemaExtractor.ts | 106 ++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 40 deletions(-) create mode 100644 packages/build/src/build.ts create mode 100644 packages/build/src/schemaExtractor.ts diff --git a/packages/build/src/build.ts b/packages/build/src/build.ts new file mode 100644 index 0000000..cdae933 --- /dev/null +++ b/packages/build/src/build.ts @@ -0,0 +1,42 @@ +import esbuild from 'esbuild'; +import { createSchemaExtractorPlugin } from "./schemaExtractor"; + +export interface HyperwebBuildOptions extends esbuild.BuildOptions { + // Add any Hyperweb-specific options here + customPlugins?: esbuild.Plugin[]; +} + +export const defaultOptions: HyperwebBuildOptions = { + bundle: true, + minify: false, + outfile: 'dist/bundle.js', + platform: 'neutral', + sourcemap: true, + target: 'es2022', + logLevel: 'info', + format: 'esm', + minifyIdentifiers: false, + minifySyntax: false, + minifyWhitespace: false, + treeShaking: false, +}; + +export const HyperwebBuild = { + async build(options: Partial = {}): Promise { + const mergedOptions: HyperwebBuildOptions = { ...defaultOptions, ...options }; + + // Apply custom plugins if any + if (mergedOptions.customPlugins) { + mergedOptions.plugins = [ + ...(mergedOptions.plugins || []), + ...mergedOptions.customPlugins, + createSchemaExtractorPlugin({ outputPath: `${mergedOptions.outfile}.schema.json` }, mergedOptions), + ]; + delete mergedOptions.customPlugins; + } + + // @ts-ignore + return esbuild.build(mergedOptions); + } +}; + diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index c806f9a..499a2cc 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -1,40 +1,2 @@ -import esbuild from 'esbuild'; - -export interface HyperwebBuildOptions extends esbuild.BuildOptions { - // Add any Hyperweb-specific options here - customPlugins?: esbuild.Plugin[]; -} - -export const defaultOptions: HyperwebBuildOptions = { - bundle: true, - minify: false, - outfile: 'dist/bundle.js', - platform: 'neutral', - sourcemap: true, - target: 'es2022', - logLevel: 'info', - format: 'esm', - minifyIdentifiers: false, - minifySyntax: false, - minifyWhitespace: false, - treeShaking: false, -}; - -export const HyperwebBuild = { - async build(options: Partial = {}): Promise { - const mergedOptions: HyperwebBuildOptions = { ...defaultOptions, ...options }; - - // Apply custom plugins if any - if (mergedOptions.customPlugins) { - mergedOptions.plugins = [ - ...(mergedOptions.plugins || []), - ...mergedOptions.customPlugins - ]; - delete mergedOptions.customPlugins; - } - - // @ts-ignore - return esbuild.build(mergedOptions); - } -}; - +export { HyperwebBuild } from './build'; +export type { HyperwebBuildOptions } from './build'; diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts new file mode 100644 index 0000000..2101390 --- /dev/null +++ b/packages/build/src/schemaExtractor.ts @@ -0,0 +1,106 @@ +import * as parser from '@babel/parser'; +import traverse from '@babel/traverse'; +import generate from '@babel/generator'; +import * as t from '@babel/types'; +import * as path from 'path'; +import { Plugin } from 'esbuild'; +import { HyperwebBuildOptions } from './build'; +import { promises as fs } from 'fs'; + +interface SchemaExtractorOptions { + outputPath?: string; + include?: RegExp[]; + exclude?: RegExp[]; +} + +const schemaData: Record = { state: {}, methods: {} }; + +export const createSchemaExtractorPlugin = ( + pluginOptions: SchemaExtractorOptions = {}, + hyperwebOptions?: HyperwebBuildOptions +): Plugin => ({ + name: 'schema-extractor', + + setup(build) { + 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) => { + if (filter.exclude.some(pattern => pattern.test(args.path))) return null; + + const source = await fs.readFile(args.path, 'utf8'); + const ast = parser.parse(source, { sourceType: 'module', plugins: ['typescript'] }); + const normalizedPath = normalizeFilePath(args.path); + + traverse(ast, { + TSInterfaceDeclaration(path) { + if (path.node.id.name === 'State') { + schemaData.state[normalizedPath] = extractStateSchema(path.node); + } + }, + ClassDeclaration(path) { + if (path.node.id?.name === 'Contract') { + schemaData.methods[normalizedPath] = extractPublicMethods(path.node); + } + } + }); + + return null; + }); + + build.onEnd(async () => { + const bundlePath = hyperwebOptions?.outfile || 'dist/bundle.js'; + const schemaJsonPath = `${bundlePath}.schema.json`; + await fs.writeFile(schemaJsonPath, JSON.stringify(schemaData, null, 2), 'utf8'); + }); + } +}); + +// Helper function to normalize paths +function normalizeFilePath(filePath: string, rootDir?: string): string { + const projectRoot = rootDir || process.cwd(); + return path.relative(projectRoot, filePath).replace(/\\/g, '/').replace(/^\.\//, ''); +} + +// Extracts schema from State interface +function extractStateSchema(interfaceNode: t.TSInterfaceDeclaration): Record { + const schema: Record = { type: 'object', properties: {} }; + interfaceNode.body.body.forEach(member => { + if (t.isTSPropertySignature(member) && member.key) { + const propName = (member.key as t.Identifier).name; + schema.properties[propName] = parseType(member.typeAnnotation?.typeAnnotation); + } + }); + return schema; +} + +// Extracts public methods from the Contract class +function extractPublicMethods(classNode: t.ClassDeclaration): Record[] { + return classNode.body.body + .filter((member): member is t.ClassMethod => t.isClassMethod(member) && member.accessibility === 'public') + .map(member => ({ + functionName: (member.key as t.Identifier).name, + parameters: member.params.map(param => { + if (t.isIdentifier(param)) { + return { name: param.name, type: 'unknown' }; + } else if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) { + // Handle parameters with default values (AssignmentPattern) + return { name: param.left.name, type: 'unknown' }; + } else if (t.isRestElement(param) && t.isIdentifier(param.argument)) { + // Handle rest parameters (RestElement) + return { name: param.argument.name, type: 'unknown' }; + } + return { name: 'unknown', type: 'unknown' }; // Fallback for unsupported patterns + }) + })); +} + +// Parses type nodes to schema-compatible format +function parseType(typeNode: t.TSType): Record { + if (t.isTSNumberKeyword(typeNode)) return { type: 'number' }; + if (t.isTSStringKeyword(typeNode)) return { type: 'string' }; + if (t.isTSBooleanKeyword(typeNode)) return { type: 'boolean' }; + return { type: 'unknown' }; +} From d9f455efa86eee7fd62fb6ce4845d96a44be5336 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Sat, 9 Nov 2024 16:45:57 +0400 Subject: [PATCH 02/20] update schema extractor, remove normalized path functions --- packages/build/src/schemaExtractor.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 2101390..79005ec 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -32,17 +32,16 @@ export const createSchemaExtractorPlugin = ( const source = await fs.readFile(args.path, 'utf8'); const ast = parser.parse(source, { sourceType: 'module', plugins: ['typescript'] }); - const normalizedPath = normalizeFilePath(args.path); traverse(ast, { TSInterfaceDeclaration(path) { if (path.node.id.name === 'State') { - schemaData.state[normalizedPath] = extractStateSchema(path.node); + schemaData.state = extractStateSchema(path.node); } }, ClassDeclaration(path) { if (path.node.id?.name === 'Contract') { - schemaData.methods[normalizedPath] = extractPublicMethods(path.node); + schemaData.methods = extractPublicMethods(path.node); } } }); @@ -58,12 +57,6 @@ export const createSchemaExtractorPlugin = ( } }); -// Helper function to normalize paths -function normalizeFilePath(filePath: string, rootDir?: string): string { - const projectRoot = rootDir || process.cwd(); - return path.relative(projectRoot, filePath).replace(/\\/g, '/').replace(/^\.\//, ''); -} - // Extracts schema from State interface function extractStateSchema(interfaceNode: t.TSInterfaceDeclaration): Record { const schema: Record = { type: 'object', properties: {} }; From c86982f2b5079aed038907f50d4fa642e347d2e9 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Sun, 10 Nov 2024 14:11:34 +0400 Subject: [PATCH 03/20] update how schema is handled etc --- __fixtures__/schema-data/state-export/coin.ts | 4 + .../schema-data/state-export/index.ts | 7 + __output__/schema-data/bundle.js | 1 + __output__/schema-data/bundle.js.map | 7 + __output__/schema-data/bundle.js.schema.json | 36 +++ packages/build/__tests__/schema.test.ts | 52 ++++ packages/build/src/build.ts | 13 +- packages/build/src/index.ts | 1 + packages/build/src/schemaExtractor.ts | 286 +++++++++++++----- 9 files changed, 331 insertions(+), 76 deletions(-) create mode 100644 __fixtures__/schema-data/state-export/coin.ts create mode 100644 __fixtures__/schema-data/state-export/index.ts create mode 100644 __output__/schema-data/bundle.js create mode 100644 __output__/schema-data/bundle.js.map create mode 100644 __output__/schema-data/bundle.js.schema.json create mode 100644 packages/build/__tests__/schema.test.ts diff --git a/__fixtures__/schema-data/state-export/coin.ts b/__fixtures__/schema-data/state-export/coin.ts new file mode 100644 index 0000000..6ef6a4d --- /dev/null +++ b/__fixtures__/schema-data/state-export/coin.ts @@ -0,0 +1,4 @@ +export interface Coin { + denom: string; + amount: string; +} diff --git a/__fixtures__/schema-data/state-export/index.ts b/__fixtures__/schema-data/state-export/index.ts new file mode 100644 index 0000000..aa92603 --- /dev/null +++ b/__fixtures__/schema-data/state-export/index.ts @@ -0,0 +1,7 @@ +import { Coin } from "./coin"; + +export interface State { + count: number; + startCoin: Coin; + tokens: Coin[]; +} diff --git a/__output__/schema-data/bundle.js b/__output__/schema-data/bundle.js new file mode 100644 index 0000000..9f95631 --- /dev/null +++ b/__output__/schema-data/bundle.js @@ -0,0 +1 @@ +//# sourceMappingURL=bundle.js.map diff --git a/__output__/schema-data/bundle.js.map b/__output__/schema-data/bundle.js.map new file mode 100644 index 0000000..9865211 --- /dev/null +++ b/__output__/schema-data/bundle.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": [], + "sourcesContent": [], + "mappings": "", + "names": [] +} diff --git a/__output__/schema-data/bundle.js.schema.json b/__output__/schema-data/bundle.js.schema.json new file mode 100644 index 0000000..cb94ed1 --- /dev/null +++ b/__output__/schema-data/bundle.js.schema.json @@ -0,0 +1,36 @@ +{ + "state": { + "type": "object", + "properties": { + "count": { + "type": "number" + }, + "startCoin": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + } + } + } + }, + "methods": {} +} \ No newline at end of file diff --git a/packages/build/__tests__/schema.test.ts b/packages/build/__tests__/schema.test.ts new file mode 100644 index 0000000..72a083b --- /dev/null +++ b/packages/build/__tests__/schema.test.ts @@ -0,0 +1,52 @@ +import fs from 'fs/promises'; +import { join, resolve } from 'path'; + +import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; + +const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'state-export')); +const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data')); + +describe('HyperwebBuild', () => { + it('builds the fixture project successfully', async () => { + const outfile = join(outputDir, 'bundle.js'); + const schemaOutputPath = `${outfile}.schema.json`; + + const options: Partial = { + entryPoints: [join(fixtureDir, 'index.ts')], + outfile, + customPlugins: [ + schemaExtractorPlugin({ + outputPath: schemaOutputPath, + baseDir: fixtureDir, + 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); + + // Check if schema file exists + const schemafileExists = await fs.access(schemaOutputPath) + .then(() => true) + .catch(() => false); + + expect(schemafileExists).toBe(true); + + // Optionally, read the schema file and verify the contents + const schemaContent = await fs.readFile(schemaOutputPath, 'utf-8'); + const schemaData = JSON.parse(schemaContent); + + // Perform checks on schema structure (example: check if 'state' exists and has properties) + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + }); +}); diff --git a/packages/build/src/build.ts b/packages/build/src/build.ts index cdae933..751dbb3 100644 --- a/packages/build/src/build.ts +++ b/packages/build/src/build.ts @@ -1,5 +1,4 @@ import esbuild from 'esbuild'; -import { createSchemaExtractorPlugin } from "./schemaExtractor"; export interface HyperwebBuildOptions extends esbuild.BuildOptions { // Add any Hyperweb-specific options here @@ -30,13 +29,13 @@ export const HyperwebBuild = { mergedOptions.plugins = [ ...(mergedOptions.plugins || []), ...mergedOptions.customPlugins, - createSchemaExtractorPlugin({ outputPath: `${mergedOptions.outfile}.schema.json` }, mergedOptions), ]; - delete mergedOptions.customPlugins; + //delete mergedOptions.customPlugins; } - // @ts-ignore - return esbuild.build(mergedOptions); - } -}; + // Exclude customPlugins before passing to esbuild.build + const { customPlugins, ...buildOptions } = mergedOptions; + return esbuild.build(buildOptions); + }, +}; diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index 499a2cc..9144b8c 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -1,2 +1,3 @@ export { HyperwebBuild } from './build'; export type { HyperwebBuildOptions } from './build'; +export { schemaExtractorPlugin } from './schemaExtractor' \ No newline at end of file diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 79005ec..bf729f4 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -1,99 +1,247 @@ -import * as parser from '@babel/parser'; -import traverse from '@babel/traverse'; -import generate from '@babel/generator'; -import * as t from '@babel/types'; +import * as ts from 'typescript'; +import { promises as fs } from 'fs'; import * as path from 'path'; import { Plugin } from 'esbuild'; -import { HyperwebBuildOptions } from './build'; -import { promises as fs } from 'fs'; +import {HyperwebBuildOptions} from "./build"; interface SchemaExtractorOptions { outputPath?: string; + baseDir?: string; include?: RegExp[]; exclude?: RegExp[]; } -const schemaData: Record = { state: {}, methods: {} }; - -export const createSchemaExtractorPlugin = ( - pluginOptions: SchemaExtractorOptions = {}, - hyperwebOptions?: HyperwebBuildOptions +export const schemaExtractorPlugin = ( + pluginOptions: SchemaExtractorOptions = {} ): Plugin => ({ name: 'schema-extractor', setup(build) { - const filter = { - include: pluginOptions.include || [/\.[jt]sx?$/], - exclude: pluginOptions.exclude || [/node_modules/], - }; + const hyperwebBuildOptions = build.initialOptions; - build.onLoad({ filter: new RegExp(filter.include.map(r => r.source).join('|')) }, async (args) => { - if (filter.exclude.some(pattern => pattern.test(args.path))) return null; + build.onEnd(async () => { + const baseDir = pluginOptions.baseDir || process.cwd(); + const sourceFiles = getSourceFiles( + pluginOptions, + hyperwebBuildOptions, + baseDir + ); - const source = await fs.readFile(args.path, 'utf8'); - const ast = parser.parse(source, { sourceType: 'module', plugins: ['typescript'] }); + if (!sourceFiles.length) { + console.error( + 'No entry files provided or matched for schema extraction.' + ); + return; + } - traverse(ast, { - TSInterfaceDeclaration(path) { - if (path.node.id.name === 'State') { - schemaData.state = extractStateSchema(path.node); - } - }, - ClassDeclaration(path) { - if (path.node.id?.name === 'Contract') { - schemaData.methods = extractPublicMethods(path.node); + const program = ts.createProgram(sourceFiles, { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.CommonJS, + strict: true, + }); + + const checker = program.getTypeChecker(); + + const schemaData: Record = { state: {}, methods: {} }; + + const interfacesToExtract = ['State']; + + interfacesToExtract.forEach((interfaceName) => { + const sourceFile = program.getSourceFiles().find((file) => + file.statements.some( + (stmt) => + ts.isInterfaceDeclaration(stmt) && + stmt.name.text === interfaceName + ) + ); + + if (sourceFile) { + const interfaceNode = sourceFile.statements.find( + (stmt): stmt is ts.InterfaceDeclaration => + ts.isInterfaceDeclaration(stmt) && + stmt.name.text === interfaceName + ); + + if (interfaceNode) { + console.log(`Extracting schema for interface: ${interfaceName}`); + schemaData[interfaceName.toLowerCase()] = serializeType( + checker.getTypeAtLocation(interfaceNode), + checker + ); } + } else { + console.warn(`Interface ${interfaceName} not found.`); } }); - return null; + const outputPath = + pluginOptions.outputPath || path.join(baseDir, 'dist/schema.json'); + console.log('Writing schema data to:', outputPath); + + try { + await fs.writeFile( + outputPath, + JSON.stringify(schemaData, null, 2), + 'utf8' + ); + console.log(`Schema successfully written to ${outputPath}`); + } catch (error) { + console.error('Error writing schema data:', error); + } }); + }, +}); - build.onEnd(async () => { - const bundlePath = hyperwebOptions?.outfile || 'dist/bundle.js'; - const schemaJsonPath = `${bundlePath}.schema.json`; - await fs.writeFile(schemaJsonPath, JSON.stringify(schemaData, null, 2), 'utf8'); +function getSourceFiles( + pluginOptions: SchemaExtractorOptions, + hyperwebBuildOptions: HyperwebBuildOptions, + baseDir: string +): string[] { + const includePatterns = pluginOptions.include || [/\.tsx?$/]; + const excludePatterns = pluginOptions.exclude || [/node_modules/]; + + // Log the entryPoints for debugging purposes + console.log('Debug - entryPoints value:', hyperwebBuildOptions.entryPoints); + + if (!hyperwebBuildOptions.entryPoints) { + throw new Error('No entryPoints provided in build options.'); + } + + let resolvedFiles: string[] = []; + + if (Array.isArray(hyperwebBuildOptions.entryPoints)) { + resolvedFiles = hyperwebBuildOptions.entryPoints.map((entry) => { + if (typeof entry === 'string') { + return path.resolve(baseDir, entry); + } else { + throw new Error('Invalid entryPoints array item: expected string'); + } }); + } else if (typeof hyperwebBuildOptions.entryPoints === 'object') { + resolvedFiles = Object.values(hyperwebBuildOptions.entryPoints).map( + (file) => path.resolve(baseDir, file) + ); + } else { + throw new Error( + 'Invalid entryPoints format: expected string[] or Record' + ); } -}); -// Extracts schema from State interface -function extractStateSchema(interfaceNode: t.TSInterfaceDeclaration): Record { - const schema: Record = { type: 'object', properties: {} }; - interfaceNode.body.body.forEach(member => { - if (t.isTSPropertySignature(member) && member.key) { - const propName = (member.key as t.Identifier).name; - schema.properties[propName] = parseType(member.typeAnnotation?.typeAnnotation); - } + return resolvedFiles.filter((fileName) => { + const relativeFileName = path.relative(baseDir, fileName); + console.log('Checking file:', relativeFileName) + const matchesInclude = includePatterns.some((pattern) => + pattern.test(relativeFileName) + ); + const matchesExclude = excludePatterns.some((pattern) => + pattern.test(relativeFileName) + ); + return matchesInclude && !matchesExclude; }); - return schema; } -// Extracts public methods from the Contract class -function extractPublicMethods(classNode: t.ClassDeclaration): Record[] { - return classNode.body.body - .filter((member): member is t.ClassMethod => t.isClassMethod(member) && member.accessibility === 'public') - .map(member => ({ - functionName: (member.key as t.Identifier).name, - parameters: member.params.map(param => { - if (t.isIdentifier(param)) { - return { name: param.name, type: 'unknown' }; - } else if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) { - // Handle parameters with default values (AssignmentPattern) - return { name: param.left.name, type: 'unknown' }; - } else if (t.isRestElement(param) && t.isIdentifier(param.argument)) { - // Handle rest parameters (RestElement) - return { name: param.argument.name, type: 'unknown' }; - } - return { name: 'unknown', type: 'unknown' }; // Fallback for unsupported patterns - }) - })); -} +function serializeType( + type: ts.Type, + checker: ts.TypeChecker, + typeStack: ts.Type[] = [] +): any { + const typeString = checker.typeToString(type); + const isPrimitive = ['string', 'number', 'boolean', 'null', 'undefined', 'any'].includes(typeString); -// Parses type nodes to schema-compatible format -function parseType(typeNode: t.TSType): Record { - if (t.isTSNumberKeyword(typeNode)) return { type: 'number' }; - if (t.isTSStringKeyword(typeNode)) return { type: 'string' }; - if (t.isTSBooleanKeyword(typeNode)) return { type: 'boolean' }; - return { type: 'unknown' }; + if (typeStack.includes(type)) { + // Circular reference detected + return { $ref: typeString }; + } + + // Add the current type to the stack + const newTypeStack = [...typeStack, type]; + + // Handle primitive types directly + if (type.isStringLiteral()) { + return { type: 'string', enum: [type.value] }; + } + if (type.isNumberLiteral()) { + return { type: 'number', enum: [type.value] }; + } + if (typeString === 'string') { + return { type: 'string' }; + } + if (typeString === 'number') { + return { type: 'number' }; + } + if (typeString === 'boolean') { + return { type: 'boolean' }; + } + if (typeString === 'null') { + return { type: 'null' }; + } + if (typeString === 'undefined') { + return { type: 'undefined' }; + } + if (typeString === 'any') { + return { type: 'any' }; + } + + if (checker.isArrayType(type)) { + const typeReference = type as ts.TypeReference; + const typeArguments = checker.getTypeArguments(typeReference); + const elementType = typeArguments[0] || checker.getAnyType(); + + return { + type: 'array', + items: serializeType(elementType, checker, newTypeStack), + }; + } + + if (checker.isTupleType(type)) { + const typeArguments = checker.getTypeArguments(type as ts.TypeReference); + return { + type: 'array', + items: typeArguments.map((elementType) => + serializeType(elementType, checker, newTypeStack) + ), + }; + } + + if (type.isUnion()) { + return { + anyOf: type.types.map((subType) => + serializeType(subType, checker, newTypeStack) + ), + }; + } + + if (type.isIntersection()) { + return { + allOf: type.types.map((subType) => + serializeType(subType, checker, newTypeStack) + ), + }; + } + + if (type.getCallSignatures().length) { + // Function type + return { type: 'function' }; + } + + const properties = checker.getPropertiesOfType(type); + if (properties.length) { + const result: Record = {}; + properties.forEach((prop) => { + const propType = checker.getTypeOfSymbolAtLocation( + prop, + prop.valueDeclaration || prop.declarations[0] + ); + result[prop.getName()] = serializeType( + propType, + checker, + newTypeStack + ); + }); + return { type: 'object', properties: result }; + } + + // If none of the above, return any + return { type: 'any' }; } + From 04186be7a085398673ce82e731f178f1ddd8b79a Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 13:26:32 +0400 Subject: [PATCH 04/20] add matching with snapshot --- .../__snapshots__/schema.test.ts.snap | 40 +++++++++++++++++++ packages/build/__tests__/schema.test.ts | 2 + 2 files changed, 42 insertions(+) create mode 100644 packages/build/__tests__/__snapshots__/schema.test.ts.snap diff --git a/packages/build/__tests__/__snapshots__/schema.test.ts.snap b/packages/build/__tests__/__snapshots__/schema.test.ts.snap new file mode 100644 index 0000000..889463a --- /dev/null +++ b/packages/build/__tests__/__snapshots__/schema.test.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HyperwebBuild builds the fixture project successfully 1`] = ` +{ + "methods": {}, + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; diff --git a/packages/build/__tests__/schema.test.ts b/packages/build/__tests__/schema.test.ts index 72a083b..0713bde 100644 --- a/packages/build/__tests__/schema.test.ts +++ b/packages/build/__tests__/schema.test.ts @@ -48,5 +48,7 @@ describe('HyperwebBuild', () => { expect(schemaData).toHaveProperty('state'); expect(schemaData.state).toHaveProperty('type', 'object'); expect(schemaData.state).toHaveProperty('properties'); + + expect(schemaData).toMatchSnapshot(); }); }); From 7a20928ae793c11c29afa010d99c2ef17d95ea3f Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 14:06:37 +0400 Subject: [PATCH 05/20] update schame extractor --- .../schema-data/public-methods/contract.ts | 32 ++++++++++ .../schema-data/public-methods/index.ts | 4 ++ .../schema-data/public-methods/state.ts | 10 ++++ __output__/schema-data/bundle.js.schema.json | 36 ----------- .../schema-data/public-methods/bundle.js | 33 +++++++++++ .../schema-data/public-methods/bundle.js.map | 7 +++ .../public-methods/bundle.js.schema.json | 53 +++++++++++++++++ .../schema-data/{ => state-export}/bundle.js | 0 .../{ => state-export}/bundle.js.map | 0 .../state-export/bundle.js.schema.json | 4 ++ ....test.ts.snap => schemaState.test.ts.snap} | 0 .../build/__tests__/schemaMethods.test.ts | 59 +++++++++++++++++++ .../{schema.test.ts => schemaState.test.ts} | 2 +- 13 files changed, 203 insertions(+), 37 deletions(-) create mode 100644 __fixtures__/schema-data/public-methods/contract.ts create mode 100644 __fixtures__/schema-data/public-methods/index.ts create mode 100644 __fixtures__/schema-data/public-methods/state.ts delete mode 100644 __output__/schema-data/bundle.js.schema.json create mode 100644 __output__/schema-data/public-methods/bundle.js create mode 100644 __output__/schema-data/public-methods/bundle.js.map create mode 100644 __output__/schema-data/public-methods/bundle.js.schema.json rename __output__/schema-data/{ => state-export}/bundle.js (100%) rename __output__/schema-data/{ => state-export}/bundle.js.map (100%) create mode 100644 __output__/schema-data/state-export/bundle.js.schema.json rename packages/build/__tests__/__snapshots__/{schema.test.ts.snap => schemaState.test.ts.snap} (100%) create mode 100644 packages/build/__tests__/schemaMethods.test.ts rename packages/build/__tests__/{schema.test.ts => schemaState.test.ts} (98%) diff --git a/__fixtures__/schema-data/public-methods/contract.ts b/__fixtures__/schema-data/public-methods/contract.ts new file mode 100644 index 0000000..7f23674 --- /dev/null +++ b/__fixtures__/schema-data/public-methods/contract.ts @@ -0,0 +1,32 @@ +import { State } from './state'; + +export class MyContract { + private state: State; + + constructor() { + this.state = { + count: 0, + startCoin: { + denom: 'uatom', + amount: '1000' + }, + tokens: [] + }; + } + + public increment() { + this.state.count++; + } + + private reset() { + this.state.count = 0; + } + + public addToken(denom: string, amount: string) { + this.state.tokens.push({ denom, amount }); + } + + public removeToken(index: number) { + this.state.tokens.splice(index, 1); + } +} \ No newline at end of file diff --git a/__fixtures__/schema-data/public-methods/index.ts b/__fixtures__/schema-data/public-methods/index.ts new file mode 100644 index 0000000..d0740af --- /dev/null +++ b/__fixtures__/schema-data/public-methods/index.ts @@ -0,0 +1,4 @@ +import { MyContract } from "./contract"; +import { State } from './state'; + +export default MyContract; diff --git a/__fixtures__/schema-data/public-methods/state.ts b/__fixtures__/schema-data/public-methods/state.ts new file mode 100644 index 0000000..67b7d55 --- /dev/null +++ b/__fixtures__/schema-data/public-methods/state.ts @@ -0,0 +1,10 @@ +export interface State { + count: number; + startCoin: Coin; + tokens: Coin[]; +} + +interface Coin { + denom: string; + amount: string; +} diff --git a/__output__/schema-data/bundle.js.schema.json b/__output__/schema-data/bundle.js.schema.json deleted file mode 100644 index cb94ed1..0000000 --- a/__output__/schema-data/bundle.js.schema.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "state": { - "type": "object", - "properties": { - "count": { - "type": "number" - }, - "startCoin": { - "type": "object", - "properties": { - "denom": { - "type": "string" - }, - "amount": { - "type": "string" - } - } - }, - "tokens": { - "type": "array", - "items": { - "type": "object", - "properties": { - "denom": { - "type": "string" - }, - "amount": { - "type": "string" - } - } - } - } - } - }, - "methods": {} -} \ No newline at end of file diff --git a/__output__/schema-data/public-methods/bundle.js b/__output__/schema-data/public-methods/bundle.js new file mode 100644 index 0000000..40b5d5e --- /dev/null +++ b/__output__/schema-data/public-methods/bundle.js @@ -0,0 +1,33 @@ +// ../../__fixtures__/schema-data/public-methods/contract.ts +var MyContract = class { + state; + constructor() { + this.state = { + count: 0, + startCoin: { + denom: "uatom", + amount: "1000" + }, + tokens: [] + }; + } + increment() { + this.state.count++; + } + reset() { + this.state.count = 0; + } + addToken(denom, amount) { + this.state.tokens.push({ denom, amount }); + } + removeToken(index) { + this.state.tokens.splice(index, 1); + } +}; + +// ../../__fixtures__/schema-data/public-methods/index.ts +var public_methods_default = MyContract; +export { + public_methods_default as default +}; +//# sourceMappingURL=bundle.js.map diff --git a/__output__/schema-data/public-methods/bundle.js.map b/__output__/schema-data/public-methods/bundle.js.map new file mode 100644 index 0000000..88d38cc --- /dev/null +++ b/__output__/schema-data/public-methods/bundle.js.map @@ -0,0 +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\";\nimport { 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/bundle.js.schema.json b/__output__/schema-data/public-methods/bundle.js.schema.json new file mode 100644 index 0000000..b4ada5f --- /dev/null +++ b/__output__/schema-data/public-methods/bundle.js.schema.json @@ -0,0 +1,53 @@ +{ + "state": {}, + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any" + } + }, + { + "functionName": "reset", + "parameters": [], + "returnType": { + "type": "any" + } + }, + { + "functionName": "addToken", + "parameters": [ + { + "name": "denom", + "type": { + "type": "string" + } + }, + { + "name": "amount", + "type": { + "type": "string" + } + } + ], + "returnType": { + "type": "any" + } + }, + { + "functionName": "removeToken", + "parameters": [ + { + "name": "index", + "type": { + "type": "number" + } + } + ], + "returnType": { + "type": "any" + } + } + ] +} \ No newline at end of file diff --git a/__output__/schema-data/bundle.js b/__output__/schema-data/state-export/bundle.js similarity index 100% rename from __output__/schema-data/bundle.js rename to __output__/schema-data/state-export/bundle.js diff --git a/__output__/schema-data/bundle.js.map b/__output__/schema-data/state-export/bundle.js.map similarity index 100% rename from __output__/schema-data/bundle.js.map rename to __output__/schema-data/state-export/bundle.js.map diff --git a/__output__/schema-data/state-export/bundle.js.schema.json b/__output__/schema-data/state-export/bundle.js.schema.json new file mode 100644 index 0000000..641ea3d --- /dev/null +++ b/__output__/schema-data/state-export/bundle.js.schema.json @@ -0,0 +1,4 @@ +{ + "state": {}, + "methods": [] +} \ No newline at end of file diff --git a/packages/build/__tests__/__snapshots__/schema.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap similarity index 100% rename from packages/build/__tests__/__snapshots__/schema.test.ts.snap rename to packages/build/__tests__/__snapshots__/schemaState.test.ts.snap diff --git a/packages/build/__tests__/schemaMethods.test.ts b/packages/build/__tests__/schemaMethods.test.ts new file mode 100644 index 0000000..710d443 --- /dev/null +++ b/packages/build/__tests__/schemaMethods.test.ts @@ -0,0 +1,59 @@ +import fs from 'fs/promises'; +import { join, resolve } from 'path'; + +import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; + +const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'public-methods')); +const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'public-methods')); + +describe('HyperwebBuild', () => { + it('builds the fixture project successfully', async () => { + const outfile = join(outputDir, 'bundle.js'); + const schemaOutputPath = `${outfile}.schema.json`; + + const options: Partial = { + entryPoints: [join(fixtureDir, 'index.ts')], + outfile, + customPlugins: [ + schemaExtractorPlugin({ + outputPath: schemaOutputPath, + baseDir: fixtureDir, + 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); + + // Check if schema file exists + const schemafileExists = await fs.access(schemaOutputPath) + .then(() => true) + .catch(() => false); + + expect(schemafileExists).toBe(true); + + // Optionally, read the schema file and verify the contents + const schemaContent = await fs.readFile(schemaOutputPath, 'utf-8'); + const schemaData = JSON.parse(schemaContent); + + // Perform checks on schema structure (example: check if 'state' exists and has properties) + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + + // Perform checks on schema structure (example: check if 'methods' exists and has properties) + expect(schemaData).toHaveProperty('methods'); + expect(schemaData.methods).toHaveProperty('type', 'object'); + expect(schemaData.methods).toHaveProperty('properties'); + + expect(schemaData).toMatchSnapshot(); + }); +}); diff --git a/packages/build/__tests__/schema.test.ts b/packages/build/__tests__/schemaState.test.ts similarity index 98% rename from packages/build/__tests__/schema.test.ts rename to packages/build/__tests__/schemaState.test.ts index 0713bde..68003bf 100644 --- a/packages/build/__tests__/schema.test.ts +++ b/packages/build/__tests__/schemaState.test.ts @@ -4,7 +4,7 @@ import { join, resolve } from 'path'; import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'state-export')); -const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data')); +const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'state-export')); describe('HyperwebBuild', () => { it('builds the fixture project successfully', async () => { From 73fac677407b8a8aab04f57a430e4a06e06f655e Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 14:09:14 +0400 Subject: [PATCH 06/20] run lint --- packages/build/src/index.ts | 4 ++-- packages/build/src/schemaExtractor.ts | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index 9144b8c..5aaa13b 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -1,3 +1,3 @@ -export { HyperwebBuild } from './build'; export type { HyperwebBuildOptions } from './build'; -export { schemaExtractorPlugin } from './schemaExtractor' \ No newline at end of file +export { HyperwebBuild } from './build'; +export { schemaExtractorPlugin } from './schemaExtractor'; \ No newline at end of file diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index bf729f4..011f5ae 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -1,8 +1,9 @@ -import * as ts from 'typescript'; +import { Plugin } from 'esbuild'; import { promises as fs } from 'fs'; import * as path from 'path'; -import { Plugin } from 'esbuild'; -import {HyperwebBuildOptions} from "./build"; +import * as ts from 'typescript'; + +import { HyperwebBuildOptions } from './build'; interface SchemaExtractorOptions { outputPath?: string; @@ -129,7 +130,7 @@ function getSourceFiles( return resolvedFiles.filter((fileName) => { const relativeFileName = path.relative(baseDir, fileName); - console.log('Checking file:', relativeFileName) + console.log('Checking file:', relativeFileName); const matchesInclude = includePatterns.some((pattern) => pattern.test(relativeFileName) ); From 1515d8b6f97db8089df19ae028d710f86cd6c3ef Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 15:02:41 +0400 Subject: [PATCH 07/20] update schemaExtractor to output public methods properly --- .../schema-data/public-methods/index.ts | 2 +- .../schema-data/public-methods/bundle.js.map | 2 +- .../public-methods/bundle.js.schema.json | 41 ++++- packages/build/src/schemaExtractor.ts | 157 ++++++++++++------ 4 files changed, 139 insertions(+), 63 deletions(-) diff --git a/__fixtures__/schema-data/public-methods/index.ts b/__fixtures__/schema-data/public-methods/index.ts index d0740af..aea59d9 100644 --- a/__fixtures__/schema-data/public-methods/index.ts +++ b/__fixtures__/schema-data/public-methods/index.ts @@ -1,4 +1,4 @@ import { MyContract } from "./contract"; -import { State } from './state'; +export type { State } from "./state"; export default MyContract; diff --git a/__output__/schema-data/public-methods/bundle.js.map b/__output__/schema-data/public-methods/bundle.js.map index 88d38cc..d4e318a 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\";\nimport { 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}", "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/bundle.js.schema.json b/__output__/schema-data/public-methods/bundle.js.schema.json index b4ada5f..6b53510 100644 --- a/__output__/schema-data/public-methods/bundle.js.schema.json +++ b/__output__/schema-data/public-methods/bundle.js.schema.json @@ -1,5 +1,37 @@ { - "state": {}, + "state": { + "type": "object", + "properties": { + "count": { + "type": "number" + }, + "startCoin": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + } + } + } + }, "methods": [ { "functionName": "increment", @@ -8,13 +40,6 @@ "type": "any" } }, - { - "functionName": "reset", - "parameters": [], - "returnType": { - "type": "any" - } - }, { "functionName": "addToken", "parameters": [ diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 011f5ae..f23e49f 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -1,8 +1,7 @@ -import { Plugin } from 'esbuild'; +import * as ts from 'typescript'; import { promises as fs } from 'fs'; import * as path from 'path'; -import * as ts from 'typescript'; - +import { Plugin } from 'esbuild'; import { HyperwebBuildOptions } from './build'; interface SchemaExtractorOptions { @@ -42,36 +41,41 @@ export const schemaExtractorPlugin = ( }); const checker = program.getTypeChecker(); + const schemaData: Record = { state: {}, methods: [] }; - const schemaData: Record = { state: {}, methods: {} }; + // Extract state and methods from the contract's default export + program.getSourceFiles().forEach((sourceFile) => { + if (sourceFile.isDeclarationFile) return; - const interfacesToExtract = ['State']; + const defaultExport = sourceFile.statements.find((stmt) => + ts.isExportAssignment(stmt) && !stmt.isExportEquals + ) as ts.ExportAssignment | undefined; - interfacesToExtract.forEach((interfaceName) => { - const sourceFile = program.getSourceFiles().find((file) => - file.statements.some( - (stmt) => - ts.isInterfaceDeclaration(stmt) && - stmt.name.text === interfaceName - ) - ); + if (defaultExport && defaultExport.expression) { + console.log(`Found default export in file: ${sourceFile.fileName}`); - if (sourceFile) { - const interfaceNode = sourceFile.statements.find( - (stmt): stmt is ts.InterfaceDeclaration => - ts.isInterfaceDeclaration(stmt) && - stmt.name.text === interfaceName - ); + const contractSymbol = checker.getSymbolAtLocation(defaultExport.expression); - if (interfaceNode) { - console.log(`Extracting schema for interface: ${interfaceName}`); - schemaData[interfaceName.toLowerCase()] = serializeType( - checker.getTypeAtLocation(interfaceNode), - checker - ); + if (contractSymbol) { + console.log(`Extracting methods from contract symbol: ${contractSymbol.getName()}`); + extractPublicMethods(contractSymbol, checker, schemaData); + } else { + console.warn(`No symbol found for default export in ${sourceFile.fileName}`); } - } else { - console.warn(`Interface ${interfaceName} not found.`); + } + + // Extract the State interface + const stateInterface = sourceFile.statements.find( + (stmt): stmt is ts.InterfaceDeclaration => + ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'State' + ); + + if (stateInterface) { + console.log("Extracting schema for 'State' interface"); + schemaData.state = serializeType( + checker.getTypeAtLocation(stateInterface), + checker + ); } }); @@ -101,9 +105,6 @@ function getSourceFiles( const includePatterns = pluginOptions.include || [/\.tsx?$/]; const excludePatterns = pluginOptions.exclude || [/node_modules/]; - // Log the entryPoints for debugging purposes - console.log('Debug - entryPoints value:', hyperwebBuildOptions.entryPoints); - if (!hyperwebBuildOptions.entryPoints) { throw new Error('No entryPoints provided in build options.'); } @@ -130,7 +131,6 @@ function getSourceFiles( return resolvedFiles.filter((fileName) => { const relativeFileName = path.relative(baseDir, fileName); - console.log('Checking file:', relativeFileName); const matchesInclude = includePatterns.some((pattern) => pattern.test(relativeFileName) ); @@ -141,23 +141,92 @@ function getSourceFiles( }); } +function hasModifiers(node: ts.Node): node is ts.ClassElement | ts.MethodDeclaration { + return ( + ts.isClassElement(node) || + ts.isMethodDeclaration(node) || + ts.isPropertyDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + ); +} + +// Helper function to check if a declaration has public visibility +function isPublicDeclaration(node: ts.Node): boolean { + // Check if the node has modifiers and is of a type that may include them + if ("modifiers" in node && Array.isArray(node.modifiers)) { + return !node.modifiers.some( + (mod: ts.Modifier) => + mod.kind === ts.SyntaxKind.PrivateKeyword || + mod.kind === ts.SyntaxKind.ProtectedKeyword + ); + } + // If no modifiers exist, assume the node is public + return true; +} + +// Extract only public methods from the contract and add them to schemaData +function extractPublicMethods( + symbol: ts.Symbol, + checker: ts.TypeChecker, + schemaData: Record +) { + const type = checker.getDeclaredTypeOfSymbol(symbol); + const properties = checker.getPropertiesOfType(type); + + properties.forEach((prop) => { + const propType = checker.getTypeOfSymbolAtLocation( + prop, + prop.valueDeclaration || prop.declarations[0] + ); + + // Check if the property is a method and explicitly public + if (propType.getCallSignatures().length && prop.valueDeclaration) { + // Check if the declaration is public + const isPublic = isPublicDeclaration(prop.valueDeclaration); + + if (isPublic) { + const methodSchema = { + functionName: prop.getName(), + parameters: [] as { name: string; type: any }[], + returnType: serializeType( + propType.getCallSignatures()[0].getReturnType(), + checker + ), + }; + + // Get parameter types for the method + propType.getCallSignatures()[0].parameters.forEach((param) => { + const paramType = checker.getTypeOfSymbolAtLocation( + param, + param.valueDeclaration || param.declarations[0] + ); + methodSchema.parameters.push({ + name: param.getName(), + type: serializeType(paramType, checker), + }); + }); + + schemaData.methods.push(methodSchema); + console.log(`Extracted public method: ${methodSchema.functionName}`); + } + } + }); +} + function serializeType( type: ts.Type, checker: ts.TypeChecker, typeStack: ts.Type[] = [] ): any { const typeString = checker.typeToString(type); - const isPrimitive = ['string', 'number', 'boolean', 'null', 'undefined', 'any'].includes(typeString); if (typeStack.includes(type)) { - // Circular reference detected return { $ref: typeString }; } - // Add the current type to the stack const newTypeStack = [...typeStack, type]; - // Handle primitive types directly if (type.isStringLiteral()) { return { type: 'string', enum: [type.value] }; } @@ -187,23 +256,12 @@ function serializeType( const typeReference = type as ts.TypeReference; const typeArguments = checker.getTypeArguments(typeReference); const elementType = typeArguments[0] || checker.getAnyType(); - return { type: 'array', items: serializeType(elementType, checker, newTypeStack), }; } - if (checker.isTupleType(type)) { - const typeArguments = checker.getTypeArguments(type as ts.TypeReference); - return { - type: 'array', - items: typeArguments.map((elementType) => - serializeType(elementType, checker, newTypeStack) - ), - }; - } - if (type.isUnion()) { return { anyOf: type.types.map((subType) => @@ -220,11 +278,6 @@ function serializeType( }; } - if (type.getCallSignatures().length) { - // Function type - return { type: 'function' }; - } - const properties = checker.getPropertiesOfType(type); if (properties.length) { const result: Record = {}; @@ -242,7 +295,5 @@ function serializeType( return { type: 'object', properties: result }; } - // If none of the above, return any return { type: 'any' }; } - From f0dfd65300f56bf0d6eebb799ab50b851fc44bd0 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 15:06:20 +0400 Subject: [PATCH 08/20] update snapshots --- .../state-export/bundle.js.schema.json | 34 +++++++- .../__snapshots__/schemaMethods.test.ts.snap | 82 +++++++++++++++++++ .../__snapshots__/schemaState.test.ts.snap | 4 +- .../build/__tests__/schemaMethods.test.ts | 6 +- packages/build/__tests__/schemaState.test.ts | 2 +- 5 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap diff --git a/__output__/schema-data/state-export/bundle.js.schema.json b/__output__/schema-data/state-export/bundle.js.schema.json index 641ea3d..ff224e2 100644 --- a/__output__/schema-data/state-export/bundle.js.schema.json +++ b/__output__/schema-data/state-export/bundle.js.schema.json @@ -1,4 +1,36 @@ { - "state": {}, + "state": { + "type": "object", + "properties": { + "count": { + "type": "number" + }, + "startCoin": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + }, + "tokens": { + "type": "array", + "items": { + "type": "object", + "properties": { + "denom": { + "type": "string" + }, + "amount": { + "type": "string" + } + } + } + } + } + }, "methods": [] } \ No newline at end of file diff --git a/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap new file mode 100644 index 0000000..e27bc45 --- /dev/null +++ b/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap @@ -0,0 +1,82 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HyperwebBuild Schema Methods builds the fixture project successfully for public-methods 1`] = ` +{ + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "addToken", + "parameters": [ + { + "name": "denom", + "type": { + "type": "string", + }, + }, + { + "name": "amount", + "type": { + "type": "string", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "removeToken", + "parameters": [ + { + "name": "index", + "type": { + "type": "number", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + ], + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; diff --git a/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap index 889463a..3ea89bd 100644 --- a/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap +++ b/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap @@ -1,8 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`HyperwebBuild builds the fixture project successfully 1`] = ` +exports[`HyperwebBuild Schema State builds the fixture project successfully 1`] = ` { - "methods": {}, + "methods": [], "state": { "properties": { "count": { diff --git a/packages/build/__tests__/schemaMethods.test.ts b/packages/build/__tests__/schemaMethods.test.ts index 710d443..ab3e4f5 100644 --- a/packages/build/__tests__/schemaMethods.test.ts +++ b/packages/build/__tests__/schemaMethods.test.ts @@ -6,8 +6,8 @@ import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../s const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'public-methods')); const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'public-methods')); -describe('HyperwebBuild', () => { - it('builds the fixture project successfully', async () => { +describe('HyperwebBuild Schema Methods', () => { + it('builds the fixture project successfully for public-methods', async () => { const outfile = join(outputDir, 'bundle.js'); const schemaOutputPath = `${outfile}.schema.json`; @@ -51,8 +51,6 @@ describe('HyperwebBuild', () => { // Perform checks on schema structure (example: check if 'methods' exists and has properties) expect(schemaData).toHaveProperty('methods'); - expect(schemaData.methods).toHaveProperty('type', 'object'); - expect(schemaData.methods).toHaveProperty('properties'); expect(schemaData).toMatchSnapshot(); }); diff --git a/packages/build/__tests__/schemaState.test.ts b/packages/build/__tests__/schemaState.test.ts index 68003bf..f96f1ba 100644 --- a/packages/build/__tests__/schemaState.test.ts +++ b/packages/build/__tests__/schemaState.test.ts @@ -6,7 +6,7 @@ import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../s const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'state-export')); const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'state-export')); -describe('HyperwebBuild', () => { +describe('HyperwebBuild Schema State', () => { it('builds the fixture project successfully', async () => { const outfile = join(outputDir, 'bundle.js'); const schemaOutputPath = `${outfile}.schema.json`; From 375ac56d5f89e23f984dbc2230e925dfcb354db5 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 16:22:55 +0400 Subject: [PATCH 09/20] update the test cases to use a single test file --- .../bundle.js => public-methods.bundle.js} | 2 +- ...le.js.map => public-methods.bundle.js.map} | 2 +- ...schema.json => public-methods.schema.json} | 0 __output__/schema-data/state-export.bundle.js | 1 + ...ndle.js.map => state-export.bundle.js.map} | 0 ...s.schema.json => state-export.schema.json} | 0 __output__/schema-data/state-export/bundle.js | 1 - .../schemaExtractor.test.ts.snap | 121 ++++++++++++++++++ .../build/__tests__/schemaExtractor.test.ts | 58 +++++++++ .../build/__tests__/schemaMethods.test.ts | 57 --------- packages/build/__tests__/schemaState.test.ts | 54 -------- 11 files changed, 182 insertions(+), 114 deletions(-) rename __output__/schema-data/{public-methods/bundle.js => public-methods.bundle.js} (92%) rename __output__/schema-data/{public-methods/bundle.js.map => public-methods.bundle.js.map} (89%) rename __output__/schema-data/{public-methods/bundle.js.schema.json => public-methods.schema.json} (100%) create mode 100644 __output__/schema-data/state-export.bundle.js rename __output__/schema-data/{state-export/bundle.js.map => state-export.bundle.js.map} (100%) rename __output__/schema-data/{state-export/bundle.js.schema.json => state-export.schema.json} (100%) delete mode 100644 __output__/schema-data/state-export/bundle.js create mode 100644 packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap create mode 100644 packages/build/__tests__/schemaExtractor.test.ts delete mode 100644 packages/build/__tests__/schemaMethods.test.ts delete mode 100644 packages/build/__tests__/schemaState.test.ts diff --git a/__output__/schema-data/public-methods/bundle.js b/__output__/schema-data/public-methods.bundle.js similarity index 92% rename from __output__/schema-data/public-methods/bundle.js rename to __output__/schema-data/public-methods.bundle.js index 40b5d5e..cb323fe 100644 --- a/__output__/schema-data/public-methods/bundle.js +++ b/__output__/schema-data/public-methods.bundle.js @@ -30,4 +30,4 @@ var public_methods_default = MyContract; export { public_methods_default as default }; -//# sourceMappingURL=bundle.js.map +//# sourceMappingURL=public-methods.bundle.js.map diff --git a/__output__/schema-data/public-methods/bundle.js.map b/__output__/schema-data/public-methods.bundle.js.map similarity index 89% rename from __output__/schema-data/public-methods/bundle.js.map rename to __output__/schema-data/public-methods.bundle.js.map index d4e318a..6f48365 100644 --- a/__output__/schema-data/public-methods/bundle.js.map +++ b/__output__/schema-data/public-methods.bundle.js.map @@ -1,6 +1,6 @@ { "version": 3, - "sources": ["../../../__fixtures__/schema-data/public-methods/contract.ts", "../../../__fixtures__/schema-data/public-methods/index.ts"], + "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"], "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/bundle.js.schema.json b/__output__/schema-data/public-methods.schema.json similarity index 100% rename from __output__/schema-data/public-methods/bundle.js.schema.json rename to __output__/schema-data/public-methods.schema.json diff --git a/__output__/schema-data/state-export.bundle.js b/__output__/schema-data/state-export.bundle.js new file mode 100644 index 0000000..828ebcc --- /dev/null +++ b/__output__/schema-data/state-export.bundle.js @@ -0,0 +1 @@ +//# sourceMappingURL=state-export.bundle.js.map diff --git a/__output__/schema-data/state-export/bundle.js.map b/__output__/schema-data/state-export.bundle.js.map similarity index 100% rename from __output__/schema-data/state-export/bundle.js.map rename to __output__/schema-data/state-export.bundle.js.map diff --git a/__output__/schema-data/state-export/bundle.js.schema.json b/__output__/schema-data/state-export.schema.json similarity index 100% rename from __output__/schema-data/state-export/bundle.js.schema.json rename to __output__/schema-data/state-export.schema.json diff --git a/__output__/schema-data/state-export/bundle.js b/__output__/schema-data/state-export/bundle.js deleted file mode 100644 index 9f95631..0000000 --- a/__output__/schema-data/state-export/bundle.js +++ /dev/null @@ -1 +0,0 @@ -//# sourceMappingURL=bundle.js.map diff --git a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap new file mode 100644 index 0000000..dcc7291 --- /dev/null +++ b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`schemaExtractorPlugin should extract a basic contract with public and private methods 1`] = ` +{ + "methods": [], + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; + +exports[`schemaExtractorPlugin should extract methods from classes public methods 1`] = ` +{ + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "addToken", + "parameters": [ + { + "name": "denom", + "type": { + "type": "string", + }, + }, + { + "name": "amount", + "type": { + "type": "string", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "removeToken", + "parameters": [ + { + "name": "index", + "type": { + "type": "number", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + ], + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; diff --git a/packages/build/__tests__/schemaExtractor.test.ts b/packages/build/__tests__/schemaExtractor.test.ts new file mode 100644 index 0000000..e05ac73 --- /dev/null +++ b/packages/build/__tests__/schemaExtractor.test.ts @@ -0,0 +1,58 @@ +import fs from 'fs/promises'; +import { join, resolve } from 'path'; +import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; + +const outputDir = resolve(join(__dirname, '/../../../__output__/schema-data')); + +const runTest = async (fixtureName: string) => { + const fixtureDir = resolve(join(__dirname, `/../../../__fixtures__/schema-data/${fixtureName}`)); + const schemaOutputPath = join(outputDir, `${fixtureName}.schema.json`); + + const buildOptions: Partial = { + entryPoints: [join(fixtureDir, 'index.ts')], + outfile: join(outputDir, `${fixtureName}.bundle.js`), + customPlugins: [ + schemaExtractorPlugin({ + outputPath: schemaOutputPath, + baseDir: fixtureDir, + include: [/\.ts$/], + exclude: [/node_modules/, /\.test\.ts$/], + }), + ], + }; + + await HyperwebBuild.build(buildOptions); + const schemaContent = await fs.readFile(schemaOutputPath, 'utf-8'); + return JSON.parse(schemaContent); +}; + +describe('schemaExtractorPlugin', () => { + it('should extract a basic contract with public and private methods', async () => { + const schemaData = await runTest('state-export'); + + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + + expect(schemaData).toHaveProperty('methods'); + expect(schemaData.methods).toEqual([]); + + expect(schemaData).toMatchSnapshot(); + }); + + it('should extract methods from classes public methods', async () => { + const schemaData = await runTest('public-methods'); + + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + + const methodNames = schemaData.methods.map((method: any) => method.functionName); + expect(methodNames).toContain('addToken'); + expect(methodNames).toContain('increment'); + expect(methodNames).toContain('removeToken'); + expect(methodNames).not.toContain('reset'); + + expect(schemaData).toMatchSnapshot(); + }); +}); diff --git a/packages/build/__tests__/schemaMethods.test.ts b/packages/build/__tests__/schemaMethods.test.ts deleted file mode 100644 index ab3e4f5..0000000 --- a/packages/build/__tests__/schemaMethods.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import fs from 'fs/promises'; -import { join, resolve } from 'path'; - -import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; - -const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'public-methods')); -const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'public-methods')); - -describe('HyperwebBuild Schema Methods', () => { - it('builds the fixture project successfully for public-methods', async () => { - const outfile = join(outputDir, 'bundle.js'); - const schemaOutputPath = `${outfile}.schema.json`; - - const options: Partial = { - entryPoints: [join(fixtureDir, 'index.ts')], - outfile, - customPlugins: [ - schemaExtractorPlugin({ - outputPath: schemaOutputPath, - baseDir: fixtureDir, - 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); - - // Check if schema file exists - const schemafileExists = await fs.access(schemaOutputPath) - .then(() => true) - .catch(() => false); - - expect(schemafileExists).toBe(true); - - // Optionally, read the schema file and verify the contents - const schemaContent = await fs.readFile(schemaOutputPath, 'utf-8'); - const schemaData = JSON.parse(schemaContent); - - // Perform checks on schema structure (example: check if 'state' exists and has properties) - expect(schemaData).toHaveProperty('state'); - expect(schemaData.state).toHaveProperty('type', 'object'); - expect(schemaData.state).toHaveProperty('properties'); - - // Perform checks on schema structure (example: check if 'methods' exists and has properties) - expect(schemaData).toHaveProperty('methods'); - - expect(schemaData).toMatchSnapshot(); - }); -}); diff --git a/packages/build/__tests__/schemaState.test.ts b/packages/build/__tests__/schemaState.test.ts deleted file mode 100644 index f96f1ba..0000000 --- a/packages/build/__tests__/schemaState.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import fs from 'fs/promises'; -import { join, resolve } from 'path'; - -import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; - -const fixtureDir = resolve(join(__dirname, '/../../../__fixtures__/', 'schema-data', 'state-export')); -const outputDir = resolve(join(__dirname, '/../../../__output__/', 'schema-data', 'state-export')); - -describe('HyperwebBuild Schema State', () => { - it('builds the fixture project successfully', async () => { - const outfile = join(outputDir, 'bundle.js'); - const schemaOutputPath = `${outfile}.schema.json`; - - const options: Partial = { - entryPoints: [join(fixtureDir, 'index.ts')], - outfile, - customPlugins: [ - schemaExtractorPlugin({ - outputPath: schemaOutputPath, - baseDir: fixtureDir, - 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); - - // Check if schema file exists - const schemafileExists = await fs.access(schemaOutputPath) - .then(() => true) - .catch(() => false); - - expect(schemafileExists).toBe(true); - - // Optionally, read the schema file and verify the contents - const schemaContent = await fs.readFile(schemaOutputPath, 'utf-8'); - const schemaData = JSON.parse(schemaContent); - - // Perform checks on schema structure (example: check if 'state' exists and has properties) - expect(schemaData).toHaveProperty('state'); - expect(schemaData.state).toHaveProperty('type', 'object'); - expect(schemaData.state).toHaveProperty('properties'); - - expect(schemaData).toMatchSnapshot(); - }); -}); From b35c97f49b46bd3f032d555e5e2ad5be811f0b9d Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Mon, 11 Nov 2024 16:25:19 +0400 Subject: [PATCH 10/20] run lint fixes --- packages/build/__tests__/schemaExtractor.test.ts | 1 + packages/build/src/schemaExtractor.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/build/__tests__/schemaExtractor.test.ts b/packages/build/__tests__/schemaExtractor.test.ts index e05ac73..8242d1a 100644 --- a/packages/build/__tests__/schemaExtractor.test.ts +++ b/packages/build/__tests__/schemaExtractor.test.ts @@ -1,5 +1,6 @@ import fs from 'fs/promises'; import { join, resolve } from 'path'; + import { HyperwebBuild, HyperwebBuildOptions, schemaExtractorPlugin } from '../src'; const outputDir = resolve(join(__dirname, '/../../../__output__/schema-data')); diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index f23e49f..bd9a064 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -1,7 +1,8 @@ -import * as ts from 'typescript'; +import { Plugin } from 'esbuild'; import { promises as fs } from 'fs'; import * as path from 'path'; -import { Plugin } from 'esbuild'; +import * as ts from 'typescript'; + import { HyperwebBuildOptions } from './build'; interface SchemaExtractorOptions { @@ -154,7 +155,7 @@ function hasModifiers(node: ts.Node): node is ts.ClassElement | ts.MethodDeclara // Helper function to check if a declaration has public visibility function isPublicDeclaration(node: ts.Node): boolean { // Check if the node has modifiers and is of a type that may include them - if ("modifiers" in node && Array.isArray(node.modifiers)) { + if ('modifiers' in node && Array.isArray(node.modifiers)) { return !node.modifiers.some( (mod: ts.Modifier) => mod.kind === ts.SyntaxKind.PrivateKeyword || From a75926101f42b67730e25fd0d6dcb6ac052ae26c Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 09:55:14 +0400 Subject: [PATCH 11/20] delete unused snaps --- .../schemaExtractor.test.ts.snap | 121 ------------------ .../__snapshots__/schemaMethods.test.ts.snap | 82 ------------ 2 files changed, 203 deletions(-) delete mode 100644 packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap delete mode 100644 packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap diff --git a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap deleted file mode 100644 index dcc7291..0000000 --- a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap +++ /dev/null @@ -1,121 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`schemaExtractorPlugin should extract a basic contract with public and private methods 1`] = ` -{ - "methods": [], - "state": { - "properties": { - "count": { - "type": "number", - }, - "startCoin": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "tokens": { - "items": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "type": "array", - }, - }, - "type": "object", - }, -} -`; - -exports[`schemaExtractorPlugin should extract methods from classes public methods 1`] = ` -{ - "methods": [ - { - "functionName": "increment", - "parameters": [], - "returnType": { - "type": "any", - }, - }, - { - "functionName": "addToken", - "parameters": [ - { - "name": "denom", - "type": { - "type": "string", - }, - }, - { - "name": "amount", - "type": { - "type": "string", - }, - }, - ], - "returnType": { - "type": "any", - }, - }, - { - "functionName": "removeToken", - "parameters": [ - { - "name": "index", - "type": { - "type": "number", - }, - }, - ], - "returnType": { - "type": "any", - }, - }, - ], - "state": { - "properties": { - "count": { - "type": "number", - }, - "startCoin": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "tokens": { - "items": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "type": "array", - }, - }, - "type": "object", - }, -} -`; diff --git a/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap deleted file mode 100644 index e27bc45..0000000 --- a/packages/build/__tests__/__snapshots__/schemaMethods.test.ts.snap +++ /dev/null @@ -1,82 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HyperwebBuild Schema Methods builds the fixture project successfully for public-methods 1`] = ` -{ - "methods": [ - { - "functionName": "increment", - "parameters": [], - "returnType": { - "type": "any", - }, - }, - { - "functionName": "addToken", - "parameters": [ - { - "name": "denom", - "type": { - "type": "string", - }, - }, - { - "name": "amount", - "type": { - "type": "string", - }, - }, - ], - "returnType": { - "type": "any", - }, - }, - { - "functionName": "removeToken", - "parameters": [ - { - "name": "index", - "type": { - "type": "number", - }, - }, - ], - "returnType": { - "type": "any", - }, - }, - ], - "state": { - "properties": { - "count": { - "type": "number", - }, - "startCoin": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "tokens": { - "items": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "type": "array", - }, - }, - "type": "object", - }, -} -`; From 16f8da1779cf58c5a508700dd80b36713a4984cc Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 10:13:38 +0400 Subject: [PATCH 12/20] add proper snapshot files --- .../schemaExtractor.test.ts.snap | 121 ++++++++++++++++++ .../__snapshots__/schemaState.test.ts.snap | 40 ------ 2 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap delete mode 100644 packages/build/__tests__/__snapshots__/schemaState.test.ts.snap diff --git a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap new file mode 100644 index 0000000..dcc7291 --- /dev/null +++ b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap @@ -0,0 +1,121 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`schemaExtractorPlugin should extract a basic contract with public and private methods 1`] = ` +{ + "methods": [], + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; + +exports[`schemaExtractorPlugin should extract methods from classes public methods 1`] = ` +{ + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "addToken", + "parameters": [ + { + "name": "denom", + "type": { + "type": "string", + }, + }, + { + "name": "amount", + "type": { + "type": "string", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "removeToken", + "parameters": [ + { + "name": "index", + "type": { + "type": "number", + }, + }, + ], + "returnType": { + "type": "any", + }, + }, + ], + "state": { + "properties": { + "count": { + "type": "number", + }, + "startCoin": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "tokens": { + "items": { + "properties": { + "amount": { + "type": "string", + }, + "denom": { + "type": "string", + }, + }, + "type": "object", + }, + "type": "array", + }, + }, + "type": "object", + }, +} +`; diff --git a/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap deleted file mode 100644 index 3ea89bd..0000000 --- a/packages/build/__tests__/__snapshots__/schemaState.test.ts.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HyperwebBuild Schema State builds the fixture project successfully 1`] = ` -{ - "methods": [], - "state": { - "properties": { - "count": { - "type": "number", - }, - "startCoin": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "tokens": { - "items": { - "properties": { - "amount": { - "type": "string", - }, - "denom": { - "type": "string", - }, - }, - "type": "object", - }, - "type": "array", - }, - }, - "type": "object", - }, -} -`; From 4107526f7b9c8ee852f774b95c68688a17e8147a Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 10:21:55 +0400 Subject: [PATCH 13/20] remove getSources, not required, typescript does it --- packages/build/src/schemaExtractor.ts | 67 +-------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index bd9a064..ab379a2 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -22,18 +22,7 @@ export const schemaExtractorPlugin = ( build.onEnd(async () => { const baseDir = pluginOptions.baseDir || process.cwd(); - const sourceFiles = getSourceFiles( - pluginOptions, - hyperwebBuildOptions, - baseDir - ); - - if (!sourceFiles.length) { - console.error( - 'No entry files provided or matched for schema extraction.' - ); - return; - } + const sourceFiles = hyperwebBuildOptions.entryPoints as string[]; const program = ts.createProgram(sourceFiles, { target: ts.ScriptTarget.ESNext, @@ -98,60 +87,6 @@ export const schemaExtractorPlugin = ( }, }); -function getSourceFiles( - pluginOptions: SchemaExtractorOptions, - hyperwebBuildOptions: HyperwebBuildOptions, - baseDir: string -): string[] { - const includePatterns = pluginOptions.include || [/\.tsx?$/]; - const excludePatterns = pluginOptions.exclude || [/node_modules/]; - - if (!hyperwebBuildOptions.entryPoints) { - throw new Error('No entryPoints provided in build options.'); - } - - let resolvedFiles: string[] = []; - - if (Array.isArray(hyperwebBuildOptions.entryPoints)) { - resolvedFiles = hyperwebBuildOptions.entryPoints.map((entry) => { - if (typeof entry === 'string') { - return path.resolve(baseDir, entry); - } else { - throw new Error('Invalid entryPoints array item: expected string'); - } - }); - } else if (typeof hyperwebBuildOptions.entryPoints === 'object') { - resolvedFiles = Object.values(hyperwebBuildOptions.entryPoints).map( - (file) => path.resolve(baseDir, file) - ); - } else { - throw new Error( - 'Invalid entryPoints format: expected string[] or Record' - ); - } - - return resolvedFiles.filter((fileName) => { - const relativeFileName = path.relative(baseDir, fileName); - const matchesInclude = includePatterns.some((pattern) => - pattern.test(relativeFileName) - ); - const matchesExclude = excludePatterns.some((pattern) => - pattern.test(relativeFileName) - ); - return matchesInclude && !matchesExclude; - }); -} - -function hasModifiers(node: ts.Node): node is ts.ClassElement | ts.MethodDeclaration { - return ( - ts.isClassElement(node) || - ts.isMethodDeclaration(node) || - ts.isPropertyDeclaration(node) || - ts.isGetAccessorDeclaration(node) || - ts.isSetAccessorDeclaration(node) - ); -} - // Helper function to check if a declaration has public visibility function isPublicDeclaration(node: ts.Node): boolean { // Check if the node has modifiers and is of a type that may include them From 71f5d1bf339dcfa10bddf1e2542b445a0c839c98 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 10:29:44 +0400 Subject: [PATCH 14/20] add type to hyperwebBuildOptions extracted from build.initialOptions --- packages/build/src/schemaExtractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index ab379a2..3e23f13 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -18,7 +18,7 @@ export const schemaExtractorPlugin = ( name: 'schema-extractor', setup(build) { - const hyperwebBuildOptions = build.initialOptions; + const hyperwebBuildOptions: HyperwebBuildOptions = build.initialOptions; build.onEnd(async () => { const baseDir = pluginOptions.baseDir || process.cwd(); From e310b1c5fbeee4cfcf688a79436df6aee5a22024 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 13:06:00 +0400 Subject: [PATCH 15/20] add test case for inheritance contract --- .../inheritance-contract/contract.ts | 19 ++++++++++++ .../schema-data/inheritance-contract/index.ts | 4 +++ .../schema-data/inheritance-contract/state.ts | 3 ++ .../inheritance-contract.bundle.js | 22 ++++++++++++++ .../inheritance-contract.bundle.js.map | 7 +++++ .../inheritance-contract.schema.json | 26 +++++++++++++++++ .../schemaExtractor.test.ts.snap | 29 +++++++++++++++++++ .../build/__tests__/schemaExtractor.test.ts | 15 ++++++++++ 8 files changed, 125 insertions(+) create mode 100644 __fixtures__/schema-data/inheritance-contract/contract.ts create mode 100644 __fixtures__/schema-data/inheritance-contract/index.ts create mode 100644 __fixtures__/schema-data/inheritance-contract/state.ts create mode 100644 __output__/schema-data/inheritance-contract.bundle.js create mode 100644 __output__/schema-data/inheritance-contract.bundle.js.map create mode 100644 __output__/schema-data/inheritance-contract.schema.json diff --git a/__fixtures__/schema-data/inheritance-contract/contract.ts b/__fixtures__/schema-data/inheritance-contract/contract.ts new file mode 100644 index 0000000..8af4aea --- /dev/null +++ b/__fixtures__/schema-data/inheritance-contract/contract.ts @@ -0,0 +1,19 @@ +import { State } from './state'; + +class BaseContract { + protected state: State = { count: 0 }; + + public baseMethod() { + console.log('baseMethod'); + } +} + +export class InheritedContract extends BaseContract { + public increment() { + this.state.count += 1; + } + + private reset() { + this.state.count = 0; + } +} diff --git a/__fixtures__/schema-data/inheritance-contract/index.ts b/__fixtures__/schema-data/inheritance-contract/index.ts new file mode 100644 index 0000000..8abf4b5 --- /dev/null +++ b/__fixtures__/schema-data/inheritance-contract/index.ts @@ -0,0 +1,4 @@ +import { InheritedContract } from './contract'; +export { State } from './state'; + +export default InheritedContract; diff --git a/__fixtures__/schema-data/inheritance-contract/state.ts b/__fixtures__/schema-data/inheritance-contract/state.ts new file mode 100644 index 0000000..8766015 --- /dev/null +++ b/__fixtures__/schema-data/inheritance-contract/state.ts @@ -0,0 +1,3 @@ +export interface State { + count: number; +} diff --git a/__output__/schema-data/inheritance-contract.bundle.js b/__output__/schema-data/inheritance-contract.bundle.js new file mode 100644 index 0000000..2e24623 --- /dev/null +++ b/__output__/schema-data/inheritance-contract.bundle.js @@ -0,0 +1,22 @@ +// ../../__fixtures__/schema-data/inheritance-contract/contract.ts +var BaseContract = class { + state = { count: 0 }; + baseMethod() { + console.log("baseMethod"); + } +}; +var InheritedContract = class extends BaseContract { + increment() { + this.state.count += 1; + } + reset() { + this.state.count = 0; + } +}; + +// ../../__fixtures__/schema-data/inheritance-contract/index.ts +var inheritance_contract_default = InheritedContract; +export { + inheritance_contract_default as default +}; +//# sourceMappingURL=inheritance-contract.bundle.js.map diff --git a/__output__/schema-data/inheritance-contract.bundle.js.map b/__output__/schema-data/inheritance-contract.bundle.js.map new file mode 100644 index 0000000..f1e4921 --- /dev/null +++ b/__output__/schema-data/inheritance-contract.bundle.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../../__fixtures__/schema-data/inheritance-contract/contract.ts", "../../__fixtures__/schema-data/inheritance-contract/index.ts"], + "sourcesContent": ["import { State } from './state';\n\nclass BaseContract {\n protected state: State = { count: 0 };\n\n public baseMethod() {\n console.log('baseMethod');\n }\n}\n\nexport class InheritedContract extends BaseContract {\n public increment() {\n this.state.count += 1;\n }\n\n private reset() {\n this.state.count = 0;\n }\n}\n", "import { InheritedContract } from './contract';\nexport { State } from './state';\n\nexport default InheritedContract;\n"], + "mappings": ";AAEA,IAAM,eAAN,MAAmB;AAAA,EACP,QAAe,EAAE,OAAO,EAAE;AAAA,EAE7B,aAAa;AAClB,YAAQ,IAAI,YAAY;AAAA,EAC1B;AACF;AAEO,IAAM,oBAAN,cAAgC,aAAa;AAAA,EAC3C,YAAY;AACjB,SAAK,MAAM,SAAS;AAAA,EACtB;AAAA,EAEQ,QAAQ;AACd,SAAK,MAAM,QAAQ;AAAA,EACrB;AACF;;;ACfA,IAAO,+BAAQ;", + "names": [] +} diff --git a/__output__/schema-data/inheritance-contract.schema.json b/__output__/schema-data/inheritance-contract.schema.json new file mode 100644 index 0000000..4f053dd --- /dev/null +++ b/__output__/schema-data/inheritance-contract.schema.json @@ -0,0 +1,26 @@ +{ + "state": { + "type": "object", + "properties": { + "count": { + "type": "number" + } + } + }, + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any" + } + }, + { + "functionName": "baseMethod", + "parameters": [], + "returnType": { + "type": "any" + } + } + ] +} \ 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 dcc7291..a6f6e80 100644 --- a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap +++ b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap @@ -39,6 +39,35 @@ exports[`schemaExtractorPlugin should extract a basic contract with public and p } `; +exports[`schemaExtractorPlugin should extract methods and state from classes inheritance-contract 1`] = ` +{ + "methods": [ + { + "functionName": "increment", + "parameters": [], + "returnType": { + "type": "any", + }, + }, + { + "functionName": "baseMethod", + "parameters": [], + "returnType": { + "type": "any", + }, + }, + ], + "state": { + "properties": { + "count": { + "type": "number", + }, + }, + "type": "object", + }, +} +`; + exports[`schemaExtractorPlugin should extract methods from classes public methods 1`] = ` { "methods": [ diff --git a/packages/build/__tests__/schemaExtractor.test.ts b/packages/build/__tests__/schemaExtractor.test.ts index 8242d1a..d07b656 100644 --- a/packages/build/__tests__/schemaExtractor.test.ts +++ b/packages/build/__tests__/schemaExtractor.test.ts @@ -56,4 +56,19 @@ describe('schemaExtractorPlugin', () => { expect(schemaData).toMatchSnapshot(); }); + + it('should extract methods and state from classes inheritance-contract', async () => { + const schemaData = await runTest('inheritance-contract'); + + expect(schemaData).toHaveProperty('state'); + expect(schemaData.state).toHaveProperty('type', 'object'); + expect(schemaData.state).toHaveProperty('properties'); + + const methodNames = schemaData.methods.map((method: any) => method.functionName); + expect(methodNames).toContain('baseMethod'); + expect(methodNames).toContain('increment'); + expect(methodNames).not.toContain('reset'); + + expect(schemaData).toMatchSnapshot(); + }); }); From 5ac73fbad2262bb6ae35bd91d07efc992fe26459 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 13:08:55 +0400 Subject: [PATCH 16/20] fix function name in methods schema --- .../schema-data/inheritance-contract.schema.json | 4 ++-- __output__/schema-data/public-methods.schema.json | 6 +++--- .../__snapshots__/schemaExtractor.test.ts.snap | 10 +++++----- packages/build/__tests__/schemaExtractor.test.ts | 4 ++-- packages/build/src/schemaExtractor.ts | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/__output__/schema-data/inheritance-contract.schema.json b/__output__/schema-data/inheritance-contract.schema.json index 4f053dd..9a155bd 100644 --- a/__output__/schema-data/inheritance-contract.schema.json +++ b/__output__/schema-data/inheritance-contract.schema.json @@ -9,14 +9,14 @@ }, "methods": [ { - "functionName": "increment", + "name": "increment", "parameters": [], "returnType": { "type": "any" } }, { - "functionName": "baseMethod", + "name": "baseMethod", "parameters": [], "returnType": { "type": "any" diff --git a/__output__/schema-data/public-methods.schema.json b/__output__/schema-data/public-methods.schema.json index 6b53510..b226118 100644 --- a/__output__/schema-data/public-methods.schema.json +++ b/__output__/schema-data/public-methods.schema.json @@ -34,14 +34,14 @@ }, "methods": [ { - "functionName": "increment", + "name": "increment", "parameters": [], "returnType": { "type": "any" } }, { - "functionName": "addToken", + "name": "addToken", "parameters": [ { "name": "denom", @@ -61,7 +61,7 @@ } }, { - "functionName": "removeToken", + "name": "removeToken", "parameters": [ { "name": "index", diff --git a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap index a6f6e80..bc3c165 100644 --- a/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap +++ b/packages/build/__tests__/__snapshots__/schemaExtractor.test.ts.snap @@ -43,14 +43,14 @@ exports[`schemaExtractorPlugin should extract methods and state from classes inh { "methods": [ { - "functionName": "increment", + "name": "increment", "parameters": [], "returnType": { "type": "any", }, }, { - "functionName": "baseMethod", + "name": "baseMethod", "parameters": [], "returnType": { "type": "any", @@ -72,14 +72,14 @@ exports[`schemaExtractorPlugin should extract methods from classes public method { "methods": [ { - "functionName": "increment", + "name": "increment", "parameters": [], "returnType": { "type": "any", }, }, { - "functionName": "addToken", + "name": "addToken", "parameters": [ { "name": "denom", @@ -99,7 +99,7 @@ exports[`schemaExtractorPlugin should extract methods from classes public method }, }, { - "functionName": "removeToken", + "name": "removeToken", "parameters": [ { "name": "index", diff --git a/packages/build/__tests__/schemaExtractor.test.ts b/packages/build/__tests__/schemaExtractor.test.ts index d07b656..9fb446e 100644 --- a/packages/build/__tests__/schemaExtractor.test.ts +++ b/packages/build/__tests__/schemaExtractor.test.ts @@ -48,7 +48,7 @@ describe('schemaExtractorPlugin', () => { expect(schemaData.state).toHaveProperty('type', 'object'); expect(schemaData.state).toHaveProperty('properties'); - const methodNames = schemaData.methods.map((method: any) => method.functionName); + const methodNames = schemaData.methods.map((method: any) => method.name); expect(methodNames).toContain('addToken'); expect(methodNames).toContain('increment'); expect(methodNames).toContain('removeToken'); @@ -64,7 +64,7 @@ describe('schemaExtractorPlugin', () => { expect(schemaData.state).toHaveProperty('type', 'object'); expect(schemaData.state).toHaveProperty('properties'); - const methodNames = schemaData.methods.map((method: any) => method.functionName); + const methodNames = schemaData.methods.map((method: any) => method.name); expect(methodNames).toContain('baseMethod'); expect(methodNames).toContain('increment'); expect(methodNames).not.toContain('reset'); diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 3e23f13..449c59b 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -123,7 +123,7 @@ function extractPublicMethods( if (isPublic) { const methodSchema = { - functionName: prop.getName(), + name: prop.getName(), parameters: [] as { name: string; type: any }[], returnType: serializeType( propType.getCallSignatures()[0].getReturnType(), @@ -144,7 +144,7 @@ function extractPublicMethods( }); schemaData.methods.push(methodSchema); - console.log(`Extracted public method: ${methodSchema.functionName}`); + console.log(`Extracted public method: ${methodSchema.name}`); } } }); From ab98a888ecc239623da06af0b96f8e3cdc3be2de Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 13:36:07 +0400 Subject: [PATCH 17/20] refactor codebase for more functional support --- packages/build/src/schemaExtractor.ts | 76 ++++++++++++++++----------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 449c59b..665f5ef 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -33,40 +33,12 @@ export const schemaExtractorPlugin = ( const checker = program.getTypeChecker(); const schemaData: Record = { state: {}, methods: [] }; - // Extract state and methods from the contract's default export + // Extract state, methods, and decorators from the contract's default export program.getSourceFiles().forEach((sourceFile) => { if (sourceFile.isDeclarationFile) return; - const defaultExport = sourceFile.statements.find((stmt) => - ts.isExportAssignment(stmt) && !stmt.isExportEquals - ) as ts.ExportAssignment | undefined; - - if (defaultExport && defaultExport.expression) { - console.log(`Found default export in file: ${sourceFile.fileName}`); - - const contractSymbol = checker.getSymbolAtLocation(defaultExport.expression); - - if (contractSymbol) { - console.log(`Extracting methods from contract symbol: ${contractSymbol.getName()}`); - extractPublicMethods(contractSymbol, checker, schemaData); - } else { - console.warn(`No symbol found for default export in ${sourceFile.fileName}`); - } - } - - // Extract the State interface - const stateInterface = sourceFile.statements.find( - (stmt): stmt is ts.InterfaceDeclaration => - ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'State' - ); - - if (stateInterface) { - console.log("Extracting schema for 'State' interface"); - schemaData.state = serializeType( - checker.getTypeAtLocation(stateInterface), - checker - ); - } + extractDefaultExport(sourceFile, checker, schemaData); + extractStateInterface(sourceFile, checker, schemaData); }); const outputPath = @@ -101,6 +73,29 @@ function isPublicDeclaration(node: ts.Node): boolean { return true; } +function extractDefaultExport( + sourceFile: ts.SourceFile, + checker: ts.TypeChecker, + schemaData: Record +) { + const defaultExport = sourceFile.statements.find((stmt) => + ts.isExportAssignment(stmt) && !stmt.isExportEquals + ) as ts.ExportAssignment | undefined; + + if (defaultExport && defaultExport.expression) { + console.log(`Found default export in file: ${sourceFile.fileName}`); + + const contractSymbol = checker.getSymbolAtLocation(defaultExport.expression); + + if (contractSymbol) { + console.log(`Extracting methods from contract symbol: ${contractSymbol.getName()}`); + extractPublicMethods(contractSymbol, checker, schemaData); + } else { + console.warn(`No symbol found for default export in ${sourceFile.fileName}`); + } + } +} + // Extract only public methods from the contract and add them to schemaData function extractPublicMethods( symbol: ts.Symbol, @@ -150,6 +145,25 @@ function extractPublicMethods( }); } +function extractStateInterface( + sourceFile: ts.SourceFile, + checker: ts.TypeChecker, + schemaData: Record +) { + const stateInterface = sourceFile.statements.find( + (stmt): stmt is ts.InterfaceDeclaration => + ts.isInterfaceDeclaration(stmt) && stmt.name.text === 'State' + ); + + if (stateInterface) { + console.log("Extracting schema for 'State' interface"); + schemaData.state = serializeType( + checker.getTypeAtLocation(stateInterface), + checker + ); + } +} + function serializeType( type: ts.Type, checker: ts.TypeChecker, From c2d4f3f4f355b31ba630d8f428ef67230f45bb7d Mon Sep 17 00:00:00 2001 From: Anmol Date: Tue, 12 Nov 2024 15:35:29 +0530 Subject: [PATCH 18/20] Update packages/build/src/index.ts --- packages/build/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts index 5aaa13b..5484333 100644 --- a/packages/build/src/index.ts +++ b/packages/build/src/index.ts @@ -1,3 +1,3 @@ export type { HyperwebBuildOptions } from './build'; export { HyperwebBuild } from './build'; -export { schemaExtractorPlugin } from './schemaExtractor'; \ No newline at end of file +export { schemaExtractorPlugin } from './schemaExtractor'; From 101d6803a83edc6607be939f66cff70c8e122fb1 Mon Sep 17 00:00:00 2001 From: Anmol Date: Tue, 12 Nov 2024 15:35:40 +0530 Subject: [PATCH 19/20] Update __fixtures__/schema-data/public-methods/contract.ts --- __fixtures__/schema-data/public-methods/contract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__fixtures__/schema-data/public-methods/contract.ts b/__fixtures__/schema-data/public-methods/contract.ts index 7f23674..12c9085 100644 --- a/__fixtures__/schema-data/public-methods/contract.ts +++ b/__fixtures__/schema-data/public-methods/contract.ts @@ -29,4 +29,4 @@ export class MyContract { public removeToken(index: number) { this.state.tokens.splice(index, 1); } -} \ No newline at end of file +} From 077f9a5a6e9a894ba32ee5e6d302d014a74c0803 Mon Sep 17 00:00:00 2001 From: Anmol1696 Date: Tue, 12 Nov 2024 14:07:42 +0400 Subject: [PATCH 20/20] remove unused comment for decorator, will be added later --- packages/build/src/schemaExtractor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/build/src/schemaExtractor.ts b/packages/build/src/schemaExtractor.ts index 665f5ef..044201f 100644 --- a/packages/build/src/schemaExtractor.ts +++ b/packages/build/src/schemaExtractor.ts @@ -33,7 +33,7 @@ export const schemaExtractorPlugin = ( const checker = program.getTypeChecker(); const schemaData: Record = { state: {}, methods: [] }; - // Extract state, methods, and decorators from the contract's default export + // Extract state and methods from the contract's default export program.getSourceFiles().forEach((sourceFile) => { if (sourceFile.isDeclarationFile) return;