shokupan 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer-BqIe1p0R.js +35 -0
- package/dist/analyzer-BqIe1p0R.js.map +1 -0
- package/dist/analyzer-CKLGLFtx.cjs +35 -0
- package/dist/analyzer-CKLGLFtx.cjs.map +1 -0
- package/dist/{analyzer-Ce_7JxZh.js → analyzer.impl-CV6W1Eq7.js} +238 -21
- package/dist/analyzer.impl-CV6W1Eq7.js.map +1 -0
- package/dist/{analyzer-Bei1sVWp.cjs → analyzer.impl-D9Yi1Hax.cjs} +237 -20
- package/dist/analyzer.impl-D9Yi1Hax.cjs.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.js +1 -1
- package/dist/context.d.ts +19 -7
- package/dist/http-server-BEMPIs33.cjs.map +1 -1
- package/dist/http-server-CCeagTyU.js.map +1 -1
- package/dist/index.cjs +1500 -275
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1482 -256
- package/dist/index.js.map +1 -1
- package/dist/plugins/application/api-explorer/plugin.d.ts +9 -0
- package/dist/plugins/application/api-explorer/static/explorer-client.mjs +880 -0
- package/dist/plugins/application/api-explorer/static/style.css +767 -0
- package/dist/plugins/application/api-explorer/static/theme.css +128 -0
- package/dist/plugins/application/asyncapi/generator.d.ts +3 -0
- package/dist/plugins/application/asyncapi/plugin.d.ts +15 -0
- package/dist/plugins/application/asyncapi/static/asyncapi-client.mjs +748 -0
- package/dist/plugins/application/asyncapi/static/style.css +565 -0
- package/dist/plugins/application/asyncapi/static/theme.css +128 -0
- package/dist/plugins/application/auth.d.ts +3 -1
- package/dist/plugins/application/dashboard/metrics-collector.d.ts +3 -1
- package/dist/plugins/application/dashboard/plugin.d.ts +13 -3
- package/dist/plugins/application/dashboard/static/registry.css +0 -53
- package/dist/plugins/application/dashboard/static/styles.css +29 -20
- package/dist/plugins/application/dashboard/static/tabulator.css +83 -31
- package/dist/plugins/application/dashboard/static/theme.css +128 -0
- package/dist/plugins/application/graphql-apollo.d.ts +33 -0
- package/dist/plugins/application/graphql-yoga.d.ts +25 -0
- package/dist/plugins/application/openapi/analyzer.d.ts +12 -119
- package/dist/plugins/application/openapi/analyzer.impl.d.ts +167 -0
- package/dist/plugins/application/scalar.d.ts +9 -2
- package/dist/router.d.ts +80 -51
- package/dist/shokupan.d.ts +14 -8
- package/dist/util/datastore.d.ts +71 -7
- package/dist/util/decorators.d.ts +2 -2
- package/dist/util/types.d.ts +96 -3
- package/package.json +32 -12
- package/dist/analyzer-Bei1sVWp.cjs.map +0 -1
- package/dist/analyzer-Ce_7JxZh.js.map +0 -1
- package/dist/plugins/application/dashboard/static/scrollbar.css +0 -24
- package/dist/plugins/application/dashboard/template.eta +0 -246
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyzer.impl-CV6W1Eq7.js","sources":["../src/plugins/application/openapi/analyzer.impl.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport ts from 'typescript';\n\n/**\n * File information collected during scan\n */\ninterface CollectedFile {\n path: string;\n type: 'ts' | 'tsx' | 'cts' | 'dts' | 'mts' | 'js' | 'jsx' | 'mjs' | 'cjs' | 'map';\n content?: string;\n}\n\n/**\n * Route information extracted from AST\n */\nexport interface RouteInfo {\n method: string;\n path: string;\n handlerName?: string;\n handlerSource?: string;\n requestTypes?: {\n body?: any;\n query?: Record<string, string>;\n params?: Record<string, string>;\n headers?: Record<string, string>;\n };\n responseType?: string;\n responseSchema?: any;\n summary?: string;\n description?: string;\n tags?: string[];\n operationId?: string;\n emits?: { event: string; payload?: any; location?: { startLine: number; endLine: number; }; }[];\n sourceContext?: {\n file: string;\n startLine: number;\n endLine: number;\n highlights?: { startLine: number; endLine: number; type: 'emit' | 'return-success' | 'return-warning'; }[];\n };\n}\n\n/**\n * Dependency information\n */\ninterface DependencyInfo {\n packageName: string;\n version?: string;\n importPath: string;\n isExternal: boolean;\n}\n\n/**\n * Application/Router instance found in code\n */\nexport interface ApplicationInstance {\n name: string;\n filePath: string;\n className: 'Shokupan' | 'ShokupanRouter' | 'Controller';\n controllerPrefix?: string;\n routes: RouteInfo[];\n mounted: MountInfo[];\n}\n\ninterface MountInfo {\n prefix: string;\n target: string; // Controller/Router name or file path\n targetFilePath?: string;\n dependency?: DependencyInfo;\n sourceContext?: {\n file: string;\n startLine: number;\n endLine: number;\n };\n}\n\n/**\n * Main analyzer class\n */\nexport class OpenAPIAnalyzer {\n private files: CollectedFile[] = [];\n private applications: ApplicationInstance[] = [];\n private program?: ts.Program;\n\n private entrypoint?: string;\n\n constructor(private rootDir: string, entrypoint?: string) {\n if (entrypoint) {\n this.entrypoint = path.resolve(entrypoint);\n }\n }\n\n /**\n * Main analysis entry point\n */\n /**\n * Main analysis entry point\n */\n public async analyze(): Promise<{ applications: ApplicationInstance[]; }> {\n // console.log(`Analyzing directory: ${this.rootDir}`);\n\n // Step 1: Parse TypeScript files (which might involve scanning or using entrypoint)\n await this.parseTypeScriptFiles();\n\n // Step 2: Process source maps if needed\n await this.processSourceMaps();\n\n // Step 3: Find Shokupan applications\n await this.findApplications();\n\n // Step 4: Extract route information\n await this.extractRoutes();\n\n // Step 5: Prune unreachable GenericModules\n this.pruneApplications();\n\n // Return the raw application data for further processing\n return { applications: this.applications };\n }\n\n /**\n * Remove GenericModules that are not mounted by any Shokupan application/router\n */\n private pruneApplications(): void {\n const reachable = new Set<string>();\n const queue: ApplicationInstance[] = [];\n\n // Seed with explicit applications (Shokupan, ShokupanRouter, Controller)\n for (const app of this.applications) {\n if (app.name !== 'GenericModule') {\n reachable.add(app.filePath);\n queue.push(app);\n }\n }\n\n // BFS to find all reachable modules via mounts\n while (queue.length > 0) {\n const app = queue.shift()!;\n\n for (const mount of app.mounted) {\n if (mount.targetFilePath && !reachable.has(mount.targetFilePath)) {\n reachable.add(mount.targetFilePath);\n\n // Find the app instance for this file\n const mountedApp = this.applications.find(a => a.filePath === mount.targetFilePath);\n if (mountedApp) {\n queue.push(mountedApp);\n }\n }\n }\n }\n\n // Filter out unreachable GenericModules\n const initialCount = this.applications.length;\n this.applications = this.applications.filter(app => {\n if (app.name === 'GenericModule' && !reachable.has(app.filePath)) {\n // console.log(`[Analyzer] Pruning unreachable module: ${app.filePath}`);\n return false;\n }\n return true;\n });\n\n // console.log(`[Analyzer] Pruned ${initialCount - this.applications.length} unreachable modules.`);\n }\n\n /**\n * Recursively scan directory for TypeScript/JavaScript files\n */\n private async scanDirectory(dir: string): Promise<void> {\n try {\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const fullPath = path.join(dir, entry.name);\n\n // Skip node_modules for source files (we'll handle deps separately)\n if (entry.isDirectory()) {\n if ([\"node_modules\", \".git\", \"dist\"].includes(entry.name)) {\n continue;\n }\n await this.scanDirectory(fullPath);\n } else {\n const ext = path.extname(entry.name);\n if ([\".ts\", \".tsx\", \".cts\", \".dts\", \".mts\", \".js\", \".jsx\", \".mjs\", \".cjs\", \".map\"].includes(ext)) {\n this.files.push({ path: fullPath, type: ext.slice(1) as any });\n }\n }\n }\n } catch (error: any) {\n // Silently skip directories that don't exist or can't be read\n if (error.code !== 'ENOENT' && error.code !== 'EACCES') {\n throw error;\n }\n }\n }\n\n /**\n * Process source maps to reconstruct TypeScript\n */\n private async processSourceMaps(): Promise<void> {\n // Find JS files that have corresponding .map files\n const jsFiles = this.files.filter(f => f.type === 'js');\n const mapFiles = this.files.filter(f => f.type === 'map');\n\n for (let i = 0; i < jsFiles.length; i++) {\n const jsFile = jsFiles[i];\n const mapFile = mapFiles.find(m => m.path === jsFile.path + '.map');\n\n if (mapFile && !this.files.some(f => f.path === jsFile.path.replace(/\\.js$/, '.ts'))) {\n // We have .js + .map but no .ts file\n // For now, we'll just parse the JS file directly\n // Full source map reconstruction would require the 'source-map' library\n console.log(`Note: Found ${jsFile.path} with source map but no .ts file. Will parse JS directly.`);\n }\n }\n }\n\n /**\n * Parse TypeScript files and create AST\n */\n private async parseTypeScriptFiles(): Promise<void> {\n let fileNames: string[] = [];\n\n if (this.entrypoint) {\n // If entrypoint is provided, let TypeScript resolve dependencies\n fileNames = [this.entrypoint];\n // console.log(`[Analyzer] Using entrypoint: ${this.entrypoint}`);\n } else {\n // Otherwise, scan the directory manually\n await this.scanDirectory(this.rootDir);\n const tsFiles = this.files.filter(f => f.type === 'ts' || f.type === 'js');\n fileNames = tsFiles.map(f => f.path);\n // console.log(`[Analyzer] Scanning directory, found ${fileNames.length} files`);\n }\n\n // Create TypeScript program\n this.program = ts.createProgram(fileNames, {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n allowJs: true,\n moduleResolution: ts.ModuleResolutionKind.Node10,\n rootDir: this.rootDir,\n skipLibCheck: true,\n skipDefaultLibCheck: true,\n });\n\n // If using entrypoint, update this.files with what TS found in the project\n if (this.entrypoint) {\n this.files = this.program.getSourceFiles()\n .filter(sf => !sf.fileName.includes('node_modules'))\n .map(sf => ({ path: sf.fileName, type: sf.fileName.endsWith('.js') ? 'js' : 'ts' }));\n }\n }\n\n /**\n * Find all Shokupan/ShokupanRouter instances\n */\n private async findApplications(): Promise<void> {\n if (!this.program) return;\n\n const typeChecker = this.program.getTypeChecker();\n\n for (let i = 0; i < this.program.getSourceFiles().length; i++) {\n const sourceFile = this.program.getSourceFiles()[i];\n // Skip node_modules and declaration files/tests\n if (sourceFile.fileName.includes('node_modules')) continue;\n if (sourceFile.isDeclarationFile) continue;\n\n // Allow analyzing test files if we are pointing explicitly to a test directory (fixtures)\n // OR if the file is in a fixtures directory (used for OpenAPI spec generation from test apps)\n // OR if the file IS the entrypoint\n const isTestEnv = this.rootDir.includes('/test/') ||\n this.rootDir.includes('/tests/') ||\n this.rootDir.includes('/fixtures/') ||\n (this.entrypoint && (this.entrypoint.includes('/test/') || this.entrypoint.includes('/tests/')));\n\n const isFixtureFile = sourceFile.fileName.includes('/fixtures/');\n const isEntrypoint = this.entrypoint && sourceFile.fileName === this.entrypoint;\n\n // console.log(`[Analyzer] check ${sourceFile.fileName}: isTestEnv=${isTestEnv}, isFixture=${isFixtureFile}, isEntry=${isEntrypoint}`);\n\n if (!isTestEnv && !isFixtureFile && !isEntrypoint) {\n if (sourceFile.fileName.includes('/test/') || sourceFile.fileName.includes('/tests/')) {\n // console.log(`[Analyzer] Skipping test file: ${sourceFile.fileName}`);\n continue;\n }\n if (sourceFile.fileName.includes('/base_test/')) continue;\n if (sourceFile.fileName.includes('.test.ts') || sourceFile.fileName.includes('.spec.ts')) continue;\n }\n\n ts.forEachChild(sourceFile, (node) => {\n this.visitNode(node, sourceFile, typeChecker);\n });\n }\n }\n\n /**\n * Visit AST node to find application instances\n */\n private visitNode(node: ts.Node, sourceFile: ts.SourceFile, typeChecker: ts.TypeChecker): void {\n // Look for: class FooController ... @Controller(...)\n if (ts.isClassDeclaration(node)) {\n // Check for @Controller decorator\n let isController = false;\n let controllerPrefix: string | undefined;\n let className = node.name?.getText(sourceFile);\n\n const decorators: ts.Decorator[] = (node as any).decorators || node.modifiers?.filter((m: any) => ts.isDecorator(m));\n\n if (decorators) {\n const controllerDecorator = decorators.find((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return identifier === 'Controller';\n }\n return false;\n });\n if (controllerDecorator) {\n isController = true;\n const expr = controllerDecorator.expression as ts.CallExpression;\n if (expr.arguments.length > 0 && ts.isStringLiteral(expr.arguments[0])) {\n controllerPrefix = expr.arguments[0].text;\n }\n }\n }\n\n // Fallback: Check for method decorators (@Get, @Post, etc.)\n if (!isController) {\n const hasRouteDecorators = node.members.some(m => {\n // Trust 175 as MethodDeclaration if TS matches mostly, or just check members\n if (ts.isMethodDeclaration(m) || m.kind === 175 || m.kind === 170 || m.kind === 171) {\n const decs = (m as any).decorators || (m as any).modifiers?.filter((mod: any) => ts.isDecorator(mod));\n if (decs) {\n return decs.some((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'event'].includes(identifier.toLowerCase());\n }\n return false;\n });\n }\n }\n return false;\n });\n if (hasRouteDecorators) {\n isController = true;\n }\n }\n\n if (isController && className) {\n this.applications.push({\n name: className,\n filePath: sourceFile.fileName,\n className: 'Controller',\n controllerPrefix,\n routes: [],\n mounted: []\n });\n }\n }\n\n // Look for: new Shokupan() or new ShokupanRouter()\n if (ts.isVariableDeclaration(node) && node.initializer) {\n if (ts.isNewExpression(node.initializer)) {\n const expr = node.initializer;\n const className = expr.expression.getText(sourceFile);\n\n if (className === 'Shokupan' || className === 'ShokupanRouter') {\n const varName = node.name.getText(sourceFile);\n\n this.applications.push({\n name: varName,\n filePath: sourceFile.fileName,\n className: className as 'Shokupan' | 'ShokupanRouter',\n routes: [],\n mounted: []\n });\n }\n }\n }\n\n // Look for standalone route calls in files that don't instantiate an app\n if (ts.isCallExpression(node)) {\n const expr = node.expression;\n if (ts.isPropertyAccessExpression(expr)) {\n const method = expr.name.getText(sourceFile);\n if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'event', 'on'].includes(method)) {\n const existing = this.applications.find(a => a.filePath === sourceFile.fileName);\n if (!existing) {\n this.applications.push({\n name: 'GenericModule',\n filePath: sourceFile.fileName,\n className: 'Shokupan',\n routes: [],\n mounted: []\n });\n }\n }\n }\n }\n\n // Recursively visit children\n ts.forEachChild(node, (child) => this.visitNode(child, sourceFile, typeChecker));\n }\n\n /**\n * Extract route information from applications\n */\n private async extractRoutes(): Promise<void> {\n if (!this.program) return;\n\n for (let i = 0; i < this.applications.length; i++) {\n const app = this.applications[i];\n const sourceFile = this.program.getSourceFile(app.filePath);\n if (!sourceFile) continue;\n\n this.extractRoutesFromFile(app, sourceFile);\n }\n }\n\n /**\n * Extract routes from a Controller class\n */\n private extractRoutesFromController(app: ApplicationInstance, classNode: ts.ClassDeclaration, sourceFile: ts.SourceFile): void {\n const methods = classNode.members.filter(m => ts.isMethodDeclaration(m) || m.kind === 175);\n\n for (let i = 0; i < methods.length; i++) {\n const method = methods[i];\n const methodNode = method as any; // Cast to any to access decorators in newer/older TS mix\n if (!methodNode.decorators && !methodNode.modifiers) continue;\n\n const decorators = methodNode.decorators || methodNode.modifiers?.filter((m: any) => ts.isDecorator(m));\n if (!decorators) continue;\n\n // Find route decorators: @Get, @Post, etc.\n const routeDecorator = decorators.find((d: any) => {\n const expr = d.expression;\n if (ts.isCallExpression(expr)) {\n const identifier = expr.expression.getText(sourceFile);\n return ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'event'].includes(identifier.toLowerCase());\n }\n return false;\n });\n\n if (routeDecorator && ts.isCallExpression(routeDecorator.expression)) {\n const decoratorName = routeDecorator.expression.expression.getText(sourceFile);\n const httpMethod = decoratorName.toUpperCase();\n let routePath = '/';\n\n // Get path\n const pathArg = routeDecorator.expression.arguments[0];\n if (pathArg && ts.isStringLiteral(pathArg)) {\n routePath = pathArg.text;\n }\n\n // Normalize path params: /users/:id -> /users/{id}\n routePath = routePath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n // Handler Name (Class.method)\n const handlerName = `${app.name}.${methodNode.name.getText(sourceFile)}`;\n\n // Analyze the method body\n const analysis = this.analyzeHandler(methodNode, sourceFile);\n\n app.routes.push({\n method: httpMethod,\n path: routePath,\n handlerName: handlerName,\n handlerSource: methodNode.getText(sourceFile),\n requestTypes: analysis.requestTypes,\n responseType: analysis.responseType,\n responseSchema: analysis.responseSchema,\n emits: analysis.emits,\n sourceContext: {\n file: sourceFile.fileName,\n startLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getStart()).line + 1,\n endLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getEnd()).line + 1,\n highlights: analysis.highlights\n }\n });\n }\n }\n };\n\n /**\n * Extract routes from a specific file\n */\n private extractRoutesFromFile(app: ApplicationInstance, sourceFile: ts.SourceFile): void {\n if (app.className === 'Controller') {\n const classNode = sourceFile.statements.find(s => ts.isClassDeclaration(s) && s.name?.getText(sourceFile) === app.name) as ts.ClassDeclaration;\n if (classNode) {\n this.extractRoutesFromController(app, classNode, sourceFile);\n }\n } else {\n // Existing logic for app/router instances\n const visit = (node: ts.Node) => {\n // ... (rest of the existing logic)\n // Look for method calls: app.get(...), app.post(...), app.mount(...)\n if (ts.isCallExpression(node)) {\n const expr = node.expression;\n\n if (ts.isPropertyAccessExpression(expr)) {\n const objName = expr.expression.getText(sourceFile);\n const methodName = expr.name.getText(sourceFile);\n\n // Check if this is our application instance\n // For GenericModule, we accept any variable name as it's likely an argument (e.g. app.event)\n if (objName === app.name || (app.name === 'GenericModule' && node.arguments.length >= 2)) {\n // console.log(`[Analyzer] Inspecting route call: ${objName}.${methodName} in ${app.name}`);\n if (['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'on', 'event'].includes(methodName.toLowerCase())) {\n // Extract route info\n const route = this.extractRouteFromCall(node, sourceFile, methodName.toUpperCase());\n if (route) {\n app.routes.push(route);\n }\n } else if (methodName === 'mount') {\n // Extract mount info\n const mount = this.extractMountFromCall(node, sourceFile);\n if (mount) {\n app.mounted.push(mount);\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n ts.forEachChild(sourceFile, visit);\n }\n }\n\n /**\n * Extract route information from a route call (e.g., app.get('/path', handler))\n */\n private extractRouteFromCall(node: ts.CallExpression, sourceFile: ts.SourceFile, method: string): RouteInfo | null {\n const args = node.arguments;\n\n if (args.length < 2) return null;\n\n const pathArg = args[0];\n let routePath = '/';\n\n if (ts.isStringLiteral(pathArg)) {\n routePath = pathArg.text;\n } else {\n routePath = '__DYNAMIC_EVENT__';\n }\n\n // Normalize path params: /users/:id -> /users/{id}\n // This ensures matching with runtime-generated keys\n const normalizedPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n let metadata: any = {};\n\n // Check for metadata argument (3 args: path, metadata, handler)\n if (args.length >= 3 && ts.isObjectLiteralExpression(args[1])) {\n const metaObj = args[1];\n // Extract summary, description, tags, etc.\n const rawMeta = this.convertExpressionToSchema(metaObj, sourceFile, new Map());\n\n // convertExpressionToSchema returns a schema-like object { type: 'object', properties: {...} }\n // But we want the actual values if they are literals.\n // convertExpressionToSchema is designed for SCHEMAS, not values.\n // We need a simpler value extractor or just parse the props directly for this specific case.\n\n for (let i = 0; i < metaObj.properties.length; i++) {\n const prop = metaObj.properties[i];\n if (ts.isPropertyAssignment(prop) && prop.name) {\n const name = prop.name.getText(sourceFile);\n const val = prop.initializer;\n\n if (ts.isStringLiteral(val)) {\n metadata[name] = val.text;\n } else if (ts.isArrayLiteralExpression(val) && name === 'tags') {\n metadata.tags = val.elements\n .filter(e => ts.isStringLiteral(e))\n .map(e => (e as ts.StringLiteral).text);\n } else if (name === 'operationId' && ts.isStringLiteral(val)) {\n metadata.operationId = val.text;\n }\n }\n }\n }\n\n // Extract handler information\n const handlerArg = args[args.length - 1];\n const handlerInfo = this.analyzeHandler(handlerArg, sourceFile);\n\n return {\n method,\n path: normalizedPath,\n handlerName: handlerArg.getText(sourceFile).substring(0, 50), // Truncate for display\n handlerSource: handlerArg.getText(sourceFile),\n requestTypes: handlerInfo.requestTypes,\n responseType: handlerInfo.responseType,\n responseSchema: handlerInfo.responseSchema,\n emits: handlerInfo.emits,\n ...metadata,\n sourceContext: {\n file: sourceFile.fileName,\n startLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getStart()).line + 1,\n endLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getEnd()).line + 1,\n highlights: handlerInfo.highlights\n }\n };\n }\n\n /**\n * Analyze a route handler to extract type information\n */\n private analyzeHandler(handler: ts.Node, sourceFile: ts.SourceFile): {\n requestTypes?: RouteInfo['requestTypes'];\n responseType?: string;\n responseSchema?: any;\n emits?: { event: string; payload?: any; location?: { startLine: number; endLine: number; }; }[];\n highlights?: { startLine: number; endLine: number; type: 'emit' | 'return-success' | 'return-warning'; }[];\n } {\n const requestTypes: RouteInfo['requestTypes'] = {};\n let responseType: string | undefined;\n let responseSchema: any | undefined;\n let hasExplicitReturnType = false;\n const emits: { event: string; payload?: any; location?: { startLine: number; endLine: number; }; }[] = [];\n const highlights: { startLine: number; endLine: number; type: 'emit' | 'return-success' | 'return-warning'; }[] = [];\n\n // Simple scope to track variable types (name -> schema)\n const scope = new Map<string, any>();\n\n // Pre-populate scope with function parameters\n if (ts.isFunctionLike(handler)) {\n handler.parameters.forEach(param => {\n if (ts.isIdentifier(param.name) && param.type) {\n const paramName = param.name.getText(sourceFile);\n // Resolving TypeReference for parameters (e.g. User) would require partial type checker or expanded scope logic\n // For now, we rely on basic types\n const paramType = this.convertTypeNodeToSchema(param.type, sourceFile);\n if (paramType) {\n scope.set(paramName, paramType);\n }\n }\n });\n\n // Check for explicit return type annotation\n if (handler.type) {\n const returnSchema = this.convertTypeNodeToSchema(handler.type, sourceFile);\n if (returnSchema) {\n responseSchema = returnSchema;\n responseType = returnSchema.type;\n hasExplicitReturnType = true;\n }\n }\n }\n\n // Helper to analyze an expression that is being returned (either explicitly or implicitly)\n const analyzeReturnExpression = (expr: ts.Expression) => {\n let node = expr;\n // Unwrap await\n if (ts.isAwaitExpression(node)) {\n node = node.expression;\n }\n\n\n // Case 1: return ctx.json(...) or ctx.text(...)\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\n const callObj = node.expression.expression.getText(sourceFile);\n const callProp = node.expression.name.getText(sourceFile);\n\n if (callObj === 'ctx' || callObj.endsWith('.ctx')) {\n if (callProp === 'json') {\n if (node.arguments.length > 0) {\n responseSchema = this.convertExpressionToSchema(node.arguments[0], sourceFile, scope);\n responseType = 'object';\n }\n return;\n }\n else if (callProp === 'text') {\n responseType = 'string';\n return;\n }\n else if (callProp === 'html' || callProp === 'jsx') {\n responseType = 'html';\n return;\n }\n }\n }\n\n // Case 2: Direct object return\n // Only use this if we haven't found a better schema yet, or if it looks specific\n // And if we don't have an explicit return type\n if (!hasExplicitReturnType && (!responseSchema || responseSchema.type === 'object')) {\n const schema = this.convertExpressionToSchema(node, sourceFile, scope);\n if (schema && (schema.type !== 'object' || Object.keys(schema.properties || {}).length > 0)) {\n responseSchema = schema;\n responseType = schema.type;\n }\n }\n\n // Fallback to text matching if schema inference failed and we still don't have a type\n if (!responseSchema && !responseType) {\n const returnText = node.getText(sourceFile);\n if (returnText.startsWith('{')) {\n responseType = 'object';\n } else if (returnText.startsWith('[')) {\n responseType = 'array';\n } else if (returnText.startsWith('\"') || returnText.startsWith(\"'\")) {\n responseType = 'string';\n }\n }\n };\n\n // Handle arrow functions\n let body: ts.Block | undefined;\n // Also check for Kind 175 (Method/Accessor in some TS versions)\n if (ts.isArrowFunction(handler) || ts.isFunctionExpression(handler) || ts.isMethodDeclaration(handler) || handler.kind === 175) {\n // TS method has .body which is FunctionBody (Block) or undefined\n body = (handler as any).body;\n\n // Visit the handler body to find ctx usage\n const visit = (node: ts.Node) => {\n // Track variable declarations\n if (ts.isVariableDeclaration(node)) {\n if (node.initializer && ts.isIdentifier(node.name)) {\n const varName = node.name.getText(sourceFile);\n\n // Check if initializer is a type assertion on ctx.body()\n let initializer = node.initializer;\n if (ts.isAsExpression(initializer)) {\n if (this.isCtxBodyCall(initializer.expression, sourceFile)) {\n const schema = this.convertTypeNodeToSchema(initializer.type, sourceFile);\n if (schema) {\n requestTypes.body = schema;\n scope.set(varName, schema);\n }\n } else {\n const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);\n scope.set(varName, schema);\n }\n } else {\n const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);\n scope.set(varName, schema);\n }\n }\n }\n\n // Check for type assertions on ctx.body()\n if (ts.isAsExpression(node)) {\n if (this.isCtxBodyCall(node.expression, sourceFile)) {\n const schema = this.convertTypeNodeToSchema(node.type, sourceFile);\n if (schema) {\n requestTypes.body = schema;\n\n // Track variables assigned to this body\n if (ts.isVariableDeclaration(node.parent)) {\n const varName = node.parent.name.getText(sourceFile);\n scope.set(varName, schema);\n }\n }\n }\n }\n\n // Look for ctx calls (json, text, html, send, emit) to highlight\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\n const objText = node.expression.expression.getText(sourceFile);\n const propText = node.expression.name.getText(sourceFile);\n\n if (objText === 'ctx' || objText.endsWith('.ctx') || objText === 'this') {\n const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;\n const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;\n\n if (['text', 'html', 'jsx'].includes(propText)) {\n // text/html/jsx are always strings, so statically valid\n highlights.push({ startLine, endLine, type: 'return-success' });\n } else if (propText === 'json') {\n // Check if we can extract a schema for the argument\n let isStatic = false;\n if (node.arguments.length > 0) {\n const schema = this.convertExpressionToSchema(node.arguments[0], sourceFile, scope);\n // If schema is not just a bare object or any, it's static\n if (schema && (schema.type !== 'object' || (schema.properties && Object.keys(schema.properties).length > 0))) {\n isStatic = true;\n }\n }\n highlights.push({ startLine, endLine, type: isStatic ? 'return-success' : 'return-warning' });\n } else if (['send', 'emit'].includes(propText)) {\n highlights.push({ startLine, endLine, type: 'emit' });\n }\n }\n }\n\n // Look for ctx usage\n if (ts.isPropertyAccessExpression(node)) {\n const objText = node.expression.getText(sourceFile);\n const propText = node.name.getText(sourceFile);\n\n if (objText === 'ctx' || objText.endsWith('.ctx')) {\n if (propText === 'body') {\n if (!requestTypes.body) {\n requestTypes.body = { type: 'object' };\n }\n } else if (propText === 'query') {\n if (!requestTypes.query) requestTypes.query = {};\n } else if (propText === 'params') {\n if (!requestTypes.params) requestTypes.params = {};\n } else if (propText === 'headers') {\n if (!requestTypes.headers) requestTypes.headers = {};\n }\n }\n }\n\n // Explicit Return\n if (ts.isReturnStatement(node)) {\n const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;\n const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;\n\n let isStatic = false;\n if (node.expression) {\n analyzeReturnExpression(node.expression);\n // If analyzeReturnExpression successfully set responseSchema to something specific, it's static\n // Note: analyzeReturnExpression updates local 'responseSchema' variable. \n // We check if it's \"good\"\n if (responseSchema && (responseSchema.type !== 'object' || (responseSchema.properties && Object.keys(responseSchema.properties).length > 0))) {\n isStatic = true;\n }\n } else if (hasExplicitReturnType) {\n // void return or similar\n isStatic = true;\n }\n\n highlights.push({ startLine, endLine, type: isStatic ? 'return-success' : 'return-warning' });\n }\n\n // Implicit Return (Concise Arrow Function)\n // e.g. (ctx) => ctx.json(...) or .then(res => ctx.json(res))\n if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {\n // For concise arrow function, the body IS the return value\n const startLine = sourceFile.getLineAndCharacterOfPosition(node.body.getStart()).line + 1;\n const endLine = sourceFile.getLineAndCharacterOfPosition(node.body.getEnd()).line + 1;\n\n analyzeReturnExpression(node.body as ts.Expression);\n\n let isStatic = false;\n if (responseSchema && (responseSchema.type !== 'object' || (responseSchema.properties && Object.keys(responseSchema.properties).length > 0))) {\n isStatic = true;\n }\n\n highlights.push({ startLine, endLine, type: isStatic ? 'return-success' : 'return-warning' });\n }\n\n // Implicit Return call (e.g. ctx.json(...) as a statement without return)\n if (ts.isExpressionStatement(node)) {\n analyzeReturnExpression(node.expression);\n\n // Check for ctx.emit() or this.emit()\n if (ts.isCallExpression(node.expression)) {\n const expr = node.expression;\n if (ts.isPropertyAccessExpression(expr.expression)) {\n const objText = expr.expression.expression.getText(sourceFile);\n const propText = expr.expression.name.getText(sourceFile);\n\n if (((objText === 'ctx' || objText.endsWith('.ctx')) || (objText === 'this' || objText.endsWith('.this'))) && propText === 'emit') {\n if (expr.arguments.length >= 1) {\n const eventNameArg = expr.arguments[0];\n if (ts.isStringLiteral(eventNameArg)) {\n const eventName = eventNameArg.text;\n let payload = { type: 'object' };\n\n if (expr.arguments.length >= 2) {\n payload = this.convertExpressionToSchema(expr.arguments[1], sourceFile, scope);\n }\n\n const emitLoc = {\n startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,\n endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1\n };\n emits.push({ event: eventName, payload, location: emitLoc });\n } else {\n const emitLoc = {\n startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,\n endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1\n };\n emits.push({ event: '__DYNAMIC_EMIT__', payload: { type: 'object' }, location: emitLoc });\n }\n }\n }\n }\n }\n }\n\n ts.forEachChild(node, visit);\n };\n\n if (ts.isBlock(body)) {\n ts.forEachChild(body, visit);\n } else {\n // Main handler is an implicit return: app.get('/', (ctx) => ctx.json(...))\n analyzeReturnExpression(body);\n // Also verify children (e.g. if body is a CallExpression, verify args??)\n // analyzeReturnExpression analyzes the expression itself.\n // But we ALSO want to visit children to find other usages (request types)\n // or nested arrow functions in a call chain (e.g. chaining .then())\n ts.forEachChild(body, visit);\n }\n }\n\n return { requestTypes, responseType, responseSchema, emits, highlights };\n }\n\n /**\n * Convert an Expression node to an OpenAPI schema (best effort)\n */\n private convertExpressionToSchema(node: ts.Expression, sourceFile: ts.SourceFile, scope: Map<string, any>): any {\n // Object Literal: { a: 1, b: \"text\" }\n if (ts.isObjectLiteralExpression(node)) {\n const schema: any = {\n type: 'object',\n properties: {},\n required: []\n };\n\n for (let i = 0; i < node.properties.length; i++) {\n const prop = node.properties[i];\n if (ts.isPropertyAssignment(prop)) {\n const name = prop.name.getText(sourceFile);\n const valueSchema = this.convertExpressionToSchema(prop.initializer, sourceFile, scope);\n\n schema.properties[name] = valueSchema;\n schema.required.push(name); // Properties in literal return are required\n }\n else if (ts.isShorthandPropertyAssignment(prop)) {\n const name = prop.name.getText(sourceFile);\n // Check scope for variable\n const scopedSchema = scope.get(name);\n schema.properties[name] = scopedSchema || { type: 'object' };\n schema.required.push(name);\n }\n }\n if (schema.required.length === 0) {\n delete schema.required;\n }\n return schema;\n }\n\n // Array Literal: [1, 2]\n if (ts.isArrayLiteralExpression(node)) {\n const schema: any = { type: 'array' };\n if (node.elements.length > 0) {\n // Infer item type from first element\n schema.items = this.convertExpressionToSchema(node.elements[0], sourceFile, scope);\n } else {\n schema.items = {};\n }\n return schema;\n }\n\n // Conditional (Ternary) Expression: cond ? trueVal : falseVal\n if (ts.isConditionalExpression(node)) {\n const trueSchema = this.convertExpressionToSchema(node.whenTrue, sourceFile, scope);\n // const falseSchema = this.convertExpressionToSchema(node.whenFalse, sourceFile, scope);\n\n // Simplified: return true branch schema. Ideally we'd do oneOf\n return trueSchema;\n }\n\n // Template Expression: `Hello ${name}`\n if (ts.isTemplateExpression(node)) {\n return { type: 'string' };\n }\n\n // Identifier (Variable reference)\n if (ts.isIdentifier(node)) {\n const name = node.getText(sourceFile);\n const scopedSchema = scope.get(name);\n if (scopedSchema) return scopedSchema;\n return { type: 'object' }; // Unknown reference\n }\n\n // Literals\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return { type: 'string' };\n if (ts.isNumericLiteral(node)) return { type: 'number' };\n if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return { type: 'boolean' };\n\n // Unknown\n return { type: 'object' };\n }\n\n /**\n * Check if an expression is a call to ctx.body()\n */\n private isCtxBodyCall(node: ts.Expression, sourceFile: ts.SourceFile): boolean {\n // Handle await ctx.body()\n if (ts.isAwaitExpression(node)) {\n return this.isCtxBodyCall(node.expression, sourceFile);\n }\n\n // Handle ctx.body()\n if (ts.isCallExpression(node)) {\n // Check expression: ctx.body\n if (ts.isPropertyAccessExpression(node.expression)) {\n const objText = node.expression.expression.getText(sourceFile);\n const propText = node.expression.name.getText(sourceFile);\n return (objText === 'ctx' || objText.endsWith('.ctx')) && propText === 'body';\n }\n }\n\n return false;\n }\n\n /**\n * Convert a TypeScript TypeNode to an OpenAPI schema\n */\n private convertTypeNodeToSchema(typeNode: ts.TypeNode, sourceFile: ts.SourceFile): any {\n switch (typeNode.kind) {\n case ts.SyntaxKind.StringKeyword:\n return { type: 'string' };\n case ts.SyntaxKind.NumberKeyword:\n return { type: 'number' };\n case ts.SyntaxKind.BooleanKeyword:\n return { type: 'boolean' };\n case ts.SyntaxKind.AnyKeyword:\n case ts.SyntaxKind.UnknownKeyword:\n return {}; // Any/Unknown -> empty schema (accepts anything)\n\n case ts.SyntaxKind.TypeLiteral: {\n const literal = typeNode as ts.TypeLiteralNode;\n const schema: any = {\n type: 'object',\n properties: {},\n required: []\n };\n\n for (let i = 0; i < literal.members.length; i++) {\n const member = literal.members[i];\n if (ts.isPropertySignature(member) && member.type) {\n const name = member.name.getText(sourceFile);\n const propSchema = this.convertTypeNodeToSchema(member.type, sourceFile);\n\n schema.properties[name] = propSchema;\n\n // Property is required unless it has a question token\n if (!member.questionToken) {\n schema.required.push(name);\n }\n }\n }\n\n if (schema.required.length === 0) {\n delete schema.required;\n }\n\n return schema;\n }\n\n case ts.SyntaxKind.ArrayType: {\n const arrayType = typeNode as ts.ArrayTypeNode;\n return {\n type: 'array',\n items: this.convertTypeNodeToSchema(arrayType.elementType, sourceFile)\n };\n }\n\n // Handle Type References (e.g. Array<string>)\n case ts.SyntaxKind.TypeReference: {\n const typeRef = typeNode as ts.TypeReferenceNode;\n const typeName = typeRef.typeName.getText(sourceFile);\n\n if (typeName === 'Array' && typeRef.typeArguments?.length > 0) {\n return {\n type: 'array',\n items: this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile)\n };\n }\n\n if (typeName === 'Promise' && typeRef.typeArguments?.length > 0) {\n return this.convertTypeNodeToSchema(typeRef.typeArguments[0], sourceFile);\n }\n\n // For other references, we default to string or object as fallback\n // A fuller implementation would resolve the reference\n return { type: 'object', description: `Ref: ${typeName}` };\n }\n\n default:\n return { type: 'object' };\n }\n }\n\n /**\n * Extract mount information from mount call\n */\n private extractMountFromCall(node: ts.CallExpression, sourceFile: ts.SourceFile): MountInfo | null {\n const args = node.arguments;\n\n if (args.length < 2) return null;\n\n const pathArg = args[0];\n const targetArg = args[1];\n\n let prefix = '/';\n if (ts.isStringLiteral(pathArg)) {\n prefix = pathArg.text;\n }\n\n const target = targetArg.getText(sourceFile);\n\n // Check if target is from node_modules\n const dependency = this.checkIfExternalDependency(target, sourceFile);\n let targetFilePath: string | undefined;\n\n if (!dependency) {\n // Check for internal import\n let modulePath: string | undefined;\n ts.forEachChild(sourceFile, (node) => {\n if (targetFilePath || modulePath) return; // Found\n\n if (ts.isImportDeclaration(node)) {\n const specifier = node.moduleSpecifier;\n if (ts.isStringLiteral(specifier)) {\n const path = specifier.text;\n if (path.startsWith('.')) {\n // Check default import: import target from './...'\n if (node.importClause?.name?.getText(sourceFile) === target) {\n modulePath = path;\n }\n // Check named imports: import { target } from './...'\n else if (node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {\n for (const element of node.importClause.namedBindings.elements) {\n if (element.name.getText(sourceFile) === target) {\n modulePath = path;\n break;\n }\n }\n }\n }\n }\n }\n });\n\n if (modulePath) {\n const dir = path.dirname(sourceFile.fileName);\n const absolutePath = path.resolve(dir, modulePath);\n\n // Try to resolve extension\n const extensions = ['.ts', '.js', '.tsx', '.jsx', '/index.ts', '/index.js'];\n for (const ext of extensions) {\n if (fs.existsSync(absolutePath + ext)) {\n targetFilePath = absolutePath + ext;\n break;\n }\n // If absolutePath ends in /index (implicit)\n // Or if modulePath points to directory\n }\n // Try straight (if user included extension)\n if (!targetFilePath && fs.existsSync(absolutePath)) {\n targetFilePath = absolutePath;\n }\n // Try directory index\n if (!targetFilePath && fs.existsSync(path.join(absolutePath, 'index.ts'))) {\n targetFilePath = path.join(absolutePath, 'index.ts');\n }\n\n // Normalize result?\n }\n }\n\n return {\n prefix,\n target,\n targetFilePath,\n dependency,\n sourceContext: {\n file: sourceFile.fileName,\n startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,\n endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1\n }\n };\n }\n\n /**\n * Check if a reference is to an external dependency\n */\n private checkIfExternalDependency(identifier: string, sourceFile: ts.SourceFile): DependencyInfo | undefined {\n // This is a simplified check - in a full implementation, \n // we'd track imports and resolve them\n\n // For now, check if there's an import statement for this identifier\n const imports: ts.ImportDeclaration[] = [];\n\n ts.forEachChild(sourceFile, (node) => {\n if (ts.isImportDeclaration(node)) {\n imports.push(node);\n }\n });\n\n for (let i = 0; i < imports.length; i++) {\n const imp = imports[i];\n const moduleSpecifier = imp.moduleSpecifier;\n if (ts.isStringLiteral(moduleSpecifier)) {\n const modulePath = moduleSpecifier.text;\n\n // Check if it's a node_modules import (no relative path)\n if (!modulePath.startsWith('.') && !modulePath.startsWith('/')) {\n const namedBindings = imp.importClause?.namedBindings;\n\n if (namedBindings && ts.isNamedImports(namedBindings)) {\n for (let j = 0; j < namedBindings.elements.length; j++) {\n const element = namedBindings.elements[j];\n if (element.name.text === identifier) {\n // Try to read package version\n const version = this.getPackageVersion(modulePath);\n\n return {\n packageName: modulePath,\n version,\n importPath: modulePath,\n isExternal: true\n };\n }\n }\n }\n }\n }\n }\n\n return undefined;\n }\n\n /**\n * Get package version from package.json\n */\n private getPackageVersion(packageName: string): string | undefined {\n try {\n const packageJsonPath = path.join(this.rootDir, 'node_modules', packageName, 'package.json');\n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));\n return packageJson.version;\n }\n } catch (e) {\n // Ignore\n }\n return undefined;\n }\n\n /**\n * Generate OpenAPI specification\n */\n public generateOpenAPISpec(): any {\n const paths: Record<string, any> = {};\n\n const collectRoutes = (app: ApplicationInstance, prefix: string = '') => {\n // Add direct routes\n for (let i = 0; i < app.routes.length; i++) {\n const route = app.routes[i];\n // Ensure prefix handles slashes correctly\n const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n const cleanPath = route.path.startsWith('/') ? route.path : '/' + route.path;\n const fullPath = (cleanPrefix + cleanPath) || '/';\n\n // Normalization is already done in extractRouteFromCall, but double check\n const pathKey = fullPath.replace(/:([a-zA-Z0-9_]+)/g, '{$1}');\n\n if (!paths[pathKey]) {\n paths[pathKey] = {};\n }\n\n const method = route.method.toLowerCase();\n const operation: any = {\n summary: route.summary || `${route.method.toUpperCase()} ${pathKey}`,\n description: route.description,\n tags: route.tags,\n operationId: route.operationId,\n responses: {\n '200': {\n description: 'Successful response'\n }\n }\n };\n\n // Clean up undefined\n if (!operation.description) delete operation.description;\n if (!operation.tags) delete operation.tags;\n if (!operation.operationId) delete operation.operationId;\n\n // Add inferred response schema\n if (route.responseSchema) {\n operation.responses['200'].content = {\n 'application/json': {\n schema: route.responseSchema\n }\n };\n } else if (route.responseType) {\n // Fallback to basic type\n const contentType = route.responseType === 'string' ? 'text/plain' : 'application/json';\n operation.responses['200'].content = {\n [contentType]: {\n schema: { type: route.responseType }\n }\n };\n } else {\n // Default object\n operation.responses['200'].content = {\n 'application/json': {\n schema: { type: 'object' }\n }\n };\n }\n\n // Add request body schema if available\n if (route.requestTypes?.body) {\n operation.requestBody = {\n content: {\n 'application/json': {\n schema: route.requestTypes.body\n }\n }\n };\n }\n\n // Add query/path parameters\n const parameters: any[] = [];\n\n if (route.requestTypes?.query) {\n const entries = Object.entries(route.requestTypes.query);\n for (let i = 0; i < entries.length; i++) {\n const [key] = entries[i];\n parameters.push({\n name: key,\n in: 'query',\n schema: { type: 'string' }\n });\n }\n }\n\n if (route.requestTypes?.params) {\n // Also check for path params implied by the URL {param}\n // But assume extractRouteFromCall handled explicit ones?\n // Let's just trust requestTypes for now\n const entries = Object.entries(route.requestTypes.params);\n for (let i = 0; i < entries.length; i++) {\n const [key] = entries[i];\n parameters.push({\n name: key,\n in: 'path',\n required: true,\n schema: { type: 'string' }\n });\n }\n }\n\n // Extract params from path if not explicitly typed but present in URL\n // This backfills untyped params so they appear in spec\n const pathParams = pathKey.match(/{([^}]+)}/g);\n if (pathParams) {\n pathParams.forEach(p => {\n const name = p.slice(1, -1);\n if (!parameters.some(param => param.name === name && param.in === 'path')) {\n parameters.push({\n name,\n in: 'path',\n required: true,\n schema: { type: 'string' } // Default to string\n });\n }\n });\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n paths[pathKey][method] = operation;\n }\n\n // Recurse into mounted apps\n for (let i = 0; i < app.mounted.length; i++) {\n const mount = app.mounted[i];\n // We need to resolve the ApplicationInstance for the target\n // The 'mounted' array currently only stores { prefix, target, dependency }\n // We need to find the AppInstance that matches 'target' class name\n\n // Note: This simple matching by class name might be brittle if multiple files have same class name\n // In a full implementation we would resolve the file path.\n const mountedApp = this.applications.find(a => a.name === mount.target || a.className === mount.target);\n\n if (mountedApp) {\n // Prevent infinite recursion\n if (mountedApp === app) continue;\n\n const cleanPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;\n const mountPrefix = mount.prefix.startsWith('/') ? mount.prefix : '/' + mount.prefix;\n const nextPrefix = cleanPrefix + mountPrefix;\n\n collectRoutes(mountedApp, nextPrefix);\n }\n }\n };\n\n for (let i = 0; i < this.applications.length; i++) {\n const app = this.applications[i];\n // We only want to start collection from \"root\" apps? \n // Or just collect everything? \n // If we collect everything, we might duplicate routes if they are mounted.\n // Current findApplications finds ALL instances.\n\n // Heuristic: If this app is mounted by another app, don't collect it as root.\n // But we don't have back-references easily.\n // Simplified: Just collect everything for now, but realize that un-mounted routers might show up as root.\n // Ideally we need to find the \"Main\" app (root).\n\n // For now, let's collect all, but we might have duplicates if we traverse mounts.\n // Let's rely on the user to only have one Main entrypoint usually.\n // Or we can try to detect if it is mounted.\n\n const isMounted = this.applications.some(parent =>\n parent.mounted.some(m => m.target === app.name || m.target === app.className)\n );\n\n if (!isMounted) {\n collectRoutes(app);\n }\n }\n\n return {\n openapi: '3.1.0',\n info: {\n title: 'Shokupan API',\n version: '1.0.0',\n description: 'Auto-generated from Shokupan application analysis'\n },\n paths,\n components: {\n schemas: {}\n }\n };\n }\n}\n\n/**\n * Analyze a directory and generate OpenAPI spec\n */\nexport async function analyzeDirectory(directory: string): Promise<any> {\n const analyzer = new OpenAPIAnalyzer(directory);\n return await analyzer.analyze();\n}\n"],"names":["node","path","i"],"mappings":"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"}
|
|
@@ -25,8 +25,41 @@ class OpenAPIAnalyzer {
|
|
|
25
25
|
await this.processSourceMaps();
|
|
26
26
|
await this.findApplications();
|
|
27
27
|
await this.extractRoutes();
|
|
28
|
+
this.pruneApplications();
|
|
28
29
|
return { applications: this.applications };
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Remove GenericModules that are not mounted by any Shokupan application/router
|
|
33
|
+
*/
|
|
34
|
+
pruneApplications() {
|
|
35
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
36
|
+
const queue = [];
|
|
37
|
+
for (const app of this.applications) {
|
|
38
|
+
if (app.name !== "GenericModule") {
|
|
39
|
+
reachable.add(app.filePath);
|
|
40
|
+
queue.push(app);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
while (queue.length > 0) {
|
|
44
|
+
const app = queue.shift();
|
|
45
|
+
for (const mount of app.mounted) {
|
|
46
|
+
if (mount.targetFilePath && !reachable.has(mount.targetFilePath)) {
|
|
47
|
+
reachable.add(mount.targetFilePath);
|
|
48
|
+
const mountedApp = this.applications.find((a) => a.filePath === mount.targetFilePath);
|
|
49
|
+
if (mountedApp) {
|
|
50
|
+
queue.push(mountedApp);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.applications.length;
|
|
56
|
+
this.applications = this.applications.filter((app) => {
|
|
57
|
+
if (app.name === "GenericModule" && !reachable.has(app.filePath)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
30
63
|
/**
|
|
31
64
|
* Recursively scan directory for TypeScript/JavaScript files
|
|
32
65
|
*/
|
|
@@ -103,10 +136,13 @@ class OpenAPIAnalyzer {
|
|
|
103
136
|
const sourceFile = this.program.getSourceFiles()[i];
|
|
104
137
|
if (sourceFile.fileName.includes("node_modules")) continue;
|
|
105
138
|
if (sourceFile.isDeclarationFile) continue;
|
|
106
|
-
const isTestEnv = this.rootDir.includes("/test/") || this.rootDir.includes("/tests/") || this.rootDir.includes("/fixtures/");
|
|
139
|
+
const isTestEnv = this.rootDir.includes("/test/") || this.rootDir.includes("/tests/") || this.rootDir.includes("/fixtures/") || this.entrypoint && (this.entrypoint.includes("/test/") || this.entrypoint.includes("/tests/"));
|
|
107
140
|
const isFixtureFile = sourceFile.fileName.includes("/fixtures/");
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
const isEntrypoint = this.entrypoint && sourceFile.fileName === this.entrypoint;
|
|
142
|
+
if (!isTestEnv && !isFixtureFile && !isEntrypoint) {
|
|
143
|
+
if (sourceFile.fileName.includes("/test/") || sourceFile.fileName.includes("/tests/")) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
110
146
|
if (sourceFile.fileName.includes("/base_test/")) continue;
|
|
111
147
|
if (sourceFile.fileName.includes(".test.ts") || sourceFile.fileName.includes(".spec.ts")) continue;
|
|
112
148
|
}
|
|
@@ -121,6 +157,7 @@ class OpenAPIAnalyzer {
|
|
|
121
157
|
visitNode(node, sourceFile, typeChecker) {
|
|
122
158
|
if (ts.isClassDeclaration(node)) {
|
|
123
159
|
let isController = false;
|
|
160
|
+
let controllerPrefix;
|
|
124
161
|
let className = node.name?.getText(sourceFile);
|
|
125
162
|
const decorators = node.decorators || node.modifiers?.filter((m) => ts.isDecorator(m));
|
|
126
163
|
if (decorators) {
|
|
@@ -132,7 +169,13 @@ class OpenAPIAnalyzer {
|
|
|
132
169
|
}
|
|
133
170
|
return false;
|
|
134
171
|
});
|
|
135
|
-
if (controllerDecorator)
|
|
172
|
+
if (controllerDecorator) {
|
|
173
|
+
isController = true;
|
|
174
|
+
const expr = controllerDecorator.expression;
|
|
175
|
+
if (expr.arguments.length > 0 && ts.isStringLiteral(expr.arguments[0])) {
|
|
176
|
+
controllerPrefix = expr.arguments[0].text;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
136
179
|
}
|
|
137
180
|
if (!isController) {
|
|
138
181
|
const hasRouteDecorators = node.members.some((m) => {
|
|
@@ -143,7 +186,7 @@ class OpenAPIAnalyzer {
|
|
|
143
186
|
const expr = d.expression;
|
|
144
187
|
if (ts.isCallExpression(expr)) {
|
|
145
188
|
const identifier = expr.expression.getText(sourceFile);
|
|
146
|
-
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
189
|
+
return ["get", "post", "put", "delete", "patch", "options", "head", "event"].includes(identifier.toLowerCase());
|
|
147
190
|
}
|
|
148
191
|
return false;
|
|
149
192
|
});
|
|
@@ -160,6 +203,7 @@ class OpenAPIAnalyzer {
|
|
|
160
203
|
name: className,
|
|
161
204
|
filePath: sourceFile.fileName,
|
|
162
205
|
className: "Controller",
|
|
206
|
+
controllerPrefix,
|
|
163
207
|
routes: [],
|
|
164
208
|
mounted: []
|
|
165
209
|
});
|
|
@@ -181,6 +225,24 @@ class OpenAPIAnalyzer {
|
|
|
181
225
|
}
|
|
182
226
|
}
|
|
183
227
|
}
|
|
228
|
+
if (ts.isCallExpression(node)) {
|
|
229
|
+
const expr = node.expression;
|
|
230
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
231
|
+
const method = expr.name.getText(sourceFile);
|
|
232
|
+
if (["get", "post", "put", "delete", "patch", "options", "head", "event", "on"].includes(method)) {
|
|
233
|
+
const existing = this.applications.find((a) => a.filePath === sourceFile.fileName);
|
|
234
|
+
if (!existing) {
|
|
235
|
+
this.applications.push({
|
|
236
|
+
name: "GenericModule",
|
|
237
|
+
filePath: sourceFile.fileName,
|
|
238
|
+
className: "Shokupan",
|
|
239
|
+
routes: [],
|
|
240
|
+
mounted: []
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
184
246
|
ts.forEachChild(node, (child) => this.visitNode(child, sourceFile, typeChecker));
|
|
185
247
|
}
|
|
186
248
|
/**
|
|
@@ -210,7 +272,7 @@ class OpenAPIAnalyzer {
|
|
|
210
272
|
const expr = d.expression;
|
|
211
273
|
if (ts.isCallExpression(expr)) {
|
|
212
274
|
const identifier = expr.expression.getText(sourceFile);
|
|
213
|
-
return ["get", "post", "put", "delete", "patch", "options", "head"].includes(identifier.toLowerCase());
|
|
275
|
+
return ["get", "post", "put", "delete", "patch", "options", "head", "event"].includes(identifier.toLowerCase());
|
|
214
276
|
}
|
|
215
277
|
return false;
|
|
216
278
|
});
|
|
@@ -232,7 +294,14 @@ class OpenAPIAnalyzer {
|
|
|
232
294
|
handlerSource: methodNode.getText(sourceFile),
|
|
233
295
|
requestTypes: analysis.requestTypes,
|
|
234
296
|
responseType: analysis.responseType,
|
|
235
|
-
responseSchema: analysis.responseSchema
|
|
297
|
+
responseSchema: analysis.responseSchema,
|
|
298
|
+
emits: analysis.emits,
|
|
299
|
+
sourceContext: {
|
|
300
|
+
file: sourceFile.fileName,
|
|
301
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getStart()).line + 1,
|
|
302
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(methodNode.getEnd()).line + 1,
|
|
303
|
+
highlights: analysis.highlights
|
|
304
|
+
}
|
|
236
305
|
});
|
|
237
306
|
}
|
|
238
307
|
}
|
|
@@ -253,8 +322,8 @@ class OpenAPIAnalyzer {
|
|
|
253
322
|
if (ts.isPropertyAccessExpression(expr)) {
|
|
254
323
|
const objName = expr.expression.getText(sourceFile);
|
|
255
324
|
const methodName = expr.name.getText(sourceFile);
|
|
256
|
-
if (objName === app.name) {
|
|
257
|
-
if (["get", "post", "put", "delete", "patch", "options", "head"].includes(methodName.toLowerCase())) {
|
|
325
|
+
if (objName === app.name || app.name === "GenericModule" && node.arguments.length >= 2) {
|
|
326
|
+
if (["get", "post", "put", "delete", "patch", "options", "head", "on", "event"].includes(methodName.toLowerCase())) {
|
|
258
327
|
const route = this.extractRouteFromCall(node, sourceFile, methodName.toUpperCase());
|
|
259
328
|
if (route) {
|
|
260
329
|
app.routes.push(route);
|
|
@@ -283,6 +352,8 @@ class OpenAPIAnalyzer {
|
|
|
283
352
|
let routePath = "/";
|
|
284
353
|
if (ts.isStringLiteral(pathArg)) {
|
|
285
354
|
routePath = pathArg.text;
|
|
355
|
+
} else {
|
|
356
|
+
routePath = "__DYNAMIC_EVENT__";
|
|
286
357
|
}
|
|
287
358
|
const normalizedPath = routePath.replace(/:([a-zA-Z0-9_]+)/g, "{$1}");
|
|
288
359
|
let metadata = {};
|
|
@@ -315,7 +386,14 @@ class OpenAPIAnalyzer {
|
|
|
315
386
|
requestTypes: handlerInfo.requestTypes,
|
|
316
387
|
responseType: handlerInfo.responseType,
|
|
317
388
|
responseSchema: handlerInfo.responseSchema,
|
|
318
|
-
|
|
389
|
+
emits: handlerInfo.emits,
|
|
390
|
+
...metadata,
|
|
391
|
+
sourceContext: {
|
|
392
|
+
file: sourceFile.fileName,
|
|
393
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getStart()).line + 1,
|
|
394
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(handlerArg.getEnd()).line + 1,
|
|
395
|
+
highlights: handlerInfo.highlights
|
|
396
|
+
}
|
|
319
397
|
};
|
|
320
398
|
}
|
|
321
399
|
/**
|
|
@@ -326,6 +404,8 @@ class OpenAPIAnalyzer {
|
|
|
326
404
|
let responseType;
|
|
327
405
|
let responseSchema;
|
|
328
406
|
let hasExplicitReturnType = false;
|
|
407
|
+
const emits = [];
|
|
408
|
+
const highlights = [];
|
|
329
409
|
const scope = /* @__PURE__ */ new Map();
|
|
330
410
|
if (ts.isFunctionLike(handler)) {
|
|
331
411
|
handler.parameters.forEach((param) => {
|
|
@@ -364,6 +444,9 @@ class OpenAPIAnalyzer {
|
|
|
364
444
|
} else if (callProp === "text") {
|
|
365
445
|
responseType = "string";
|
|
366
446
|
return;
|
|
447
|
+
} else if (callProp === "html" || callProp === "jsx") {
|
|
448
|
+
responseType = "html";
|
|
449
|
+
return;
|
|
367
450
|
}
|
|
368
451
|
}
|
|
369
452
|
}
|
|
@@ -389,6 +472,27 @@ class OpenAPIAnalyzer {
|
|
|
389
472
|
if (ts.isArrowFunction(handler) || ts.isFunctionExpression(handler) || ts.isMethodDeclaration(handler) || handler.kind === 175) {
|
|
390
473
|
body = handler.body;
|
|
391
474
|
const visit = (node) => {
|
|
475
|
+
if (ts.isVariableDeclaration(node)) {
|
|
476
|
+
if (node.initializer && ts.isIdentifier(node.name)) {
|
|
477
|
+
const varName = node.name.getText(sourceFile);
|
|
478
|
+
let initializer = node.initializer;
|
|
479
|
+
if (ts.isAsExpression(initializer)) {
|
|
480
|
+
if (this.isCtxBodyCall(initializer.expression, sourceFile)) {
|
|
481
|
+
const schema = this.convertTypeNodeToSchema(initializer.type, sourceFile);
|
|
482
|
+
if (schema) {
|
|
483
|
+
requestTypes.body = schema;
|
|
484
|
+
scope.set(varName, schema);
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);
|
|
488
|
+
scope.set(varName, schema);
|
|
489
|
+
}
|
|
490
|
+
} else {
|
|
491
|
+
const schema = this.convertExpressionToSchema(initializer, sourceFile, scope);
|
|
492
|
+
scope.set(varName, schema);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
392
496
|
if (ts.isAsExpression(node)) {
|
|
393
497
|
if (this.isCtxBodyCall(node.expression, sourceFile)) {
|
|
394
498
|
const schema = this.convertTypeNodeToSchema(node.type, sourceFile);
|
|
@@ -401,6 +505,28 @@ class OpenAPIAnalyzer {
|
|
|
401
505
|
}
|
|
402
506
|
}
|
|
403
507
|
}
|
|
508
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
509
|
+
const objText = node.expression.expression.getText(sourceFile);
|
|
510
|
+
const propText = node.expression.name.getText(sourceFile);
|
|
511
|
+
if (objText === "ctx" || objText.endsWith(".ctx") || objText === "this") {
|
|
512
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
513
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
514
|
+
if (["text", "html", "jsx"].includes(propText)) {
|
|
515
|
+
highlights.push({ startLine, endLine, type: "return-success" });
|
|
516
|
+
} else if (propText === "json") {
|
|
517
|
+
let isStatic = false;
|
|
518
|
+
if (node.arguments.length > 0) {
|
|
519
|
+
const schema = this.convertExpressionToSchema(node.arguments[0], sourceFile, scope);
|
|
520
|
+
if (schema && (schema.type !== "object" || schema.properties && Object.keys(schema.properties).length > 0)) {
|
|
521
|
+
isStatic = true;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
525
|
+
} else if (["send", "emit"].includes(propText)) {
|
|
526
|
+
highlights.push({ startLine, endLine, type: "emit" });
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
404
530
|
if (ts.isPropertyAccessExpression(node)) {
|
|
405
531
|
const objText = node.expression.getText(sourceFile);
|
|
406
532
|
const propText = node.name.getText(sourceFile);
|
|
@@ -418,14 +544,62 @@ class OpenAPIAnalyzer {
|
|
|
418
544
|
}
|
|
419
545
|
}
|
|
420
546
|
}
|
|
421
|
-
if (ts.isReturnStatement(node)
|
|
422
|
-
|
|
547
|
+
if (ts.isReturnStatement(node)) {
|
|
548
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
549
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
550
|
+
let isStatic = false;
|
|
551
|
+
if (node.expression) {
|
|
552
|
+
analyzeReturnExpression(node.expression);
|
|
553
|
+
if (responseSchema && (responseSchema.type !== "object" || responseSchema.properties && Object.keys(responseSchema.properties).length > 0)) {
|
|
554
|
+
isStatic = true;
|
|
555
|
+
}
|
|
556
|
+
} else if (hasExplicitReturnType) {
|
|
557
|
+
isStatic = true;
|
|
558
|
+
}
|
|
559
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
423
560
|
}
|
|
424
561
|
if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
|
|
562
|
+
const startLine = sourceFile.getLineAndCharacterOfPosition(node.body.getStart()).line + 1;
|
|
563
|
+
const endLine = sourceFile.getLineAndCharacterOfPosition(node.body.getEnd()).line + 1;
|
|
425
564
|
analyzeReturnExpression(node.body);
|
|
565
|
+
let isStatic = false;
|
|
566
|
+
if (responseSchema && (responseSchema.type !== "object" || responseSchema.properties && Object.keys(responseSchema.properties).length > 0)) {
|
|
567
|
+
isStatic = true;
|
|
568
|
+
}
|
|
569
|
+
highlights.push({ startLine, endLine, type: isStatic ? "return-success" : "return-warning" });
|
|
426
570
|
}
|
|
427
571
|
if (ts.isExpressionStatement(node)) {
|
|
428
572
|
analyzeReturnExpression(node.expression);
|
|
573
|
+
if (ts.isCallExpression(node.expression)) {
|
|
574
|
+
const expr = node.expression;
|
|
575
|
+
if (ts.isPropertyAccessExpression(expr.expression)) {
|
|
576
|
+
const objText = expr.expression.expression.getText(sourceFile);
|
|
577
|
+
const propText = expr.expression.name.getText(sourceFile);
|
|
578
|
+
if ((objText === "ctx" || objText.endsWith(".ctx") || (objText === "this" || objText.endsWith(".this"))) && propText === "emit") {
|
|
579
|
+
if (expr.arguments.length >= 1) {
|
|
580
|
+
const eventNameArg = expr.arguments[0];
|
|
581
|
+
if (ts.isStringLiteral(eventNameArg)) {
|
|
582
|
+
const eventName = eventNameArg.text;
|
|
583
|
+
let payload = { type: "object" };
|
|
584
|
+
if (expr.arguments.length >= 2) {
|
|
585
|
+
payload = this.convertExpressionToSchema(expr.arguments[1], sourceFile, scope);
|
|
586
|
+
}
|
|
587
|
+
const emitLoc = {
|
|
588
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,
|
|
589
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1
|
|
590
|
+
};
|
|
591
|
+
emits.push({ event: eventName, payload, location: emitLoc });
|
|
592
|
+
} else {
|
|
593
|
+
const emitLoc = {
|
|
594
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(expr.getStart()).line + 1,
|
|
595
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(expr.getEnd()).line + 1
|
|
596
|
+
};
|
|
597
|
+
emits.push({ event: "__DYNAMIC_EMIT__", payload: { type: "object" }, location: emitLoc });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
429
603
|
}
|
|
430
604
|
ts.forEachChild(node, visit);
|
|
431
605
|
};
|
|
@@ -436,7 +610,7 @@ class OpenAPIAnalyzer {
|
|
|
436
610
|
ts.forEachChild(body, visit);
|
|
437
611
|
}
|
|
438
612
|
}
|
|
439
|
-
return { requestTypes, responseType, responseSchema };
|
|
613
|
+
return { requestTypes, responseType, responseSchema, emits, highlights };
|
|
440
614
|
}
|
|
441
615
|
/**
|
|
442
616
|
* Convert an Expression node to an OpenAPI schema (best effort)
|
|
@@ -588,10 +762,58 @@ class OpenAPIAnalyzer {
|
|
|
588
762
|
}
|
|
589
763
|
const target = targetArg.getText(sourceFile);
|
|
590
764
|
const dependency = this.checkIfExternalDependency(target, sourceFile);
|
|
765
|
+
let targetFilePath;
|
|
766
|
+
if (!dependency) {
|
|
767
|
+
let modulePath;
|
|
768
|
+
ts.forEachChild(sourceFile, (node2) => {
|
|
769
|
+
if (targetFilePath || modulePath) return;
|
|
770
|
+
if (ts.isImportDeclaration(node2)) {
|
|
771
|
+
const specifier = node2.moduleSpecifier;
|
|
772
|
+
if (ts.isStringLiteral(specifier)) {
|
|
773
|
+
const path2 = specifier.text;
|
|
774
|
+
if (path2.startsWith(".")) {
|
|
775
|
+
if (node2.importClause?.name?.getText(sourceFile) === target) {
|
|
776
|
+
modulePath = path2;
|
|
777
|
+
} else if (node2.importClause?.namedBindings && ts.isNamedImports(node2.importClause.namedBindings)) {
|
|
778
|
+
for (const element of node2.importClause.namedBindings.elements) {
|
|
779
|
+
if (element.name.getText(sourceFile) === target) {
|
|
780
|
+
modulePath = path2;
|
|
781
|
+
break;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
if (modulePath) {
|
|
790
|
+
const dir = path.dirname(sourceFile.fileName);
|
|
791
|
+
const absolutePath = path.resolve(dir, modulePath);
|
|
792
|
+
const extensions = [".ts", ".js", ".tsx", ".jsx", "/index.ts", "/index.js"];
|
|
793
|
+
for (const ext of extensions) {
|
|
794
|
+
if (fs.existsSync(absolutePath + ext)) {
|
|
795
|
+
targetFilePath = absolutePath + ext;
|
|
796
|
+
break;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (!targetFilePath && fs.existsSync(absolutePath)) {
|
|
800
|
+
targetFilePath = absolutePath;
|
|
801
|
+
}
|
|
802
|
+
if (!targetFilePath && fs.existsSync(path.join(absolutePath, "index.ts"))) {
|
|
803
|
+
targetFilePath = path.join(absolutePath, "index.ts");
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
591
807
|
return {
|
|
592
808
|
prefix,
|
|
593
809
|
target,
|
|
594
|
-
|
|
810
|
+
targetFilePath,
|
|
811
|
+
dependency,
|
|
812
|
+
sourceContext: {
|
|
813
|
+
file: sourceFile.fileName,
|
|
814
|
+
startLine: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1,
|
|
815
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1
|
|
816
|
+
}
|
|
595
817
|
};
|
|
596
818
|
}
|
|
597
819
|
/**
|
|
@@ -782,10 +1004,5 @@ class OpenAPIAnalyzer {
|
|
|
782
1004
|
};
|
|
783
1005
|
}
|
|
784
1006
|
}
|
|
785
|
-
async function analyzeDirectory(directory) {
|
|
786
|
-
const analyzer = new OpenAPIAnalyzer(directory);
|
|
787
|
-
return await analyzer.analyze();
|
|
788
|
-
}
|
|
789
1007
|
exports.OpenAPIAnalyzer = OpenAPIAnalyzer;
|
|
790
|
-
|
|
791
|
-
//# sourceMappingURL=analyzer-Bei1sVWp.cjs.map
|
|
1008
|
+
//# sourceMappingURL=analyzer.impl-D9Yi1Hax.cjs.map
|