codesift-mcp 0.3.0 → 0.5.0
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/README.md +215 -23
- package/dist/cache/hono-cache.d.ts +50 -0
- package/dist/cache/hono-cache.d.ts.map +1 -0
- package/dist/cache/hono-cache.js +132 -0
- package/dist/cache/hono-cache.js.map +1 -0
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +8 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/platform.d.ts.map +1 -1
- package/dist/cli/platform.js +12 -14
- package/dist/cli/platform.js.map +1 -1
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +27 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/formatters-shortening.d.ts +13 -0
- package/dist/formatters-shortening.d.ts.map +1 -1
- package/dist/formatters-shortening.js +131 -0
- package/dist/formatters-shortening.js.map +1 -1
- package/dist/formatters.d.ts +38 -0
- package/dist/formatters.d.ts.map +1 -1
- package/dist/formatters.js +521 -0
- package/dist/formatters.js.map +1 -1
- package/dist/instructions.d.ts +1 -1
- package/dist/instructions.d.ts.map +1 -1
- package/dist/instructions.js +39 -38
- package/dist/instructions.js.map +1 -1
- package/dist/lsp/lsp-servers.d.ts.map +1 -1
- package/dist/lsp/lsp-servers.js +5 -0
- package/dist/lsp/lsp-servers.js.map +1 -1
- package/dist/lsp/lsp-tools.d.ts.map +1 -1
- package/dist/lsp/lsp-tools.js +1 -0
- package/dist/lsp/lsp-tools.js.map +1 -1
- package/dist/parser/astro-template.d.ts +47 -0
- package/dist/parser/astro-template.d.ts.map +1 -0
- package/dist/parser/astro-template.js +171 -0
- package/dist/parser/astro-template.js.map +1 -0
- package/dist/parser/extractors/_shared.d.ts +4 -0
- package/dist/parser/extractors/_shared.d.ts.map +1 -1
- package/dist/parser/extractors/_shared.js +8 -0
- package/dist/parser/extractors/_shared.js.map +1 -1
- package/dist/parser/extractors/astro.d.ts +4 -5
- package/dist/parser/extractors/astro.d.ts.map +1 -1
- package/dist/parser/extractors/astro.js +102 -26
- package/dist/parser/extractors/astro.js.map +1 -1
- package/dist/parser/extractors/gradle-kts.d.ts +4 -0
- package/dist/parser/extractors/gradle-kts.d.ts.map +1 -0
- package/dist/parser/extractors/gradle-kts.js +246 -0
- package/dist/parser/extractors/gradle-kts.js.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts +34 -0
- package/dist/parser/extractors/hono-inline-analyzer.d.ts.map +1 -0
- package/dist/parser/extractors/hono-inline-analyzer.js +465 -0
- package/dist/parser/extractors/hono-inline-analyzer.js.map +1 -0
- package/dist/parser/extractors/hono-model.d.ts +196 -0
- package/dist/parser/extractors/hono-model.d.ts.map +1 -0
- package/dist/parser/extractors/hono-model.js +10 -0
- package/dist/parser/extractors/hono-model.js.map +1 -0
- package/dist/parser/extractors/hono.d.ts +118 -0
- package/dist/parser/extractors/hono.d.ts.map +1 -0
- package/dist/parser/extractors/hono.js +1527 -0
- package/dist/parser/extractors/hono.js.map +1 -0
- package/dist/parser/extractors/kotlin.d.ts +4 -0
- package/dist/parser/extractors/kotlin.d.ts.map +1 -0
- package/dist/parser/extractors/kotlin.js +521 -0
- package/dist/parser/extractors/kotlin.js.map +1 -0
- package/dist/parser/extractors/php.d.ts +22 -0
- package/dist/parser/extractors/php.d.ts.map +1 -0
- package/dist/parser/extractors/php.js +334 -0
- package/dist/parser/extractors/php.js.map +1 -0
- package/dist/parser/extractors/python.d.ts.map +1 -1
- package/dist/parser/extractors/python.js +234 -11
- package/dist/parser/extractors/python.js.map +1 -1
- package/dist/parser/extractors/sql.d.ts +33 -0
- package/dist/parser/extractors/sql.d.ts.map +1 -0
- package/dist/parser/extractors/sql.js +506 -0
- package/dist/parser/extractors/sql.js.map +1 -0
- package/dist/parser/extractors/typescript.d.ts.map +1 -1
- package/dist/parser/extractors/typescript.js +209 -3
- package/dist/parser/extractors/typescript.js.map +1 -1
- package/dist/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/dist/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php.wasm +0 -0
- package/dist/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/dist/parser/languages/tree-sitter-python.wasm +0 -0
- package/dist/parser/parse-cache.d.ts +39 -0
- package/dist/parser/parse-cache.d.ts.map +1 -0
- package/dist/parser/parse-cache.js +87 -0
- package/dist/parser/parse-cache.js.map +1 -0
- package/dist/parser/parser-manager.d.ts +32 -0
- package/dist/parser/parser-manager.d.ts.map +1 -1
- package/dist/parser/parser-manager.js +93 -3
- package/dist/parser/parser-manager.js.map +1 -1
- package/dist/parser/symbol-extractor.d.ts.map +1 -1
- package/dist/parser/symbol-extractor.js +16 -0
- package/dist/parser/symbol-extractor.js.map +1 -1
- package/dist/register-tools.d.ts +38 -2
- package/dist/register-tools.d.ts.map +1 -1
- package/dist/register-tools.js +2444 -195
- package/dist/register-tools.js.map +1 -1
- package/dist/search/reranker.js +1 -1
- package/dist/search/reranker.js.map +1 -1
- package/dist/search/tool-ranker.d.ts +90 -0
- package/dist/search/tool-ranker.d.ts.map +1 -0
- package/dist/search/tool-ranker.js +420 -0
- package/dist/search/tool-ranker.js.map +1 -0
- package/dist/server-helpers.d.ts.map +1 -1
- package/dist/server-helpers.js +11 -0
- package/dist/server-helpers.js.map +1 -1
- package/dist/server.js +47 -14
- package/dist/server.js.map +1 -1
- package/dist/storage/index-store.d.ts +15 -1
- package/dist/storage/index-store.d.ts.map +1 -1
- package/dist/storage/index-store.js +27 -1
- package/dist/storage/index-store.js.map +1 -1
- package/dist/storage/session-state.d.ts +1 -1
- package/dist/storage/session-state.d.ts.map +1 -1
- package/dist/storage/session-state.js +6 -4
- package/dist/storage/session-state.js.map +1 -1
- package/dist/storage/usage-tracker.d.ts.map +1 -1
- package/dist/storage/usage-tracker.js +4 -1
- package/dist/storage/usage-tracker.js.map +1 -1
- package/dist/tools/agent-config-tools.d.ts +24 -0
- package/dist/tools/agent-config-tools.d.ts.map +1 -0
- package/dist/tools/agent-config-tools.js +119 -0
- package/dist/tools/agent-config-tools.js.map +1 -0
- package/dist/tools/architecture-tools.d.ts +23 -0
- package/dist/tools/architecture-tools.d.ts.map +1 -0
- package/dist/tools/architecture-tools.js +140 -0
- package/dist/tools/architecture-tools.js.map +1 -0
- package/dist/tools/astro-actions.d.ts +54 -0
- package/dist/tools/astro-actions.d.ts.map +1 -0
- package/dist/tools/astro-actions.js +561 -0
- package/dist/tools/astro-actions.js.map +1 -0
- package/dist/tools/astro-audit.d.ts +87 -0
- package/dist/tools/astro-audit.d.ts.map +1 -0
- package/dist/tools/astro-audit.js +345 -0
- package/dist/tools/astro-audit.js.map +1 -0
- package/dist/tools/astro-config.d.ts +33 -0
- package/dist/tools/astro-config.d.ts.map +1 -0
- package/dist/tools/astro-config.js +260 -0
- package/dist/tools/astro-config.js.map +1 -0
- package/dist/tools/astro-content-collections.d.ts +44 -0
- package/dist/tools/astro-content-collections.d.ts.map +1 -0
- package/dist/tools/astro-content-collections.js +630 -0
- package/dist/tools/astro-content-collections.js.map +1 -0
- package/dist/tools/astro-islands.d.ts +63 -0
- package/dist/tools/astro-islands.d.ts.map +1 -0
- package/dist/tools/astro-islands.js +255 -0
- package/dist/tools/astro-islands.js.map +1 -0
- package/dist/tools/astro-migration.d.ts +31 -0
- package/dist/tools/astro-migration.d.ts.map +1 -0
- package/dist/tools/astro-migration.js +378 -0
- package/dist/tools/astro-migration.js.map +1 -0
- package/dist/tools/astro-routes.d.ts +49 -0
- package/dist/tools/astro-routes.d.ts.map +1 -0
- package/dist/tools/astro-routes.js +119 -0
- package/dist/tools/astro-routes.js.map +1 -0
- package/dist/tools/async-correctness.d.ts +26 -0
- package/dist/tools/async-correctness.d.ts.map +1 -0
- package/dist/tools/async-correctness.js +166 -0
- package/dist/tools/async-correctness.js.map +1 -0
- package/dist/tools/audit-tools.d.ts +38 -0
- package/dist/tools/audit-tools.d.ts.map +1 -0
- package/dist/tools/audit-tools.js +248 -0
- package/dist/tools/audit-tools.js.map +1 -0
- package/dist/tools/celery-tools.d.ts +38 -0
- package/dist/tools/celery-tools.d.ts.map +1 -0
- package/dist/tools/celery-tools.js +154 -0
- package/dist/tools/celery-tools.js.map +1 -0
- package/dist/tools/clone-tools.js +1 -1
- package/dist/tools/clone-tools.js.map +1 -1
- package/dist/tools/complexity-tools.d.ts +4 -0
- package/dist/tools/complexity-tools.d.ts.map +1 -1
- package/dist/tools/complexity-tools.js +78 -4
- package/dist/tools/complexity-tools.js.map +1 -1
- package/dist/tools/compose-tools.d.ts +60 -0
- package/dist/tools/compose-tools.d.ts.map +1 -0
- package/dist/tools/compose-tools.js +203 -0
- package/dist/tools/compose-tools.js.map +1 -0
- package/dist/tools/coupling-tools.d.ts +50 -0
- package/dist/tools/coupling-tools.d.ts.map +1 -0
- package/dist/tools/coupling-tools.js +262 -0
- package/dist/tools/coupling-tools.js.map +1 -0
- package/dist/tools/dependency-audit-tools.d.ts +65 -0
- package/dist/tools/dependency-audit-tools.d.ts.map +1 -0
- package/dist/tools/dependency-audit-tools.js +553 -0
- package/dist/tools/dependency-audit-tools.js.map +1 -0
- package/dist/tools/django-settings.d.ts +22 -0
- package/dist/tools/django-settings.d.ts.map +1 -0
- package/dist/tools/django-settings.js +301 -0
- package/dist/tools/django-settings.js.map +1 -0
- package/dist/tools/django-view-security-tools.d.ts +32 -0
- package/dist/tools/django-view-security-tools.d.ts.map +1 -0
- package/dist/tools/django-view-security-tools.js +184 -0
- package/dist/tools/django-view-security-tools.js.map +1 -0
- package/dist/tools/fastapi-depends.d.ts +63 -0
- package/dist/tools/fastapi-depends.d.ts.map +1 -0
- package/dist/tools/fastapi-depends.js +191 -0
- package/dist/tools/fastapi-depends.js.map +1 -0
- package/dist/tools/frequency-tools.js +1 -1
- package/dist/tools/frequency-tools.js.map +1 -1
- package/dist/tools/graph-tools.d.ts +8 -2
- package/dist/tools/graph-tools.d.ts.map +1 -1
- package/dist/tools/graph-tools.js +44 -3
- package/dist/tools/graph-tools.js.map +1 -1
- package/dist/tools/hilt-tools.d.ts +55 -0
- package/dist/tools/hilt-tools.d.ts.map +1 -0
- package/dist/tools/hilt-tools.js +258 -0
- package/dist/tools/hilt-tools.js.map +1 -0
- package/dist/tools/hono-analyze-app.d.ts +48 -0
- package/dist/tools/hono-analyze-app.d.ts.map +1 -0
- package/dist/tools/hono-analyze-app.js +94 -0
- package/dist/tools/hono-analyze-app.js.map +1 -0
- package/dist/tools/hono-api-contract.d.ts +22 -0
- package/dist/tools/hono-api-contract.d.ts.map +1 -0
- package/dist/tools/hono-api-contract.js +112 -0
- package/dist/tools/hono-api-contract.js.map +1 -0
- package/dist/tools/hono-conditional-middleware.d.ts +27 -0
- package/dist/tools/hono-conditional-middleware.d.ts.map +1 -0
- package/dist/tools/hono-conditional-middleware.js +62 -0
- package/dist/tools/hono-conditional-middleware.js.map +1 -0
- package/dist/tools/hono-context-flow.d.ts +24 -0
- package/dist/tools/hono-context-flow.d.ts.map +1 -0
- package/dist/tools/hono-context-flow.js +70 -0
- package/dist/tools/hono-context-flow.js.map +1 -0
- package/dist/tools/hono-dead-routes.d.ts +26 -0
- package/dist/tools/hono-dead-routes.d.ts.map +1 -0
- package/dist/tools/hono-dead-routes.js +102 -0
- package/dist/tools/hono-dead-routes.js.map +1 -0
- package/dist/tools/hono-entry-resolver.d.ts +27 -0
- package/dist/tools/hono-entry-resolver.d.ts.map +1 -0
- package/dist/tools/hono-entry-resolver.js +31 -0
- package/dist/tools/hono-entry-resolver.js.map +1 -0
- package/dist/tools/hono-env-regression.d.ts +29 -0
- package/dist/tools/hono-env-regression.d.ts.map +1 -0
- package/dist/tools/hono-env-regression.js +157 -0
- package/dist/tools/hono-env-regression.js.map +1 -0
- package/dist/tools/hono-inline-analyze.d.ts +31 -0
- package/dist/tools/hono-inline-analyze.d.ts.map +1 -0
- package/dist/tools/hono-inline-analyze.js +59 -0
- package/dist/tools/hono-inline-analyze.js.map +1 -0
- package/dist/tools/hono-middleware-chain.d.ts +40 -0
- package/dist/tools/hono-middleware-chain.d.ts.map +1 -0
- package/dist/tools/hono-middleware-chain.js +121 -0
- package/dist/tools/hono-middleware-chain.js.map +1 -0
- package/dist/tools/hono-modules.d.ts +22 -0
- package/dist/tools/hono-modules.d.ts.map +1 -0
- package/dist/tools/hono-modules.js +118 -0
- package/dist/tools/hono-modules.js.map +1 -0
- package/dist/tools/hono-response-types.d.ts +37 -0
- package/dist/tools/hono-response-types.d.ts.map +1 -0
- package/dist/tools/hono-response-types.js +76 -0
- package/dist/tools/hono-response-types.js.map +1 -0
- package/dist/tools/hono-rpc-types.d.ts +21 -0
- package/dist/tools/hono-rpc-types.d.ts.map +1 -0
- package/dist/tools/hono-rpc-types.js +49 -0
- package/dist/tools/hono-rpc-types.js.map +1 -0
- package/dist/tools/hono-security.d.ts +31 -0
- package/dist/tools/hono-security.d.ts.map +1 -0
- package/dist/tools/hono-security.js +269 -0
- package/dist/tools/hono-security.js.map +1 -0
- package/dist/tools/hono-visualize.d.ts +13 -0
- package/dist/tools/hono-visualize.d.ts.map +1 -0
- package/dist/tools/hono-visualize.js +64 -0
- package/dist/tools/hono-visualize.js.map +1 -0
- package/dist/tools/hotspot-tools.d.ts.map +1 -1
- package/dist/tools/hotspot-tools.js +9 -7
- package/dist/tools/hotspot-tools.js.map +1 -1
- package/dist/tools/index-tools.d.ts +17 -0
- package/dist/tools/index-tools.d.ts.map +1 -1
- package/dist/tools/index-tools.js +210 -10
- package/dist/tools/index-tools.js.map +1 -1
- package/dist/tools/kotlin-tools.d.ts +142 -0
- package/dist/tools/kotlin-tools.d.ts.map +1 -0
- package/dist/tools/kotlin-tools.js +572 -0
- package/dist/tools/kotlin-tools.js.map +1 -0
- package/dist/tools/legacy-hono-conventions.d.ts +14 -0
- package/dist/tools/legacy-hono-conventions.d.ts.map +1 -0
- package/dist/tools/legacy-hono-conventions.js +152 -0
- package/dist/tools/legacy-hono-conventions.js.map +1 -0
- package/dist/tools/migration-lint-tools.d.ts +26 -0
- package/dist/tools/migration-lint-tools.d.ts.map +1 -0
- package/dist/tools/migration-lint-tools.js +247 -0
- package/dist/tools/migration-lint-tools.js.map +1 -0
- package/dist/tools/model-tools.d.ts +30 -0
- package/dist/tools/model-tools.d.ts.map +1 -0
- package/dist/tools/model-tools.js +145 -0
- package/dist/tools/model-tools.js.map +1 -0
- package/dist/tools/nest-ext-tools.d.ts +207 -0
- package/dist/tools/nest-ext-tools.d.ts.map +1 -0
- package/dist/tools/nest-ext-tools.js +752 -0
- package/dist/tools/nest-ext-tools.js.map +1 -0
- package/dist/tools/nest-tools.d.ts +198 -0
- package/dist/tools/nest-tools.d.ts.map +1 -0
- package/dist/tools/nest-tools.js +1142 -0
- package/dist/tools/nest-tools.js.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts +14 -0
- package/dist/tools/nextjs-api-contract-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-readers.js +204 -0
- package/dist/tools/nextjs-api-contract-readers.js.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts +57 -0
- package/dist/tools/nextjs-api-contract-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-api-contract-tools.js +144 -0
- package/dist/tools/nextjs-api-contract-tools.js.map +1 -0
- package/dist/tools/nextjs-boundary-tools.d.ts +39 -0
- package/dist/tools/nextjs-boundary-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-boundary-tools.js +152 -0
- package/dist/tools/nextjs-boundary-tools.js.map +1 -0
- package/dist/tools/nextjs-component-readers.d.ts +101 -0
- package/dist/tools/nextjs-component-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-component-readers.js +287 -0
- package/dist/tools/nextjs-component-readers.js.map +1 -0
- package/dist/tools/nextjs-component-tools.d.ts +51 -0
- package/dist/tools/nextjs-component-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-component-tools.js +212 -0
- package/dist/tools/nextjs-component-tools.js.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts +42 -0
- package/dist/tools/nextjs-data-flow-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-data-flow-tools.js +158 -0
- package/dist/tools/nextjs-data-flow-tools.js.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts +60 -0
- package/dist/tools/nextjs-framework-audit-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-framework-audit-tools.js +394 -0
- package/dist/tools/nextjs-framework-audit-tools.js.map +1 -0
- package/dist/tools/nextjs-link-tools.d.ts +41 -0
- package/dist/tools/nextjs-link-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-link-tools.js +157 -0
- package/dist/tools/nextjs-link-tools.js.map +1 -0
- package/dist/tools/nextjs-metadata-tools.d.ts +74 -0
- package/dist/tools/nextjs-metadata-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-metadata-tools.js +252 -0
- package/dist/tools/nextjs-metadata-tools.js.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts +41 -0
- package/dist/tools/nextjs-middleware-coverage-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js +88 -0
- package/dist/tools/nextjs-middleware-coverage-tools.js.map +1 -0
- package/dist/tools/nextjs-route-readers.d.ts +81 -0
- package/dist/tools/nextjs-route-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-route-readers.js +340 -0
- package/dist/tools/nextjs-route-readers.js.map +1 -0
- package/dist/tools/nextjs-route-tools.d.ts +36 -0
- package/dist/tools/nextjs-route-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-route-tools.js +175 -0
- package/dist/tools/nextjs-route-tools.js.map +1 -0
- package/dist/tools/nextjs-security-readers.d.ts +22 -0
- package/dist/tools/nextjs-security-readers.d.ts.map +1 -0
- package/dist/tools/nextjs-security-readers.js +318 -0
- package/dist/tools/nextjs-security-readers.js.map +1 -0
- package/dist/tools/nextjs-security-scoring.d.ts +15 -0
- package/dist/tools/nextjs-security-scoring.d.ts.map +1 -0
- package/dist/tools/nextjs-security-scoring.js +65 -0
- package/dist/tools/nextjs-security-scoring.js.map +1 -0
- package/dist/tools/nextjs-security-tools.d.ts +75 -0
- package/dist/tools/nextjs-security-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-security-tools.js +153 -0
- package/dist/tools/nextjs-security-tools.js.map +1 -0
- package/dist/tools/nextjs-tools.d.ts +15 -0
- package/dist/tools/nextjs-tools.d.ts.map +1 -0
- package/dist/tools/nextjs-tools.js +15 -0
- package/dist/tools/nextjs-tools.js.map +1 -0
- package/dist/tools/outline-tools.d.ts.map +1 -1
- package/dist/tools/outline-tools.js +20 -0
- package/dist/tools/outline-tools.js.map +1 -1
- package/dist/tools/pattern-tools.d.ts +8 -0
- package/dist/tools/pattern-tools.d.ts.map +1 -1
- package/dist/tools/pattern-tools.js +651 -3
- package/dist/tools/pattern-tools.js.map +1 -1
- package/dist/tools/perf-tools.d.ts +32 -0
- package/dist/tools/perf-tools.d.ts.map +1 -0
- package/dist/tools/perf-tools.js +227 -0
- package/dist/tools/perf-tools.js.map +1 -0
- package/dist/tools/php-tools.d.ts +185 -0
- package/dist/tools/php-tools.d.ts.map +1 -0
- package/dist/tools/php-tools.js +645 -0
- package/dist/tools/php-tools.js.map +1 -0
- package/dist/tools/plan-turn-tools.d.ts +89 -0
- package/dist/tools/plan-turn-tools.d.ts.map +1 -0
- package/dist/tools/plan-turn-tools.js +508 -0
- package/dist/tools/plan-turn-tools.js.map +1 -0
- package/dist/tools/prisma-schema-tools.d.ts +44 -0
- package/dist/tools/prisma-schema-tools.d.ts.map +1 -0
- package/dist/tools/prisma-schema-tools.js +358 -0
- package/dist/tools/prisma-schema-tools.js.map +1 -0
- package/dist/tools/project-tools.d.ts +116 -7
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +595 -218
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/pydantic-models.d.ts +46 -0
- package/dist/tools/pydantic-models.d.ts.map +1 -0
- package/dist/tools/pydantic-models.js +249 -0
- package/dist/tools/pydantic-models.js.map +1 -0
- package/dist/tools/pyproject-tools.d.ts +23 -0
- package/dist/tools/pyproject-tools.d.ts.map +1 -0
- package/dist/tools/pyproject-tools.js +133 -0
- package/dist/tools/pyproject-tools.js.map +1 -0
- package/dist/tools/pytest-tools.d.ts +20 -0
- package/dist/tools/pytest-tools.d.ts.map +1 -0
- package/dist/tools/pytest-tools.js +106 -0
- package/dist/tools/pytest-tools.js.map +1 -0
- package/dist/tools/python-audit.d.ts +40 -0
- package/dist/tools/python-audit.d.ts.map +1 -0
- package/dist/tools/python-audit.js +244 -0
- package/dist/tools/python-audit.js.map +1 -0
- package/dist/tools/python-callers.d.ts +28 -0
- package/dist/tools/python-callers.d.ts.map +1 -0
- package/dist/tools/python-callers.js +110 -0
- package/dist/tools/python-callers.js.map +1 -0
- package/dist/tools/python-circular-imports.d.ts +19 -0
- package/dist/tools/python-circular-imports.d.ts.map +1 -0
- package/dist/tools/python-circular-imports.js +126 -0
- package/dist/tools/python-circular-imports.js.map +1 -0
- package/dist/tools/python-constants-tools.d.ts +44 -0
- package/dist/tools/python-constants-tools.d.ts.map +1 -0
- package/dist/tools/python-constants-tools.js +525 -0
- package/dist/tools/python-constants-tools.js.map +1 -0
- package/dist/tools/python-deps-analyzer.d.ts +46 -0
- package/dist/tools/python-deps-analyzer.d.ts.map +1 -0
- package/dist/tools/python-deps-analyzer.js +227 -0
- package/dist/tools/python-deps-analyzer.js.map +1 -0
- package/dist/tools/query-tools.d.ts +23 -0
- package/dist/tools/query-tools.d.ts.map +1 -0
- package/dist/tools/query-tools.js +256 -0
- package/dist/tools/query-tools.js.map +1 -0
- package/dist/tools/react-tools.d.ts +263 -0
- package/dist/tools/react-tools.d.ts.map +1 -0
- package/dist/tools/react-tools.js +839 -0
- package/dist/tools/react-tools.js.map +1 -0
- package/dist/tools/report-tools.js +47 -0
- package/dist/tools/report-tools.js.map +1 -1
- package/dist/tools/review-diff-tools.d.ts +5 -4
- package/dist/tools/review-diff-tools.d.ts.map +1 -1
- package/dist/tools/review-diff-tools.js +157 -66
- package/dist/tools/review-diff-tools.js.map +1 -1
- package/dist/tools/room-tools.d.ts +36 -0
- package/dist/tools/room-tools.d.ts.map +1 -0
- package/dist/tools/room-tools.js +147 -0
- package/dist/tools/room-tools.js.map +1 -0
- package/dist/tools/route-tools.d.ts +27 -1
- package/dist/tools/route-tools.d.ts.map +1 -1
- package/dist/tools/route-tools.js +744 -18
- package/dist/tools/route-tools.js.map +1 -1
- package/dist/tools/ruff-tools.d.ts +32 -0
- package/dist/tools/ruff-tools.d.ts.map +1 -0
- package/dist/tools/ruff-tools.js +114 -0
- package/dist/tools/ruff-tools.js.map +1 -0
- package/dist/tools/search-ranker.d.ts.map +1 -1
- package/dist/tools/search-ranker.js +7 -0
- package/dist/tools/search-ranker.js.map +1 -1
- package/dist/tools/search-tools.d.ts +3 -2
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +16 -3
- package/dist/tools/search-tools.js.map +1 -1
- package/dist/tools/serialization-tools.d.ts +24 -0
- package/dist/tools/serialization-tools.d.ts.map +1 -0
- package/dist/tools/serialization-tools.js +156 -0
- package/dist/tools/serialization-tools.js.map +1 -0
- package/dist/tools/sql-tools.d.ts +274 -0
- package/dist/tools/sql-tools.d.ts.map +1 -0
- package/dist/tools/sql-tools.js +1160 -0
- package/dist/tools/sql-tools.js.map +1 -0
- package/dist/tools/status-tools.d.ts +10 -0
- package/dist/tools/status-tools.d.ts.map +1 -0
- package/dist/tools/status-tools.js +32 -0
- package/dist/tools/status-tools.js.map +1 -0
- package/dist/tools/symbol-tools.d.ts +19 -0
- package/dist/tools/symbol-tools.d.ts.map +1 -1
- package/dist/tools/symbol-tools.js +75 -4
- package/dist/tools/symbol-tools.js.map +1 -1
- package/dist/tools/taint-tools.d.ts +43 -0
- package/dist/tools/taint-tools.d.ts.map +1 -0
- package/dist/tools/taint-tools.js +922 -0
- package/dist/tools/taint-tools.js.map +1 -0
- package/dist/tools/test-impact-tools.d.ts +29 -0
- package/dist/tools/test-impact-tools.d.ts.map +1 -0
- package/dist/tools/test-impact-tools.js +156 -0
- package/dist/tools/test-impact-tools.js.map +1 -0
- package/dist/tools/typecheck-tools.d.ts +39 -0
- package/dist/tools/typecheck-tools.d.ts.map +1 -0
- package/dist/tools/typecheck-tools.js +191 -0
- package/dist/tools/typecheck-tools.js.map +1 -0
- package/dist/tools/wiring-tools.d.ts +19 -0
- package/dist/tools/wiring-tools.d.ts.map +1 -0
- package/dist/tools/wiring-tools.js +147 -0
- package/dist/tools/wiring-tools.js.map +1 -0
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/framework-detect.d.ts +18 -2
- package/dist/utils/framework-detect.d.ts.map +1 -1
- package/dist/utils/framework-detect.js +150 -3
- package/dist/utils/framework-detect.js.map +1 -1
- package/dist/utils/import-graph.d.ts +42 -0
- package/dist/utils/import-graph.d.ts.map +1 -1
- package/dist/utils/import-graph.js +248 -9
- package/dist/utils/import-graph.js.map +1 -1
- package/dist/utils/language-detect.d.ts +21 -0
- package/dist/utils/language-detect.d.ts.map +1 -0
- package/dist/utils/language-detect.js +183 -0
- package/dist/utils/language-detect.js.map +1 -0
- package/dist/utils/nextjs-ast-readers.d.ts +44 -0
- package/dist/utils/nextjs-ast-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-ast-readers.js +341 -0
- package/dist/utils/nextjs-ast-readers.js.map +1 -0
- package/dist/utils/nextjs-audit-cache.d.ts +51 -0
- package/dist/utils/nextjs-audit-cache.d.ts.map +1 -0
- package/dist/utils/nextjs-audit-cache.js +116 -0
- package/dist/utils/nextjs-audit-cache.js.map +1 -0
- package/dist/utils/nextjs-metadata-readers.d.ts +65 -0
- package/dist/utils/nextjs-metadata-readers.d.ts.map +1 -0
- package/dist/utils/nextjs-metadata-readers.js +447 -0
- package/dist/utils/nextjs-metadata-readers.js.map +1 -0
- package/dist/utils/nextjs.d.ts +42 -0
- package/dist/utils/nextjs.d.ts.map +1 -0
- package/dist/utils/nextjs.js +284 -0
- package/dist/utils/nextjs.js.map +1 -0
- package/dist/utils/python-import-resolver.d.ts +42 -0
- package/dist/utils/python-import-resolver.d.ts.map +1 -0
- package/dist/utils/python-import-resolver.js +101 -0
- package/dist/utils/python-import-resolver.js.map +1 -0
- package/dist/utils/python-imports.d.ts +28 -0
- package/dist/utils/python-imports.d.ts.map +1 -0
- package/dist/utils/python-imports.js +117 -0
- package/dist/utils/python-imports.js.map +1 -0
- package/dist/utils/react-alias.d.ts +15 -0
- package/dist/utils/react-alias.d.ts.map +1 -0
- package/dist/utils/react-alias.js +31 -0
- package/dist/utils/react-alias.js.map +1 -0
- package/dist/utils/test-file.d.ts.map +1 -1
- package/dist/utils/test-file.js +7 -0
- package/dist/utils/test-file.js.map +1 -1
- package/dist/utils/walk.d.ts +22 -0
- package/dist/utils/walk.d.ts.map +1 -1
- package/dist/utils/walk.js +70 -2
- package/dist/utils/walk.js.map +1 -1
- package/package.json +4 -3
- package/rules/codesift.md +71 -5
- package/rules/codesift.mdc +71 -5
- package/rules/codex.md +71 -5
- package/rules/gemini.md +71 -5
- package/src/parser/languages/tree-sitter-javascript.wasm +0 -0
- package/src/parser/languages/tree-sitter-kotlin.wasm +0 -0
- package/src/parser/languages/tree-sitter-php.wasm +0 -0
- package/src/parser/languages/tree-sitter-php_only.wasm +0 -0
- package/src/parser/languages/tree-sitter-python.wasm +0 -0
|
@@ -0,0 +1,1527 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HonoExtractor — AST-based extractor for Hono framework applications.
|
|
3
|
+
*
|
|
4
|
+
* Parses a Hono entry file using tree-sitter TypeScript grammar and produces
|
|
5
|
+
* a HonoAppModel that describes routes, middleware, context flow, OpenAPI
|
|
6
|
+
* schemas, and RPC exports.
|
|
7
|
+
*
|
|
8
|
+
* Spec: docs/specs/2026-04-10-hono-framework-intelligence-spec.md
|
|
9
|
+
* Plan: docs/specs/2026-04-10-hono-framework-intelligence-plan.md (Task 2-3)
|
|
10
|
+
*/
|
|
11
|
+
import { readFile } from "node:fs/promises";
|
|
12
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { getParser } from "../parser-manager.js";
|
|
15
|
+
import { HonoInlineAnalyzer } from "./hono-inline-analyzer.js";
|
|
16
|
+
const HTTP_METHODS = new Set([
|
|
17
|
+
"get", "post", "put", "delete", "patch", "options", "all", "on",
|
|
18
|
+
]);
|
|
19
|
+
export class HonoExtractor {
|
|
20
|
+
inlineAnalyzer = new HonoInlineAnalyzer();
|
|
21
|
+
async parse(entryFile) {
|
|
22
|
+
const absoluteEntry = canonicalize(path.resolve(entryFile));
|
|
23
|
+
const model = emptyModel(absoluteEntry, {});
|
|
24
|
+
const parsedCache = new Map();
|
|
25
|
+
await this.parseFile(absoluteEntry, "", new Set(), parsedCache, model);
|
|
26
|
+
// Post-pass runtime upgrade: if the entry-file-only detector left runtime
|
|
27
|
+
// as "unknown" but an imported file (e.g. bindings.ts) contains a CF
|
|
28
|
+
// Worker type, promote runtime to "cloudflare". Real Hono apps commonly
|
|
29
|
+
// isolate Bindings type definitions in a separate file.
|
|
30
|
+
if (model.runtime === "unknown") {
|
|
31
|
+
model.runtime = await this.upgradeRuntimeFromImports(model.files_used);
|
|
32
|
+
}
|
|
33
|
+
return model;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Post-parse runtime upgrade. Scans each file in files_used (excluding the
|
|
37
|
+
* entry file, which was already checked) for Cloudflare Worker type
|
|
38
|
+
* references. Returns "cloudflare" on first match, "unknown" otherwise.
|
|
39
|
+
*/
|
|
40
|
+
async upgradeRuntimeFromImports(filesUsed) {
|
|
41
|
+
for (const file of filesUsed) {
|
|
42
|
+
let source;
|
|
43
|
+
try {
|
|
44
|
+
source = await readFile(file, "utf-8");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (hasCloudflareBindingsType(source))
|
|
50
|
+
return "cloudflare";
|
|
51
|
+
}
|
|
52
|
+
return "unknown";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Parse a single file and merge results into the model.
|
|
56
|
+
* @param file Absolute canonicalized path
|
|
57
|
+
* @param prefix Path prefix to apply to all routes (from parent app.route())
|
|
58
|
+
* @param inFlight In-flight stack for cycle detection (pushed/popped per call)
|
|
59
|
+
* @param parsedCache Memoized child models keyed by canonical path
|
|
60
|
+
* @param model Accumulated model being built
|
|
61
|
+
*/
|
|
62
|
+
async parseFile(file, prefix, inFlight, parsedCache, model) {
|
|
63
|
+
// Cycle detection: break if this file is already being parsed up the call stack
|
|
64
|
+
if (inFlight.has(file))
|
|
65
|
+
return;
|
|
66
|
+
// Record file as used
|
|
67
|
+
if (!model.files_used.includes(file)) {
|
|
68
|
+
model.files_used.push(file);
|
|
69
|
+
}
|
|
70
|
+
// Check memoization cache for previously parsed child
|
|
71
|
+
const cached = parsedCache.get(file);
|
|
72
|
+
if (cached) {
|
|
73
|
+
// Re-use parsed routes with fresh prefix
|
|
74
|
+
// route.path already has basePath applied — apply mount prefix on top
|
|
75
|
+
for (const route of cached.routes) {
|
|
76
|
+
model.routes.push({
|
|
77
|
+
...route,
|
|
78
|
+
path: joinPaths(prefix, route.path),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
let source;
|
|
84
|
+
try {
|
|
85
|
+
source = await readFile(file, "utf-8");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
model.skip_reasons.file_read_failed =
|
|
89
|
+
(model.skip_reasons.file_read_failed ?? 0) + 1;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const language = pickLanguage(file);
|
|
93
|
+
const parser = await getParser(language);
|
|
94
|
+
if (!parser) {
|
|
95
|
+
model.skip_reasons.parser_unavailable =
|
|
96
|
+
(model.skip_reasons.parser_unavailable ?? 0) + 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const tree = parser.parse(source);
|
|
100
|
+
if (!tree) {
|
|
101
|
+
model.skip_reasons.parse_failed =
|
|
102
|
+
(model.skip_reasons.parse_failed ?? 0) + 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
// Track local data
|
|
107
|
+
const localAppVars = {};
|
|
108
|
+
const localRoutes = [];
|
|
109
|
+
const localMounts = [];
|
|
110
|
+
const importMap = this.extractImportMap(tree.rootNode, file);
|
|
111
|
+
// Walk for Hono app variables
|
|
112
|
+
this.walkAppVariables(tree.rootNode, file, localAppVars);
|
|
113
|
+
// Merge local app variables into model
|
|
114
|
+
for (const [name, app] of Object.entries(localAppVars)) {
|
|
115
|
+
model.app_variables[name] = app;
|
|
116
|
+
}
|
|
117
|
+
// Walk for HTTP routes (not app.route — those are handled separately)
|
|
118
|
+
this.walkHttpRoutes(tree.rootNode, file, localAppVars, localRoutes);
|
|
119
|
+
// Apply mount prefix on top of routes (route.path already has basePath)
|
|
120
|
+
for (const route of localRoutes) {
|
|
121
|
+
model.routes.push({
|
|
122
|
+
...route,
|
|
123
|
+
path: joinPaths(prefix, route.path),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Walk for context flow: c.set(), c.get(), c.var.*, c.env.*
|
|
127
|
+
this.walkContextFlow(tree.rootNode, file, model);
|
|
128
|
+
// Walk for middleware chains: app.use("scope", mw1, mw2, ...)
|
|
129
|
+
this.walkMiddleware(tree.rootNode, file, localAppVars, importMap, model);
|
|
130
|
+
// Walk for RPC type exports: export type AppType = typeof app
|
|
131
|
+
this.walkRPCExports(tree.rootNode, file, localAppVars, model);
|
|
132
|
+
// Walk for OpenAPI: createRoute() definitions + app.openapi() registrations
|
|
133
|
+
this.walkOpenAPI(tree.rootNode, file, localAppVars, prefix, model);
|
|
134
|
+
// Scan imported files for context flow (middleware files like auth.ts)
|
|
135
|
+
for (const [, importedFile] of importMap) {
|
|
136
|
+
if (!model.files_used.includes(importedFile)) {
|
|
137
|
+
model.files_used.push(importedFile);
|
|
138
|
+
}
|
|
139
|
+
await this.scanFileForContextFlow(importedFile, model);
|
|
140
|
+
}
|
|
141
|
+
// Detect runtime and env bindings (only for entry file)
|
|
142
|
+
if (file === model.entry_file) {
|
|
143
|
+
model.runtime = await this.detectRuntime(file);
|
|
144
|
+
this.extractEnvBindings(tree.rootNode, source, model);
|
|
145
|
+
}
|
|
146
|
+
// Cache local parse result (routes stored with raw_path, prefix applied on read)
|
|
147
|
+
parsedCache.set(file, {
|
|
148
|
+
app_variables: localAppVars,
|
|
149
|
+
routes: localRoutes,
|
|
150
|
+
mounts: localMounts,
|
|
151
|
+
files_used: [file],
|
|
152
|
+
});
|
|
153
|
+
// Walk for app.route() mounts — recursive into child files
|
|
154
|
+
inFlight.add(file);
|
|
155
|
+
await this.walkRouteMounts(tree.rootNode, file, prefix, localAppVars, importMap, inFlight, parsedCache, model, localMounts);
|
|
156
|
+
inFlight.delete(file);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
tree.delete();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build a map of { variableName → absoluteFilePath } from import statements.
|
|
164
|
+
*/
|
|
165
|
+
extractImportMap(root, currentFile) {
|
|
166
|
+
const importMap = new Map();
|
|
167
|
+
const cursor = root.walk();
|
|
168
|
+
walk(cursor, (node) => {
|
|
169
|
+
if (node.type !== "import_statement")
|
|
170
|
+
return;
|
|
171
|
+
const sourceNode = node.childForFieldName("source");
|
|
172
|
+
if (!sourceNode)
|
|
173
|
+
return;
|
|
174
|
+
const specifier = stringLiteralValue(sourceNode);
|
|
175
|
+
if (!specifier || !specifier.startsWith("."))
|
|
176
|
+
return;
|
|
177
|
+
const resolved = resolveImportPath(currentFile, specifier);
|
|
178
|
+
if (!resolved)
|
|
179
|
+
return;
|
|
180
|
+
// Default import: import X from "./file"
|
|
181
|
+
const importClause = node.children.find((c) => c.type === "import_clause");
|
|
182
|
+
if (!importClause)
|
|
183
|
+
return;
|
|
184
|
+
for (const child of importClause.namedChildren) {
|
|
185
|
+
if (child.type === "identifier") {
|
|
186
|
+
// default import
|
|
187
|
+
importMap.set(child.text, resolved);
|
|
188
|
+
}
|
|
189
|
+
if (child.type === "named_imports") {
|
|
190
|
+
for (const spec of child.namedChildren) {
|
|
191
|
+
if (spec.type === "import_specifier") {
|
|
192
|
+
const alias = spec.childForFieldName("alias");
|
|
193
|
+
const name = spec.childForFieldName("name");
|
|
194
|
+
const varName = alias?.text ?? name?.text;
|
|
195
|
+
if (varName)
|
|
196
|
+
importMap.set(varName, resolved);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
return importMap;
|
|
203
|
+
}
|
|
204
|
+
walkAppVariables(root, file, localVars) {
|
|
205
|
+
// First pass: detect factory variables (createFactory<Env>())
|
|
206
|
+
const factoryVars = new Set();
|
|
207
|
+
const cursor1 = root.walk();
|
|
208
|
+
walk(cursor1, (node) => {
|
|
209
|
+
if (node.type !== "variable_declarator")
|
|
210
|
+
return;
|
|
211
|
+
const nameNode = node.childForFieldName("name");
|
|
212
|
+
const valueNode = node.childForFieldName("value");
|
|
213
|
+
if (!nameNode || !valueNode || nameNode.type !== "identifier")
|
|
214
|
+
return;
|
|
215
|
+
if (isCreateFactoryCall(valueNode)) {
|
|
216
|
+
factoryVars.add(nameNode.text);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
// Second pass: detect Hono app variables
|
|
220
|
+
const cursor = root.walk();
|
|
221
|
+
walk(cursor, (node) => {
|
|
222
|
+
if (node.type !== "variable_declarator")
|
|
223
|
+
return;
|
|
224
|
+
const nameNode = node.childForFieldName("name");
|
|
225
|
+
const valueNode = node.childForFieldName("value");
|
|
226
|
+
if (!nameNode || !valueNode)
|
|
227
|
+
return;
|
|
228
|
+
if (nameNode.type !== "identifier")
|
|
229
|
+
return;
|
|
230
|
+
const name = nameNode.text;
|
|
231
|
+
// Direct creation: new Hono(), new OpenAPIHono()
|
|
232
|
+
const createdVia = classifyAppCreation(valueNode);
|
|
233
|
+
if (createdVia) {
|
|
234
|
+
localVars[name] = {
|
|
235
|
+
variable_name: name,
|
|
236
|
+
file,
|
|
237
|
+
line: nameNode.startPosition.row + 1,
|
|
238
|
+
created_via: createdVia,
|
|
239
|
+
base_path: "",
|
|
240
|
+
};
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
// basePath derivation: const v1 = app.basePath("/v1")
|
|
244
|
+
const basePath = extractBasePathCall(valueNode, localVars);
|
|
245
|
+
if (basePath) {
|
|
246
|
+
localVars[name] = {
|
|
247
|
+
variable_name: name,
|
|
248
|
+
file,
|
|
249
|
+
line: nameNode.startPosition.row + 1,
|
|
250
|
+
created_via: "basePath",
|
|
251
|
+
base_path: basePath.prefix,
|
|
252
|
+
parent: basePath.parentVar,
|
|
253
|
+
};
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// factory.createApp(): const api = factory.createApp()
|
|
257
|
+
if (isFactoryCreateApp(valueNode, factoryVars)) {
|
|
258
|
+
localVars[name] = {
|
|
259
|
+
variable_name: name,
|
|
260
|
+
file,
|
|
261
|
+
line: nameNode.startPosition.row + 1,
|
|
262
|
+
created_via: "factory.createApp",
|
|
263
|
+
base_path: "",
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
walkHttpRoutes(root, file, appVars, routes) {
|
|
269
|
+
const cursor = root.walk();
|
|
270
|
+
walk(cursor, (node) => {
|
|
271
|
+
if (node.type !== "call_expression")
|
|
272
|
+
return;
|
|
273
|
+
const fnNode = node.childForFieldName("function");
|
|
274
|
+
const argsNode = node.childForFieldName("arguments");
|
|
275
|
+
if (!fnNode || !argsNode || fnNode.type !== "member_expression")
|
|
276
|
+
return;
|
|
277
|
+
const objectNode = fnNode.childForFieldName("object");
|
|
278
|
+
const propertyNode = fnNode.childForFieldName("property");
|
|
279
|
+
if (!objectNode || !propertyNode || objectNode.type !== "identifier")
|
|
280
|
+
return;
|
|
281
|
+
const ownerVar = objectNode.text;
|
|
282
|
+
const appDef = appVars[ownerVar];
|
|
283
|
+
if (!appDef)
|
|
284
|
+
return;
|
|
285
|
+
const method = propertyNode.text.toLowerCase();
|
|
286
|
+
if (!HTTP_METHODS.has(method) || method === "use" || method === "route")
|
|
287
|
+
return;
|
|
288
|
+
const argList = argsNode.namedChildren;
|
|
289
|
+
if (argList.length === 0)
|
|
290
|
+
return;
|
|
291
|
+
// basePath prefix inherited from the HonoApp variable
|
|
292
|
+
const basePrefix = appDef.base_path || "";
|
|
293
|
+
// Handle app.on(["GET", "POST"], "/path", handler) — fan out
|
|
294
|
+
if (method === "on") {
|
|
295
|
+
this.handleOnMethod(argList, file, node, ownerVar, basePrefix, routes);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const firstArg = argList[0];
|
|
299
|
+
if (!firstArg)
|
|
300
|
+
return;
|
|
301
|
+
const rawPath = stringLiteralValue(firstArg);
|
|
302
|
+
if (rawPath == null)
|
|
303
|
+
return;
|
|
304
|
+
// Resolve once so buildHandler and analyzeHandlerIfInline see the same node.
|
|
305
|
+
const handlerNode = argList[argList.length - 1] ?? firstArg;
|
|
306
|
+
const handler = buildHandler(handlerNode, file);
|
|
307
|
+
const regexConstraint = parseRegexConstraints(rawPath);
|
|
308
|
+
const inlineAnalysis = this.analyzeHandlerIfInline(handler, handlerNode);
|
|
309
|
+
routes.push({
|
|
310
|
+
method: method.toUpperCase(),
|
|
311
|
+
path: joinPaths(basePrefix, rawPath),
|
|
312
|
+
raw_path: rawPath,
|
|
313
|
+
file,
|
|
314
|
+
line: node.startPosition.row + 1,
|
|
315
|
+
owner_var: ownerVar,
|
|
316
|
+
handler,
|
|
317
|
+
inline_middleware: [],
|
|
318
|
+
validators: [],
|
|
319
|
+
...(regexConstraint ? { regex_constraint: regexConstraint } : {}),
|
|
320
|
+
...(inlineAnalysis ? { inline_analysis: inlineAnalysis } : {}),
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Run InlineHandlerAnalyzer on the handler AST node when buildHandler
|
|
326
|
+
* classified the handler as inline. Returns undefined for named-identifier
|
|
327
|
+
* handlers — those are defined elsewhere and analyzed via their symbol.
|
|
328
|
+
* The caller must pass the SAME node used by buildHandler so the two
|
|
329
|
+
* decisions cannot disagree.
|
|
330
|
+
*/
|
|
331
|
+
analyzeHandlerIfInline(handler, handlerNode) {
|
|
332
|
+
if (!handler.inline)
|
|
333
|
+
return undefined;
|
|
334
|
+
return this.inlineAnalyzer.analyze(handlerNode);
|
|
335
|
+
}
|
|
336
|
+
/** Handle app.on(methods, path, handler) — fan out into per-method routes. */
|
|
337
|
+
handleOnMethod(argList, file, node, ownerVar, basePrefix, routes) {
|
|
338
|
+
if (argList.length < 2)
|
|
339
|
+
return;
|
|
340
|
+
const methodsArg = argList[0];
|
|
341
|
+
const pathArg = argList[1];
|
|
342
|
+
if (!methodsArg || !pathArg)
|
|
343
|
+
return;
|
|
344
|
+
// Methods: string or array of strings
|
|
345
|
+
const methods = [];
|
|
346
|
+
if (methodsArg.type === "array") {
|
|
347
|
+
for (const el of methodsArg.namedChildren) {
|
|
348
|
+
const v = stringLiteralValue(el);
|
|
349
|
+
if (v)
|
|
350
|
+
methods.push(v.toUpperCase());
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
const v = stringLiteralValue(methodsArg);
|
|
355
|
+
if (v)
|
|
356
|
+
methods.push(v.toUpperCase());
|
|
357
|
+
}
|
|
358
|
+
if (methods.length === 0)
|
|
359
|
+
return;
|
|
360
|
+
const rawPath = stringLiteralValue(pathArg);
|
|
361
|
+
if (rawPath == null)
|
|
362
|
+
return;
|
|
363
|
+
// Resolve once so buildHandler and analyzeHandlerIfInline see the same node.
|
|
364
|
+
const handlerNode = argList[argList.length - 1] ?? pathArg;
|
|
365
|
+
const handler = buildHandler(handlerNode, file);
|
|
366
|
+
const regexConstraint = parseRegexConstraints(rawPath);
|
|
367
|
+
const inlineAnalysis = this.analyzeHandlerIfInline(handler, handlerNode);
|
|
368
|
+
for (const m of methods) {
|
|
369
|
+
routes.push({
|
|
370
|
+
method: m,
|
|
371
|
+
path: joinPaths(basePrefix, rawPath),
|
|
372
|
+
raw_path: rawPath,
|
|
373
|
+
file,
|
|
374
|
+
line: node.startPosition.row + 1,
|
|
375
|
+
owner_var: ownerVar,
|
|
376
|
+
handler,
|
|
377
|
+
inline_middleware: [],
|
|
378
|
+
validators: [],
|
|
379
|
+
...(regexConstraint ? { regex_constraint: regexConstraint } : {}),
|
|
380
|
+
...(inlineAnalysis ? { inline_analysis: inlineAnalysis } : {}),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Walk for app.use(scope, mw1, mw2, ...) calls.
|
|
386
|
+
* Handles: identifiers, inline arrows, some()/every() from hono/combine,
|
|
387
|
+
* spread arrays, call expressions like cors().
|
|
388
|
+
*/
|
|
389
|
+
walkMiddleware(root, file, appVars, _importMap, model) {
|
|
390
|
+
// Build local variable → array declaration map for spread expansion
|
|
391
|
+
const arrayVars = this.collectArrayVars(root);
|
|
392
|
+
// Identify which imported names come from which packages
|
|
393
|
+
const importSources = this.collectImportSources(root);
|
|
394
|
+
const cursor = root.walk();
|
|
395
|
+
walk(cursor, (node) => {
|
|
396
|
+
if (node.type !== "call_expression")
|
|
397
|
+
return;
|
|
398
|
+
const fnNode = node.childForFieldName("function");
|
|
399
|
+
const argsNode = node.childForFieldName("arguments");
|
|
400
|
+
if (!fnNode || !argsNode || fnNode.type !== "member_expression")
|
|
401
|
+
return;
|
|
402
|
+
const objectNode = fnNode.childForFieldName("object");
|
|
403
|
+
const propertyNode = fnNode.childForFieldName("property");
|
|
404
|
+
if (!objectNode || !propertyNode || objectNode.type !== "identifier")
|
|
405
|
+
return;
|
|
406
|
+
const ownerVar = objectNode.text;
|
|
407
|
+
if (!appVars[ownerVar])
|
|
408
|
+
return;
|
|
409
|
+
if (propertyNode.text !== "use")
|
|
410
|
+
return;
|
|
411
|
+
const argList = argsNode.namedChildren;
|
|
412
|
+
if (argList.length === 0)
|
|
413
|
+
return;
|
|
414
|
+
// First arg may be a scope path string, or directly a middleware
|
|
415
|
+
let scope = "*";
|
|
416
|
+
let mwStartIdx = 0;
|
|
417
|
+
const firstArg = argList[0];
|
|
418
|
+
if (firstArg) {
|
|
419
|
+
const maybeScope = stringLiteralValue(firstArg);
|
|
420
|
+
if (maybeScope != null) {
|
|
421
|
+
scope = maybeScope;
|
|
422
|
+
mwStartIdx = 1;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const entries = [];
|
|
426
|
+
let order = 0;
|
|
427
|
+
for (let i = mwStartIdx; i < argList.length; i++) {
|
|
428
|
+
const arg = argList[i];
|
|
429
|
+
if (!arg)
|
|
430
|
+
continue;
|
|
431
|
+
order++;
|
|
432
|
+
// Spread: ...adminChain
|
|
433
|
+
if (arg.type === "spread_element") {
|
|
434
|
+
const inner = arg.namedChildren[0];
|
|
435
|
+
if (inner?.type === "identifier") {
|
|
436
|
+
const arrItems = arrayVars.get(inner.text);
|
|
437
|
+
if (arrItems) {
|
|
438
|
+
for (const item of arrItems) {
|
|
439
|
+
entries.push(this.buildMiddlewareEntry(item, file, node.startPosition.row + 1, order++, importSources, undefined));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
// some(mw1, mw2) / every(mw1, mw2) from hono/combine
|
|
446
|
+
if (arg.type === "call_expression") {
|
|
447
|
+
const callFn = arg.childForFieldName("function");
|
|
448
|
+
const callArgs = arg.childForFieldName("arguments");
|
|
449
|
+
if (callFn?.type === "identifier" &&
|
|
450
|
+
(callFn.text === "some" || callFn.text === "every") && callArgs) {
|
|
451
|
+
const combineType = callFn.text;
|
|
452
|
+
for (const innerArg of callArgs.namedChildren) {
|
|
453
|
+
entries.push(this.buildMiddlewareEntry(innerArg.type === "identifier" ? innerArg.text : "<inline>", file, innerArg.startPosition.row + 1, order++, importSources, combineType));
|
|
454
|
+
}
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Regular middleware: identifier, call expression, or inline arrow
|
|
459
|
+
const mwName = this.extractMiddlewareName(arg);
|
|
460
|
+
entries.push(this.buildMiddlewareEntry(mwName, file, arg.startPosition.row + 1, order, importSources, undefined));
|
|
461
|
+
// T4: If this is an inline arrow, scan its body for conditional
|
|
462
|
+
// middleware calls like `if (cond) return mw(c, next)`. Each match
|
|
463
|
+
// produces an ADDITIONAL entry with applied_when set.
|
|
464
|
+
if (arg.type === "arrow_function" ||
|
|
465
|
+
arg.type === "function_expression") {
|
|
466
|
+
const conditional = this.detectConditionalMiddlewareCalls(arg);
|
|
467
|
+
for (const found of conditional) {
|
|
468
|
+
order++;
|
|
469
|
+
const extra = this.buildMiddlewareEntry(found.name, file, found.line, order, importSources, undefined);
|
|
470
|
+
extra.conditional = true;
|
|
471
|
+
extra.applied_when = found.applied_when;
|
|
472
|
+
entries.push(extra);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (entries.length > 0) {
|
|
477
|
+
// Merge into existing chain for same scope, or create new
|
|
478
|
+
const existing = model.middleware_chains.find((mc) => mc.scope === scope && mc.owner_var === ownerVar);
|
|
479
|
+
if (existing) {
|
|
480
|
+
existing.entries.push(...entries);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// scope_pattern is the raw scope (e.g., "*" or "/api/*") — downstream
|
|
484
|
+
// tools compile to regex via compileScopePattern()
|
|
485
|
+
model.middleware_chains.push({
|
|
486
|
+
scope,
|
|
487
|
+
scope_pattern: scope,
|
|
488
|
+
owner_var: ownerVar,
|
|
489
|
+
entries,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* T4: walk an inline middleware arrow body and surface any conditional
|
|
497
|
+
* calls of the form `if (cond) return mw(c, next)` or `if (cond) await mw(c, next)`.
|
|
498
|
+
*
|
|
499
|
+
* We only inspect the DIRECT `if` statements at the top of the body (one level
|
|
500
|
+
* of `statement_block` / `return_statement`). Deep nesting is out of scope to
|
|
501
|
+
* keep false positives low.
|
|
502
|
+
*
|
|
503
|
+
* Returns an entry per conditional call found, with name + condition info.
|
|
504
|
+
*/
|
|
505
|
+
detectConditionalMiddlewareCalls(fnNode) {
|
|
506
|
+
const results = [];
|
|
507
|
+
// Arrow function body is either an expression or a statement_block
|
|
508
|
+
const block = fnNode.childForFieldName("body");
|
|
509
|
+
if (!block)
|
|
510
|
+
return results;
|
|
511
|
+
// Only walk if the arrow body is a statement_block — expression-body
|
|
512
|
+
// arrows `(c) => foo(c, next)` are NOT conditional by definition.
|
|
513
|
+
if (block.type !== "statement_block")
|
|
514
|
+
return results;
|
|
515
|
+
for (let i = 0; i < block.childCount; i++) {
|
|
516
|
+
const stmt = block.child(i);
|
|
517
|
+
if (stmt?.type !== "if_statement")
|
|
518
|
+
continue;
|
|
519
|
+
const condition = stmt.childForFieldName("condition");
|
|
520
|
+
const consequence = stmt.childForFieldName("consequence");
|
|
521
|
+
if (!condition || !consequence)
|
|
522
|
+
continue;
|
|
523
|
+
// Resolve block-local `const x = mwFactory({...})` aliases so that
|
|
524
|
+
// const auth = basicAuth({...});
|
|
525
|
+
// return auth(c, next);
|
|
526
|
+
// reports "basicAuth" instead of "auth".
|
|
527
|
+
const localAliases = collectLocalAliases(consequence);
|
|
528
|
+
// Find mw call inside the consequence.
|
|
529
|
+
const mwCall = findMiddlewareCallInBlock(consequence);
|
|
530
|
+
if (!mwCall)
|
|
531
|
+
continue;
|
|
532
|
+
const rawName = extractCallCalleeName(mwCall);
|
|
533
|
+
if (!rawName)
|
|
534
|
+
continue;
|
|
535
|
+
const name = localAliases.get(rawName) ?? rawName;
|
|
536
|
+
const condText = condition.text.slice(0, 200);
|
|
537
|
+
const applied_when = {
|
|
538
|
+
condition_type: classifyConditionType(condition),
|
|
539
|
+
condition_text: condText,
|
|
540
|
+
};
|
|
541
|
+
results.push({
|
|
542
|
+
name,
|
|
543
|
+
line: mwCall.startPosition.row + 1,
|
|
544
|
+
applied_when,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
return results;
|
|
548
|
+
}
|
|
549
|
+
extractMiddlewareName(node) {
|
|
550
|
+
if (node.type === "identifier")
|
|
551
|
+
return node.text;
|
|
552
|
+
if (node.type === "arrow_function" || node.type === "function_expression") {
|
|
553
|
+
return "<inline>";
|
|
554
|
+
}
|
|
555
|
+
if (node.type === "call_expression") {
|
|
556
|
+
const fn = node.childForFieldName("function");
|
|
557
|
+
if (fn?.type === "identifier")
|
|
558
|
+
return fn.text;
|
|
559
|
+
if (fn?.type === "member_expression") {
|
|
560
|
+
const prop = fn.childForFieldName("property");
|
|
561
|
+
return prop?.text ?? "<inline>";
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return "<inline>";
|
|
565
|
+
}
|
|
566
|
+
buildMiddlewareEntry(name, file, line, order, importSources, expandedFrom) {
|
|
567
|
+
const importedFrom = importSources.get(name);
|
|
568
|
+
const isThirdParty = !!importedFrom && (importedFrom.startsWith("hono/") ||
|
|
569
|
+
!importedFrom.startsWith("."));
|
|
570
|
+
const entry = {
|
|
571
|
+
name,
|
|
572
|
+
order,
|
|
573
|
+
line,
|
|
574
|
+
file,
|
|
575
|
+
inline: name === "<inline>",
|
|
576
|
+
is_third_party: isThirdParty,
|
|
577
|
+
conditional: expandedFrom === "some",
|
|
578
|
+
};
|
|
579
|
+
if (importedFrom)
|
|
580
|
+
entry.imported_from = importedFrom;
|
|
581
|
+
if (expandedFrom)
|
|
582
|
+
entry.expanded_from = expandedFrom;
|
|
583
|
+
return entry;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Walk for `export type X = typeof varName` declarations.
|
|
587
|
+
* Classifies as "full_app" if varName is the root app (has mounts/middleware on it),
|
|
588
|
+
* or "route_group" if it's a sub-router without child mounts.
|
|
589
|
+
*/
|
|
590
|
+
walkRPCExports(root, file, appVars, model) {
|
|
591
|
+
const cursor = root.walk();
|
|
592
|
+
walk(cursor, (node) => {
|
|
593
|
+
// export_statement > type_alias_declaration
|
|
594
|
+
if (node.type !== "export_statement")
|
|
595
|
+
return;
|
|
596
|
+
const typeAlias = node.namedChildren.find((c) => c.type === "type_alias_declaration");
|
|
597
|
+
if (!typeAlias)
|
|
598
|
+
return;
|
|
599
|
+
const nameNode = typeAlias.childForFieldName("name");
|
|
600
|
+
const valueNode = typeAlias.childForFieldName("value");
|
|
601
|
+
if (!nameNode || !valueNode)
|
|
602
|
+
return;
|
|
603
|
+
// typeof <varName>
|
|
604
|
+
if (valueNode.type !== "type_query")
|
|
605
|
+
return;
|
|
606
|
+
const queryArg = valueNode.namedChildren[0];
|
|
607
|
+
if (!queryArg || queryArg.type !== "identifier")
|
|
608
|
+
return;
|
|
609
|
+
const sourceVar = queryArg.text;
|
|
610
|
+
// Must reference a known Hono app variable
|
|
611
|
+
if (!appVars[sourceVar])
|
|
612
|
+
return;
|
|
613
|
+
// Classify: full_app if the variable is the root entry (has mounts), else route_group
|
|
614
|
+
const hasMounts = model.mounts.some((m) => m.parent_var === sourceVar);
|
|
615
|
+
const hasMiddleware = model.middleware_chains.some((mc) => mc.owner_var === sourceVar);
|
|
616
|
+
const shape = (hasMounts || hasMiddleware) ? "full_app" : "route_group";
|
|
617
|
+
model.rpc_exports.push({
|
|
618
|
+
export_name: nameNode.text,
|
|
619
|
+
file,
|
|
620
|
+
line: node.startPosition.row + 1,
|
|
621
|
+
shape,
|
|
622
|
+
source_var: sourceVar,
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Walk for createRoute() definitions and app.openapi(route, handler) registrations.
|
|
628
|
+
* - createRoute({method, path, ...}) → tracked in a local map by variable name
|
|
629
|
+
* - app.openapi(routeVar, handler) → emits OpenAPIRoute + adds to model.routes
|
|
630
|
+
*/
|
|
631
|
+
walkOpenAPI(root, file, appVars, prefix, model) {
|
|
632
|
+
// First pass: collect createRoute() variable definitions
|
|
633
|
+
const routeDefs = new Map();
|
|
634
|
+
const cursor1 = root.walk();
|
|
635
|
+
walk(cursor1, (node) => {
|
|
636
|
+
if (node.type !== "variable_declarator")
|
|
637
|
+
return;
|
|
638
|
+
const nameNode = node.childForFieldName("name");
|
|
639
|
+
const valueNode = node.childForFieldName("value");
|
|
640
|
+
if (!nameNode || !valueNode || nameNode.type !== "identifier")
|
|
641
|
+
return;
|
|
642
|
+
if (valueNode.type !== "call_expression")
|
|
643
|
+
return;
|
|
644
|
+
const fn = valueNode.childForFieldName("function");
|
|
645
|
+
if (!fn || fn.text !== "createRoute")
|
|
646
|
+
return;
|
|
647
|
+
const args = valueNode.childForFieldName("arguments");
|
|
648
|
+
const objArg = args?.namedChildren[0];
|
|
649
|
+
if (!objArg || objArg.type !== "object")
|
|
650
|
+
return;
|
|
651
|
+
let method = "";
|
|
652
|
+
let routePath = "";
|
|
653
|
+
for (const prop of objArg.namedChildren) {
|
|
654
|
+
if (prop.type !== "pair")
|
|
655
|
+
continue;
|
|
656
|
+
const key = prop.childForFieldName("key");
|
|
657
|
+
const val = prop.childForFieldName("value");
|
|
658
|
+
if (!key || !val)
|
|
659
|
+
continue;
|
|
660
|
+
if (key.text === "method") {
|
|
661
|
+
method = stringLiteralValue(val) ?? "";
|
|
662
|
+
}
|
|
663
|
+
if (key.text === "path") {
|
|
664
|
+
routePath = stringLiteralValue(val) ?? "";
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (method && routePath) {
|
|
668
|
+
routeDefs.set(nameNode.text, {
|
|
669
|
+
method,
|
|
670
|
+
path: routePath,
|
|
671
|
+
line: node.startPosition.row + 1,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
// Second pass: find app.openapi(routeVar, handler) calls
|
|
676
|
+
const cursor2 = root.walk();
|
|
677
|
+
walk(cursor2, (node) => {
|
|
678
|
+
if (node.type !== "call_expression")
|
|
679
|
+
return;
|
|
680
|
+
const fnNode = node.childForFieldName("function");
|
|
681
|
+
const argsNode = node.childForFieldName("arguments");
|
|
682
|
+
if (!fnNode || !argsNode || fnNode.type !== "member_expression")
|
|
683
|
+
return;
|
|
684
|
+
const obj = fnNode.childForFieldName("object");
|
|
685
|
+
const prop = fnNode.childForFieldName("property");
|
|
686
|
+
if (!obj || !prop || obj.type !== "identifier")
|
|
687
|
+
return;
|
|
688
|
+
if (!appVars[obj.text])
|
|
689
|
+
return;
|
|
690
|
+
if (prop.text !== "openapi")
|
|
691
|
+
return;
|
|
692
|
+
const argList = argsNode.namedChildren;
|
|
693
|
+
if (argList.length < 2)
|
|
694
|
+
return;
|
|
695
|
+
const routeRef = argList[0];
|
|
696
|
+
const handlerArg = argList[1];
|
|
697
|
+
if (!routeRef || !handlerArg)
|
|
698
|
+
return;
|
|
699
|
+
if (routeRef.type !== "identifier")
|
|
700
|
+
return;
|
|
701
|
+
const routeDef = routeDefs.get(routeRef.text);
|
|
702
|
+
if (!routeDef)
|
|
703
|
+
return;
|
|
704
|
+
const honoPath = routeDef.path.replace(/\{(\w+)\}/g, ":$1");
|
|
705
|
+
const openapiRoute = {
|
|
706
|
+
id: `openapi_${routeRef.text}`,
|
|
707
|
+
method: routeDef.method,
|
|
708
|
+
path: routeDef.path,
|
|
709
|
+
hono_path: honoPath,
|
|
710
|
+
request_schemas: {},
|
|
711
|
+
response_schemas: {},
|
|
712
|
+
middleware: [],
|
|
713
|
+
hidden: false,
|
|
714
|
+
file,
|
|
715
|
+
line: routeDef.line,
|
|
716
|
+
};
|
|
717
|
+
model.openapi_routes.push(openapiRoute);
|
|
718
|
+
// Also add as a regular route
|
|
719
|
+
const handler = buildHandler(handlerArg, file);
|
|
720
|
+
model.routes.push({
|
|
721
|
+
method: routeDef.method.toUpperCase(),
|
|
722
|
+
path: joinPaths(prefix, honoPath),
|
|
723
|
+
raw_path: honoPath,
|
|
724
|
+
file,
|
|
725
|
+
line: node.startPosition.row + 1,
|
|
726
|
+
owner_var: obj.text,
|
|
727
|
+
handler,
|
|
728
|
+
inline_middleware: [],
|
|
729
|
+
validators: [],
|
|
730
|
+
openapi_route_id: openapiRoute.id,
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Lightweight scan of an imported file (e.g., middleware) for context flow only.
|
|
736
|
+
* Does not extract routes or mounts — just c.set/c.get/c.var patterns.
|
|
737
|
+
*/
|
|
738
|
+
async scanFileForContextFlow(file, model) {
|
|
739
|
+
let source;
|
|
740
|
+
try {
|
|
741
|
+
source = await readFile(file, "utf-8");
|
|
742
|
+
}
|
|
743
|
+
catch {
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const parser = await getParser(pickLanguage(file));
|
|
747
|
+
if (!parser)
|
|
748
|
+
return;
|
|
749
|
+
const tree = parser.parse(source);
|
|
750
|
+
if (!tree)
|
|
751
|
+
return;
|
|
752
|
+
try {
|
|
753
|
+
this.walkContextFlow(tree.rootNode, file, model);
|
|
754
|
+
}
|
|
755
|
+
finally {
|
|
756
|
+
tree.delete();
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Walk for context variable flow: c.set("key", val), c.get("key"), c.var.key, c.env.KEY.
|
|
761
|
+
* Detects conditional sets (inside if/try/switch blocks).
|
|
762
|
+
*/
|
|
763
|
+
walkContextFlow(root, file, model) {
|
|
764
|
+
const varsMap = new Map();
|
|
765
|
+
const getOrCreate = (name, isEnv) => {
|
|
766
|
+
let cv = varsMap.get(name);
|
|
767
|
+
if (!cv) {
|
|
768
|
+
cv = { name, set_points: [], get_points: [], is_env_binding: isEnv };
|
|
769
|
+
varsMap.set(name, cv);
|
|
770
|
+
}
|
|
771
|
+
return cv;
|
|
772
|
+
};
|
|
773
|
+
const cursor = root.walk();
|
|
774
|
+
walk(cursor, (node) => {
|
|
775
|
+
// c.set("key", value)
|
|
776
|
+
if (node.type === "call_expression") {
|
|
777
|
+
const fn = node.childForFieldName("function");
|
|
778
|
+
if (fn?.type === "member_expression") {
|
|
779
|
+
const obj = fn.childForFieldName("object");
|
|
780
|
+
const prop = fn.childForFieldName("property");
|
|
781
|
+
if (obj?.text === "c" && prop?.text === "set") {
|
|
782
|
+
const args = node.childForFieldName("arguments");
|
|
783
|
+
const keyArg = args?.namedChildren[0];
|
|
784
|
+
if (keyArg) {
|
|
785
|
+
const key = stringLiteralValue(keyArg);
|
|
786
|
+
if (key) {
|
|
787
|
+
const cv = getOrCreate(key, false);
|
|
788
|
+
cv.set_points.push({
|
|
789
|
+
file,
|
|
790
|
+
line: node.startPosition.row + 1,
|
|
791
|
+
scope: "middleware",
|
|
792
|
+
via_context_storage: false,
|
|
793
|
+
condition: isInsideBranch(node) ? "conditional" : "always",
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// c.get("key")
|
|
799
|
+
if (obj?.text === "c" && prop?.text === "get") {
|
|
800
|
+
const args = node.childForFieldName("arguments");
|
|
801
|
+
const keyArg = args?.namedChildren[0];
|
|
802
|
+
if (keyArg) {
|
|
803
|
+
const key = stringLiteralValue(keyArg);
|
|
804
|
+
if (key) {
|
|
805
|
+
const cv = getOrCreate(key, false);
|
|
806
|
+
cv.get_points.push({
|
|
807
|
+
file,
|
|
808
|
+
line: node.startPosition.row + 1,
|
|
809
|
+
scope: "handler",
|
|
810
|
+
via_context_storage: false,
|
|
811
|
+
condition: "always",
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
// c.var.key — member_expression chain: c.var.key → (c.var).key
|
|
819
|
+
if (node.type === "member_expression") {
|
|
820
|
+
const obj = node.childForFieldName("object");
|
|
821
|
+
const prop = node.childForFieldName("property");
|
|
822
|
+
if (obj?.type === "member_expression" && prop) {
|
|
823
|
+
const innerObj = obj.childForFieldName("object");
|
|
824
|
+
const innerProp = obj.childForFieldName("property");
|
|
825
|
+
if (innerObj?.text === "c" && innerProp?.text === "var") {
|
|
826
|
+
const cv = getOrCreate(prop.text, false);
|
|
827
|
+
cv.get_points.push({
|
|
828
|
+
file,
|
|
829
|
+
line: node.startPosition.row + 1,
|
|
830
|
+
scope: "handler",
|
|
831
|
+
via_context_storage: false,
|
|
832
|
+
condition: "always",
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
// Merge into model.context_vars
|
|
839
|
+
for (const cv of varsMap.values()) {
|
|
840
|
+
const existing = model.context_vars.find((e) => e.name === cv.name);
|
|
841
|
+
if (existing) {
|
|
842
|
+
existing.set_points.push(...cv.set_points);
|
|
843
|
+
existing.get_points.push(...cv.get_points);
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
model.context_vars.push(cv);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
/** Collect local array variable declarations: const adminChain = [authMw, tenantMw] */
|
|
851
|
+
collectArrayVars(root) {
|
|
852
|
+
const result = new Map();
|
|
853
|
+
const cursor = root.walk();
|
|
854
|
+
walk(cursor, (node) => {
|
|
855
|
+
if (node.type !== "variable_declarator")
|
|
856
|
+
return;
|
|
857
|
+
const nameNode = node.childForFieldName("name");
|
|
858
|
+
const valueNode = node.childForFieldName("value");
|
|
859
|
+
if (!nameNode || !valueNode || nameNode.type !== "identifier")
|
|
860
|
+
return;
|
|
861
|
+
if (valueNode.type !== "array")
|
|
862
|
+
return;
|
|
863
|
+
const items = [];
|
|
864
|
+
for (const el of valueNode.namedChildren) {
|
|
865
|
+
if (el.type === "identifier")
|
|
866
|
+
items.push(el.text);
|
|
867
|
+
}
|
|
868
|
+
if (items.length > 0)
|
|
869
|
+
result.set(nameNode.text, items);
|
|
870
|
+
});
|
|
871
|
+
return result;
|
|
872
|
+
}
|
|
873
|
+
/** Collect import source mapping: variableName → packageSpecifier */
|
|
874
|
+
collectImportSources(root) {
|
|
875
|
+
const result = new Map();
|
|
876
|
+
const cursor = root.walk();
|
|
877
|
+
walk(cursor, (node) => {
|
|
878
|
+
if (node.type !== "import_statement")
|
|
879
|
+
return;
|
|
880
|
+
const sourceNode = node.childForFieldName("source");
|
|
881
|
+
if (!sourceNode)
|
|
882
|
+
return;
|
|
883
|
+
const specifier = stringLiteralValue(sourceNode);
|
|
884
|
+
if (!specifier)
|
|
885
|
+
return;
|
|
886
|
+
const importClause = node.children.find((c) => c.type === "import_clause");
|
|
887
|
+
if (!importClause)
|
|
888
|
+
return;
|
|
889
|
+
for (const child of importClause.namedChildren) {
|
|
890
|
+
if (child.type === "identifier") {
|
|
891
|
+
result.set(child.text, specifier);
|
|
892
|
+
}
|
|
893
|
+
if (child.type === "named_imports") {
|
|
894
|
+
for (const spec of child.namedChildren) {
|
|
895
|
+
if (spec.type === "import_specifier") {
|
|
896
|
+
const alias = spec.childForFieldName("alias");
|
|
897
|
+
const name = spec.childForFieldName("name");
|
|
898
|
+
const varName = alias?.text ?? name?.text;
|
|
899
|
+
if (varName)
|
|
900
|
+
result.set(varName, specifier);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Walk for app.route("/prefix", subApp) calls.
|
|
910
|
+
* Resolves subApp import, recursively parses the file.
|
|
911
|
+
*/
|
|
912
|
+
async walkRouteMounts(root, _file, parentPrefix, appVars, importMap, inFlight, parsedCache, model, localMounts) {
|
|
913
|
+
const mounts = [];
|
|
914
|
+
const cursor = root.walk();
|
|
915
|
+
walk(cursor, (node) => {
|
|
916
|
+
if (node.type !== "call_expression")
|
|
917
|
+
return;
|
|
918
|
+
const fnNode = node.childForFieldName("function");
|
|
919
|
+
const argsNode = node.childForFieldName("arguments");
|
|
920
|
+
if (!fnNode || !argsNode || fnNode.type !== "member_expression")
|
|
921
|
+
return;
|
|
922
|
+
const objectNode = fnNode.childForFieldName("object");
|
|
923
|
+
const propertyNode = fnNode.childForFieldName("property");
|
|
924
|
+
if (!objectNode || !propertyNode || objectNode.type !== "identifier")
|
|
925
|
+
return;
|
|
926
|
+
const ownerVar = objectNode.text;
|
|
927
|
+
if (!appVars[ownerVar])
|
|
928
|
+
return;
|
|
929
|
+
const methodName = propertyNode.text;
|
|
930
|
+
// app.mount("/legacy", handler) — non-Hono external handler mount
|
|
931
|
+
if (methodName === "mount") {
|
|
932
|
+
const args = argsNode.namedChildren;
|
|
933
|
+
if (args.length < 2)
|
|
934
|
+
return;
|
|
935
|
+
const pathArg = args[0];
|
|
936
|
+
if (!pathArg)
|
|
937
|
+
return;
|
|
938
|
+
const mountPath = stringLiteralValue(pathArg);
|
|
939
|
+
if (mountPath == null)
|
|
940
|
+
return;
|
|
941
|
+
const basePrefix = appVars[ownerVar]?.base_path || "";
|
|
942
|
+
const fullMountPath = joinPaths(parentPrefix || basePrefix, mountPath);
|
|
943
|
+
const mount = {
|
|
944
|
+
parent_var: ownerVar,
|
|
945
|
+
mount_path: fullMountPath,
|
|
946
|
+
child_var: "<external>",
|
|
947
|
+
child_file: "",
|
|
948
|
+
mount_type: "hono_mount",
|
|
949
|
+
};
|
|
950
|
+
model.mounts.push(mount);
|
|
951
|
+
localMounts.push(mount);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
if (methodName !== "route")
|
|
955
|
+
return;
|
|
956
|
+
const argList = argsNode.namedChildren;
|
|
957
|
+
if (argList.length < 2)
|
|
958
|
+
return;
|
|
959
|
+
const pathArg = argList[0];
|
|
960
|
+
const childArg = argList[1];
|
|
961
|
+
if (!pathArg || !childArg)
|
|
962
|
+
return;
|
|
963
|
+
const mountPath = stringLiteralValue(pathArg);
|
|
964
|
+
if (mountPath == null)
|
|
965
|
+
return;
|
|
966
|
+
const childVar = childArg.type === "identifier" ? childArg.text : null;
|
|
967
|
+
if (!childVar)
|
|
968
|
+
return;
|
|
969
|
+
mounts.push({
|
|
970
|
+
mountPath,
|
|
971
|
+
childVar,
|
|
972
|
+
line: node.startPosition.row + 1,
|
|
973
|
+
});
|
|
974
|
+
});
|
|
975
|
+
// Process mounts — async because recursive parse needs file I/O
|
|
976
|
+
for (const { mountPath, childVar } of mounts) {
|
|
977
|
+
// T6: Fallback for LOCAL sub-apps (declared in the same file, not
|
|
978
|
+
// imported). If the import map doesn't know this var but appVars does,
|
|
979
|
+
// the child lives in the parent file — use that path instead of "".
|
|
980
|
+
// Routes on the local sub-app were already captured by walkHttpRoutes
|
|
981
|
+
// on the same root, so we record the mount without re-parsing.
|
|
982
|
+
let childFile = importMap.get(childVar);
|
|
983
|
+
let resolvedViaLocal = false;
|
|
984
|
+
if (!childFile && appVars[childVar]) {
|
|
985
|
+
childFile = appVars[childVar].file;
|
|
986
|
+
resolvedViaLocal = true;
|
|
987
|
+
}
|
|
988
|
+
const fullMountPath = joinPaths(parentPrefix, mountPath);
|
|
989
|
+
const mount = {
|
|
990
|
+
parent_var: Object.keys(appVars)[0] ?? "app",
|
|
991
|
+
mount_path: fullMountPath,
|
|
992
|
+
child_var: childVar,
|
|
993
|
+
child_file: childFile ?? "",
|
|
994
|
+
mount_type: "hono_route",
|
|
995
|
+
};
|
|
996
|
+
model.mounts.push(mount);
|
|
997
|
+
localMounts.push(mount);
|
|
998
|
+
if (resolvedViaLocal) {
|
|
999
|
+
// Local sub-app — routes already live in the parent file, no recursion.
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (childFile && existsSync(childFile)) {
|
|
1003
|
+
await this.parseFile(childFile, fullMountPath, inFlight, parsedCache, model);
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
model.skip_reasons.unresolved_import =
|
|
1007
|
+
(model.skip_reasons.unresolved_import ?? 0) + 1;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
/** Detect Hono runtime from project files and source patterns. */
|
|
1012
|
+
async detectRuntime(entryFile) {
|
|
1013
|
+
const dir = path.dirname(entryFile);
|
|
1014
|
+
const projectRoot = path.dirname(dir); // assume src/ is one level down
|
|
1015
|
+
// Check for wrangler.toml → Cloudflare Workers
|
|
1016
|
+
if (existsSync(path.join(projectRoot, "wrangler.toml")) ||
|
|
1017
|
+
existsSync(path.join(dir, "wrangler.toml"))) {
|
|
1018
|
+
return "cloudflare";
|
|
1019
|
+
}
|
|
1020
|
+
let source;
|
|
1021
|
+
try {
|
|
1022
|
+
source = await readFile(entryFile, "utf-8");
|
|
1023
|
+
}
|
|
1024
|
+
catch {
|
|
1025
|
+
return "unknown";
|
|
1026
|
+
}
|
|
1027
|
+
if (source.includes("Deno.serve"))
|
|
1028
|
+
return "deno";
|
|
1029
|
+
if (source.includes("Bun.serve"))
|
|
1030
|
+
return "bun";
|
|
1031
|
+
if (source.includes("@hono/node-server") || source.includes("serve({ fetch"))
|
|
1032
|
+
return "node";
|
|
1033
|
+
if (source.includes("hono/aws-lambda") || source.includes("handle("))
|
|
1034
|
+
return "lambda";
|
|
1035
|
+
// T5: Advanced runtime detection — only runs when existing heuristics
|
|
1036
|
+
// produced "unknown". Checks Cloudflare Bindings type signals and
|
|
1037
|
+
// deployment-target config files in the project root.
|
|
1038
|
+
return this.detectRuntimeAdvanced(source, projectRoot);
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Fallback runtime detection. Runs after detectRuntime's primary signals
|
|
1042
|
+
* miss — additive by design so Phase 1 tests remain stable.
|
|
1043
|
+
*
|
|
1044
|
+
* Signal precedence (first match wins):
|
|
1045
|
+
* 1. Cloudflare Bindings type using KVNamespace/D1Database/R2Bucket/Queue
|
|
1046
|
+
* 2. vercel.json at project root
|
|
1047
|
+
* 3. netlify.toml OR netlify/functions/ dir at project root
|
|
1048
|
+
* 4. fly.toml at project root
|
|
1049
|
+
* 5. unknown
|
|
1050
|
+
*/
|
|
1051
|
+
detectRuntimeAdvanced(source, projectRoot) {
|
|
1052
|
+
if (hasCloudflareBindingsType(source))
|
|
1053
|
+
return "cloudflare";
|
|
1054
|
+
if (existsSync(path.join(projectRoot, "vercel.json")))
|
|
1055
|
+
return "vercel";
|
|
1056
|
+
if (existsSync(path.join(projectRoot, "netlify.toml")) ||
|
|
1057
|
+
existsSync(path.join(projectRoot, "netlify", "functions"))) {
|
|
1058
|
+
return "netlify";
|
|
1059
|
+
}
|
|
1060
|
+
if (existsSync(path.join(projectRoot, "fly.toml")))
|
|
1061
|
+
return "fly";
|
|
1062
|
+
return "unknown";
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Extract env bindings from:
|
|
1066
|
+
* 1. c.env.IDENTIFIER member accesses
|
|
1067
|
+
* 2. Destructuring: const { A, B } = c.env
|
|
1068
|
+
* 3. Bindings type literal from Hono<{ Bindings: {...} }> or createFactory<{ Bindings: {...} }>
|
|
1069
|
+
*/
|
|
1070
|
+
extractEnvBindings(root, source, model) {
|
|
1071
|
+
const bindings = new Set();
|
|
1072
|
+
// Pattern 1 & 2: Walk AST for c.env.X and const { X } = c.env
|
|
1073
|
+
const cursor = root.walk();
|
|
1074
|
+
walk(cursor, (node) => {
|
|
1075
|
+
// c.env.IDENTIFIER
|
|
1076
|
+
if (node.type === "member_expression") {
|
|
1077
|
+
const obj = node.childForFieldName("object");
|
|
1078
|
+
const prop = node.childForFieldName("property");
|
|
1079
|
+
if (obj?.type === "member_expression" && prop) {
|
|
1080
|
+
const innerObj = obj.childForFieldName("object");
|
|
1081
|
+
const innerProp = obj.childForFieldName("property");
|
|
1082
|
+
if (innerObj?.text === "c" && innerProp?.text === "env") {
|
|
1083
|
+
bindings.add(prop.text);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
// const { DATABASE_URL, KV } = c.env
|
|
1088
|
+
if (node.type === "variable_declarator") {
|
|
1089
|
+
const nameNode = node.childForFieldName("name");
|
|
1090
|
+
const valueNode = node.childForFieldName("value");
|
|
1091
|
+
if (nameNode?.type === "object_pattern" && valueNode?.type === "member_expression") {
|
|
1092
|
+
const obj = valueNode.childForFieldName("object");
|
|
1093
|
+
const prop = valueNode.childForFieldName("property");
|
|
1094
|
+
if (obj?.text === "c" && prop?.text === "env") {
|
|
1095
|
+
for (const child of nameNode.namedChildren) {
|
|
1096
|
+
if (child.type === "shorthand_property_identifier_pattern" ||
|
|
1097
|
+
child.type === "shorthand_property_identifier") {
|
|
1098
|
+
bindings.add(child.text);
|
|
1099
|
+
}
|
|
1100
|
+
if (child.type === "pair_pattern") {
|
|
1101
|
+
const key = child.childForFieldName("key");
|
|
1102
|
+
if (key)
|
|
1103
|
+
bindings.add(key.text);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
// Pattern 3: Bindings type literal from source using regex (simpler than full AST type resolution)
|
|
1111
|
+
const bindingsMatch = source.match(/Bindings\s*:\s*\{([^}]+)\}/);
|
|
1112
|
+
if (bindingsMatch?.[1]) {
|
|
1113
|
+
const typeBody = bindingsMatch[1];
|
|
1114
|
+
const propRegex = /(\w+)\s*:/g;
|
|
1115
|
+
let m;
|
|
1116
|
+
while ((m = propRegex.exec(typeBody)) !== null) {
|
|
1117
|
+
if (m[1])
|
|
1118
|
+
bindings.add(m[1]);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
model.env_bindings = [...bindings].sort();
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
// --- Utility functions ---
|
|
1125
|
+
function pickLanguage(file) {
|
|
1126
|
+
const ext = path.extname(file).toLowerCase();
|
|
1127
|
+
if (ext === ".tsx")
|
|
1128
|
+
return "tsx";
|
|
1129
|
+
if (ext === ".ts")
|
|
1130
|
+
return "typescript";
|
|
1131
|
+
if (ext === ".jsx" || ext === ".js")
|
|
1132
|
+
return "javascript";
|
|
1133
|
+
return "typescript";
|
|
1134
|
+
}
|
|
1135
|
+
function emptyModel(entryFile, skipReasons) {
|
|
1136
|
+
return {
|
|
1137
|
+
entry_file: entryFile,
|
|
1138
|
+
app_variables: {},
|
|
1139
|
+
routes: [],
|
|
1140
|
+
mounts: [],
|
|
1141
|
+
middleware_chains: [],
|
|
1142
|
+
context_vars: [],
|
|
1143
|
+
openapi_routes: [],
|
|
1144
|
+
rpc_exports: [],
|
|
1145
|
+
runtime: "unknown",
|
|
1146
|
+
env_bindings: [],
|
|
1147
|
+
files_used: [],
|
|
1148
|
+
extraction_status: Object.keys(skipReasons).length > 0 ? "partial" : "complete",
|
|
1149
|
+
skip_reasons: skipReasons,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Detect `<parentVar>.basePath("/prefix")` call expression.
|
|
1154
|
+
* Returns the parent variable name and prefix path, or null.
|
|
1155
|
+
*/
|
|
1156
|
+
function extractBasePathCall(valueNode, knownVars) {
|
|
1157
|
+
if (valueNode.type !== "call_expression")
|
|
1158
|
+
return null;
|
|
1159
|
+
const fnNode = valueNode.childForFieldName("function");
|
|
1160
|
+
if (!fnNode || fnNode.type !== "member_expression")
|
|
1161
|
+
return null;
|
|
1162
|
+
const obj = fnNode.childForFieldName("object");
|
|
1163
|
+
const prop = fnNode.childForFieldName("property");
|
|
1164
|
+
if (!obj || !prop || obj.type !== "identifier")
|
|
1165
|
+
return null;
|
|
1166
|
+
if (prop.text !== "basePath")
|
|
1167
|
+
return null;
|
|
1168
|
+
if (!knownVars[obj.text])
|
|
1169
|
+
return null;
|
|
1170
|
+
const argsNode = valueNode.childForFieldName("arguments");
|
|
1171
|
+
const firstArg = argsNode?.namedChildren[0];
|
|
1172
|
+
if (!firstArg)
|
|
1173
|
+
return null;
|
|
1174
|
+
const prefix = stringLiteralValue(firstArg);
|
|
1175
|
+
if (prefix == null)
|
|
1176
|
+
return null;
|
|
1177
|
+
// Combine parent's base_path with the new prefix
|
|
1178
|
+
const parentBase = knownVars[obj.text]?.base_path || "";
|
|
1179
|
+
return { parentVar: obj.text, prefix: joinPaths(parentBase, prefix) };
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Parse regex constraints from Hono path parameters.
|
|
1183
|
+
* e.g., ":id{[0-9]+}" → { id: "[0-9]+" }
|
|
1184
|
+
*/
|
|
1185
|
+
function parseRegexConstraints(rawPath) {
|
|
1186
|
+
const regex = /:(\w+)\{([^}]+)\}/g;
|
|
1187
|
+
let match;
|
|
1188
|
+
const constraints = {};
|
|
1189
|
+
let found = false;
|
|
1190
|
+
while ((match = regex.exec(rawPath)) !== null) {
|
|
1191
|
+
if (match[1] && match[2]) {
|
|
1192
|
+
constraints[match[1]] = match[2];
|
|
1193
|
+
found = true;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return found ? constraints : undefined;
|
|
1197
|
+
}
|
|
1198
|
+
/** Check if a value is `createFactory(...)` or `createFactory<Env>(...)`. */
|
|
1199
|
+
function isCreateFactoryCall(valueNode) {
|
|
1200
|
+
if (valueNode.type !== "call_expression")
|
|
1201
|
+
return false;
|
|
1202
|
+
const fn = valueNode.childForFieldName("function");
|
|
1203
|
+
if (!fn)
|
|
1204
|
+
return false;
|
|
1205
|
+
// createFactory<...>()
|
|
1206
|
+
if (fn.type === "identifier" && fn.text === "createFactory")
|
|
1207
|
+
return true;
|
|
1208
|
+
// hono/factory.createFactory<...>()
|
|
1209
|
+
if (fn.type === "member_expression") {
|
|
1210
|
+
const prop = fn.childForFieldName("property");
|
|
1211
|
+
if (prop?.text === "createFactory")
|
|
1212
|
+
return true;
|
|
1213
|
+
}
|
|
1214
|
+
return false;
|
|
1215
|
+
}
|
|
1216
|
+
/** Check if a value is `<factoryVar>.createApp()`. */
|
|
1217
|
+
function isFactoryCreateApp(valueNode, factoryVars) {
|
|
1218
|
+
if (valueNode.type !== "call_expression")
|
|
1219
|
+
return false;
|
|
1220
|
+
const fn = valueNode.childForFieldName("function");
|
|
1221
|
+
if (!fn || fn.type !== "member_expression")
|
|
1222
|
+
return false;
|
|
1223
|
+
const obj = fn.childForFieldName("object");
|
|
1224
|
+
const prop = fn.childForFieldName("property");
|
|
1225
|
+
if (!obj || !prop || obj.type !== "identifier")
|
|
1226
|
+
return false;
|
|
1227
|
+
return factoryVars.has(obj.text) && prop.text === "createApp";
|
|
1228
|
+
}
|
|
1229
|
+
function classifyAppCreation(valueNode) {
|
|
1230
|
+
if (valueNode.type !== "new_expression")
|
|
1231
|
+
return null;
|
|
1232
|
+
const ctor = valueNode.childForFieldName("constructor");
|
|
1233
|
+
if (!ctor)
|
|
1234
|
+
return null;
|
|
1235
|
+
if (ctor.type === "identifier") {
|
|
1236
|
+
if (ctor.text === "Hono")
|
|
1237
|
+
return "new Hono";
|
|
1238
|
+
if (ctor.text === "OpenAPIHono")
|
|
1239
|
+
return "OpenAPIHono";
|
|
1240
|
+
}
|
|
1241
|
+
if (ctor.type === "member_expression") {
|
|
1242
|
+
const prop = ctor.childForFieldName("property");
|
|
1243
|
+
if (prop?.text === "Hono")
|
|
1244
|
+
return "new Hono";
|
|
1245
|
+
if (prop?.text === "OpenAPIHono")
|
|
1246
|
+
return "OpenAPIHono";
|
|
1247
|
+
}
|
|
1248
|
+
return null;
|
|
1249
|
+
}
|
|
1250
|
+
function stringLiteralValue(node) {
|
|
1251
|
+
if (node.type === "string") {
|
|
1252
|
+
const text = node.text;
|
|
1253
|
+
if (text.length < 2)
|
|
1254
|
+
return null;
|
|
1255
|
+
const quote = text[0];
|
|
1256
|
+
if (quote !== '"' && quote !== "'")
|
|
1257
|
+
return null;
|
|
1258
|
+
try {
|
|
1259
|
+
const normalized = quote === "'"
|
|
1260
|
+
? `"${text.slice(1, -1).replace(/\\'/g, "'").replace(/"/g, '\\"')}"`
|
|
1261
|
+
: text;
|
|
1262
|
+
const parsed = JSON.parse(normalized);
|
|
1263
|
+
return typeof parsed === "string" ? parsed : null;
|
|
1264
|
+
}
|
|
1265
|
+
catch {
|
|
1266
|
+
return text.slice(1, -1);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (node.type === "template_string") {
|
|
1270
|
+
const hasInterpolation = node.namedChildren.some((c) => c.type === "template_substitution");
|
|
1271
|
+
if (hasInterpolation)
|
|
1272
|
+
return null;
|
|
1273
|
+
const text = node.text;
|
|
1274
|
+
if (text.length < 2)
|
|
1275
|
+
return null;
|
|
1276
|
+
return text.slice(1, -1);
|
|
1277
|
+
}
|
|
1278
|
+
return null;
|
|
1279
|
+
}
|
|
1280
|
+
function buildHandler(node, file) {
|
|
1281
|
+
const line = node.startPosition.row + 1;
|
|
1282
|
+
if (node.type === "arrow_function" ||
|
|
1283
|
+
node.type === "function_expression" ||
|
|
1284
|
+
node.type === "function") {
|
|
1285
|
+
return { name: "<inline>", inline: true, file, line };
|
|
1286
|
+
}
|
|
1287
|
+
if (node.type === "identifier") {
|
|
1288
|
+
return { name: node.text, inline: false, file, line };
|
|
1289
|
+
}
|
|
1290
|
+
return { name: "<inline>", inline: true, file, line };
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Resolve a relative import specifier to an absolute file path.
|
|
1294
|
+
* Tries: exact path, .ts, .js, /index.ts, /index.js extensions.
|
|
1295
|
+
*/
|
|
1296
|
+
function resolveImportPath(fromFile, importSpecifier) {
|
|
1297
|
+
const dir = path.dirname(fromFile);
|
|
1298
|
+
const base = path.resolve(dir, importSpecifier);
|
|
1299
|
+
// Try with various extensions — .ts fixture files imported as .js in ESM
|
|
1300
|
+
const candidates = [
|
|
1301
|
+
base,
|
|
1302
|
+
base + ".ts",
|
|
1303
|
+
base + ".tsx",
|
|
1304
|
+
base.replace(/\.js$/, ".ts"),
|
|
1305
|
+
base.replace(/\.jsx$/, ".tsx"),
|
|
1306
|
+
path.join(base, "index.ts"),
|
|
1307
|
+
path.join(base, "index.js"),
|
|
1308
|
+
];
|
|
1309
|
+
for (const candidate of candidates) {
|
|
1310
|
+
if (existsSync(candidate)) {
|
|
1311
|
+
return canonicalize(candidate);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return null;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Canonicalize path via realpath for consistent cache invalidation.
|
|
1318
|
+
* Falls back to the input path if the file doesn't exist.
|
|
1319
|
+
*/
|
|
1320
|
+
function canonicalize(filePath) {
|
|
1321
|
+
try {
|
|
1322
|
+
return realpathSync.native(filePath);
|
|
1323
|
+
}
|
|
1324
|
+
catch {
|
|
1325
|
+
return filePath;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
/** Join a parent prefix with a child path, avoiding double/trailing slashes. */
|
|
1329
|
+
function joinPaths(prefix, childPath) {
|
|
1330
|
+
if (!prefix)
|
|
1331
|
+
return childPath;
|
|
1332
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
1333
|
+
// In Hono, a sub-router's "/" matches the mount path exactly (no trailing slash)
|
|
1334
|
+
if (childPath === "/" || childPath === "")
|
|
1335
|
+
return p || "/";
|
|
1336
|
+
const c = childPath.startsWith("/") ? childPath : "/" + childPath;
|
|
1337
|
+
return p + c;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Given the `consequence` of an if_statement, locate a middleware-call
|
|
1341
|
+
* expression of the form `mw(c, next)`. Walks the whole block (not just the
|
|
1342
|
+
* first statement) so patterns like
|
|
1343
|
+
*
|
|
1344
|
+
* if (cond) {
|
|
1345
|
+
* const auth = basicAuth({...});
|
|
1346
|
+
* return auth(c, next);
|
|
1347
|
+
* }
|
|
1348
|
+
*
|
|
1349
|
+
* are recognized. Only looks at top-level statements of the consequence —
|
|
1350
|
+
* does not descend into nested blocks. The candidate call must have >= 2
|
|
1351
|
+
* named arguments to heuristically match `mw(c, next)` shape.
|
|
1352
|
+
*/
|
|
1353
|
+
function findMiddlewareCallInBlock(consequence) {
|
|
1354
|
+
const statements = consequence.type === "statement_block"
|
|
1355
|
+
? consequence.namedChildren
|
|
1356
|
+
: [consequence];
|
|
1357
|
+
for (const stmt of statements) {
|
|
1358
|
+
let call = null;
|
|
1359
|
+
if (stmt.type === "return_statement") {
|
|
1360
|
+
const expr = stmt.namedChildren[0];
|
|
1361
|
+
if (expr)
|
|
1362
|
+
call = unwrapCallExpression(expr);
|
|
1363
|
+
}
|
|
1364
|
+
else if (stmt.type === "expression_statement") {
|
|
1365
|
+
const expr = stmt.namedChildren[0];
|
|
1366
|
+
if (expr)
|
|
1367
|
+
call = unwrapCallExpression(expr);
|
|
1368
|
+
}
|
|
1369
|
+
if (call && callHasAtLeastNArgs(call, 2))
|
|
1370
|
+
return call;
|
|
1371
|
+
}
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
function callHasAtLeastNArgs(call, n) {
|
|
1375
|
+
const args = call.childForFieldName("arguments");
|
|
1376
|
+
return (args?.namedChildren.length ?? 0) >= n;
|
|
1377
|
+
}
|
|
1378
|
+
/**
|
|
1379
|
+
* Collect block-local alias declarations of the form
|
|
1380
|
+
* const X = <callee>(...)
|
|
1381
|
+
* into a Map<X, <callee>>. Used to resolve `const auth = basicAuth({...})` so
|
|
1382
|
+
* that `return auth(c, next)` reports "basicAuth" as the applied middleware.
|
|
1383
|
+
*/
|
|
1384
|
+
function collectLocalAliases(consequence) {
|
|
1385
|
+
const map = new Map();
|
|
1386
|
+
const statements = consequence.type === "statement_block"
|
|
1387
|
+
? consequence.namedChildren
|
|
1388
|
+
: [consequence];
|
|
1389
|
+
for (const stmt of statements) {
|
|
1390
|
+
if (stmt.type !== "lexical_declaration" && stmt.type !== "variable_declaration")
|
|
1391
|
+
continue;
|
|
1392
|
+
for (const declarator of stmt.namedChildren) {
|
|
1393
|
+
if (declarator.type !== "variable_declarator")
|
|
1394
|
+
continue;
|
|
1395
|
+
const nameNode = declarator.childForFieldName("name");
|
|
1396
|
+
const valueNode = declarator.childForFieldName("value");
|
|
1397
|
+
if (nameNode?.type !== "identifier" || !valueNode)
|
|
1398
|
+
continue;
|
|
1399
|
+
if (valueNode.type !== "call_expression")
|
|
1400
|
+
continue;
|
|
1401
|
+
const calleeName = extractCallCalleeName(valueNode);
|
|
1402
|
+
if (calleeName)
|
|
1403
|
+
map.set(nameNode.text, calleeName);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return map;
|
|
1407
|
+
}
|
|
1408
|
+
/** Peel off `await ...` wrappers and return the underlying call_expression, if any. */
|
|
1409
|
+
function unwrapCallExpression(node) {
|
|
1410
|
+
let current = node;
|
|
1411
|
+
while (current.type === "await_expression") {
|
|
1412
|
+
const inner = current.namedChildren[0];
|
|
1413
|
+
if (!inner)
|
|
1414
|
+
return null;
|
|
1415
|
+
current = inner;
|
|
1416
|
+
}
|
|
1417
|
+
if (current.type === "call_expression")
|
|
1418
|
+
return current;
|
|
1419
|
+
return null;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Extract the name of the middleware at the call site. Supports:
|
|
1423
|
+
* - `foo(c, next)` → "foo"
|
|
1424
|
+
* - `foo.bar(c, next)` → "bar"
|
|
1425
|
+
* - `auth(c, next)` where `auth` came from `const auth = basicAuth({...})`
|
|
1426
|
+
* → returns "auth"; T4 reports the local identifier, and a separate
|
|
1427
|
+
* def-use pass could resolve it further (out of scope here).
|
|
1428
|
+
* - `basicAuth({...})(c, next)` → "basicAuth" (outer callee of the inner call)
|
|
1429
|
+
*/
|
|
1430
|
+
function extractCallCalleeName(callNode) {
|
|
1431
|
+
const fn = callNode.childForFieldName("function");
|
|
1432
|
+
if (!fn)
|
|
1433
|
+
return null;
|
|
1434
|
+
if (fn.type === "identifier")
|
|
1435
|
+
return fn.text;
|
|
1436
|
+
if (fn.type === "member_expression") {
|
|
1437
|
+
const prop = fn.childForFieldName("property");
|
|
1438
|
+
return prop?.text ?? null;
|
|
1439
|
+
}
|
|
1440
|
+
// `basicAuth({...})(c, next)` — fn itself is a call_expression
|
|
1441
|
+
if (fn.type === "call_expression") {
|
|
1442
|
+
const innerFn = fn.childForFieldName("function");
|
|
1443
|
+
if (innerFn?.type === "identifier")
|
|
1444
|
+
return innerFn.text;
|
|
1445
|
+
if (innerFn?.type === "member_expression") {
|
|
1446
|
+
const prop = innerFn.childForFieldName("property");
|
|
1447
|
+
return prop?.text ?? null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return null;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Classify an if-condition into method / header / path / custom by looking at
|
|
1454
|
+
* the leftmost member_expression chain. Keeps the check cheap and deterministic.
|
|
1455
|
+
*/
|
|
1456
|
+
function classifyConditionType(condition) {
|
|
1457
|
+
const text = condition.text;
|
|
1458
|
+
// Normalize for substring checks
|
|
1459
|
+
if (/c\.req\.method\b/.test(text))
|
|
1460
|
+
return "method";
|
|
1461
|
+
if (/c\.req\.header\s*\(/.test(text) || /c\.req\.headers\b/.test(text))
|
|
1462
|
+
return "header";
|
|
1463
|
+
if (/c\.req\.path\b/.test(text) || /c\.req\.url\b/.test(text))
|
|
1464
|
+
return "path";
|
|
1465
|
+
return "custom";
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* T5: Cloudflare Workers signal. These types ship with
|
|
1469
|
+
* @cloudflare/workers-types and never appear in non-Worker codebases, so
|
|
1470
|
+
* a word-boundary match on the source is sufficient — we do NOT need to
|
|
1471
|
+
* correlate with a Bindings declaration (which may be `type Bindings = {}`,
|
|
1472
|
+
* `interface Bindings {}`, inline in `Hono<{Bindings: {}}>`, or split across
|
|
1473
|
+
* nested type bodies with inner `}`). Removing the Bindings-block coupling
|
|
1474
|
+
* also fixes the first-`}` truncation that the initial regex had.
|
|
1475
|
+
*
|
|
1476
|
+
* Generic names like `Fetcher` and `Service` were dropped — they collide
|
|
1477
|
+
* with common non-CF type names in application code.
|
|
1478
|
+
*/
|
|
1479
|
+
const CF_WORKER_TYPES = [
|
|
1480
|
+
"KVNamespace",
|
|
1481
|
+
"D1Database",
|
|
1482
|
+
"R2Bucket",
|
|
1483
|
+
"DurableObjectNamespace",
|
|
1484
|
+
"AnalyticsEngineDataset",
|
|
1485
|
+
];
|
|
1486
|
+
function hasCloudflareBindingsType(source) {
|
|
1487
|
+
for (const cfType of CF_WORKER_TYPES) {
|
|
1488
|
+
const re = new RegExp(`\\b${cfType}\\b`);
|
|
1489
|
+
if (re.test(source))
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
/** Check if a node is inside a conditional branch (if/switch/try body). */
|
|
1495
|
+
function isInsideBranch(node) {
|
|
1496
|
+
let current = node.parent;
|
|
1497
|
+
while (current) {
|
|
1498
|
+
if (current.type === "if_statement" ||
|
|
1499
|
+
current.type === "switch_case" ||
|
|
1500
|
+
current.type === "catch_clause" ||
|
|
1501
|
+
current.type === "ternary_expression") {
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
// Stop at function boundary — we only care about branches within the function
|
|
1505
|
+
if (current.type === "arrow_function" ||
|
|
1506
|
+
current.type === "function_declaration" ||
|
|
1507
|
+
current.type === "function_expression" ||
|
|
1508
|
+
current.type === "method_definition") {
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
current = current.parent;
|
|
1512
|
+
}
|
|
1513
|
+
return false;
|
|
1514
|
+
}
|
|
1515
|
+
const MAX_WALK_DEPTH = 500;
|
|
1516
|
+
function walk(cursor, visit, depth = 0) {
|
|
1517
|
+
if (depth > MAX_WALK_DEPTH)
|
|
1518
|
+
return;
|
|
1519
|
+
visit(cursor.currentNode);
|
|
1520
|
+
if (cursor.gotoFirstChild()) {
|
|
1521
|
+
do {
|
|
1522
|
+
walk(cursor, visit, depth + 1);
|
|
1523
|
+
} while (cursor.gotoNextSibling());
|
|
1524
|
+
cursor.gotoParent();
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
//# sourceMappingURL=hono.js.map
|