gitnexus 1.6.10-rc.99 → 1.6.10
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 +177 -18
- package/dist/_shared/graph/types.d.ts +21 -2
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +3 -3
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/index.js +1 -1
- package/dist/_shared/index.js.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts +16 -0
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.js +19 -7
- package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +2 -2
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +3 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts +5 -0
- package/dist/_shared/scope-resolution/finalize-algorithm.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +315 -36
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +115 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/lookup-core.js +58 -2
- package/dist/_shared/scope-resolution/registries/lookup-core.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +47 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +278 -3
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +3 -2
- package/dist/cli/ai-context.js +71 -16
- package/dist/cli/analyze-config.d.ts +1 -1
- package/dist/cli/analyze-options.d.ts +131 -0
- package/dist/cli/analyze-options.js +1 -0
- package/dist/cli/analyze.d.ts +21 -113
- package/dist/cli/analyze.js +172 -29
- package/dist/cli/cli-message.d.ts +1 -1
- package/dist/cli/detect-changes-format.js +19 -2
- package/dist/cli/doctor.d.ts +12 -0
- package/dist/cli/doctor.js +32 -5
- package/dist/cli/editor-targets.js +6 -0
- package/dist/cli/eval-server.js +2 -1
- package/dist/cli/format-symbol.d.ts +15 -0
- package/dist/cli/format-symbol.js +17 -0
- package/dist/cli/generated-skill.d.ts +17 -0
- package/dist/cli/generated-skill.js +1 -0
- package/dist/cli/group.js +229 -21
- package/dist/cli/help-i18n.js +1 -2
- package/dist/cli/i18n/en.d.ts +11 -9
- package/dist/cli/i18n/en.js +13 -9
- package/dist/cli/i18n/resources.d.ts +15 -11
- package/dist/cli/i18n/zh-CN.d.ts +4 -2
- package/dist/cli/i18n/zh-CN.js +11 -9
- package/dist/cli/index.js +8 -4
- package/dist/cli/resolve-invocation.d.ts +7 -3
- package/dist/cli/resolve-invocation.js +7 -3
- package/dist/cli/setup.js +4 -3
- package/dist/cli/skill-gen.d.ts +1 -6
- package/dist/cli/tool.d.ts +1 -0
- package/dist/cli/tool.js +74 -17
- package/dist/cli/wiki.js +53 -10
- package/dist/config/ignore-service.d.ts +2 -0
- package/dist/config/ignore-service.js +54 -13
- package/dist/core/analyzer-identity.d.ts +80 -0
- package/dist/core/analyzer-identity.js +364 -26
- package/dist/core/augmentation/engine.js +86 -71
- package/dist/core/embedding-checkpoint.d.ts +143 -0
- package/dist/core/embedding-checkpoint.js +221 -0
- package/dist/core/embedding-count.d.ts +61 -0
- package/dist/core/embedding-count.js +69 -0
- package/dist/core/embeddings/ast-utils.js +9 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +8 -0
- package/dist/core/embeddings/embedding-pipeline.js +253 -14
- package/dist/core/embeddings/http-client.d.ts +5 -0
- package/dist/core/embeddings/http-client.js +142 -27
- package/dist/core/graph/graph.js +3 -0
- package/dist/core/graph/import-cycles.d.ts +194 -3
- package/dist/core/graph/import-cycles.js +522 -73
- package/dist/core/graph/types.d.ts +18 -0
- package/dist/core/group/bridge-db.d.ts +204 -1
- package/dist/core/group/bridge-db.js +401 -28
- package/dist/core/group/completeness.d.ts +130 -0
- package/dist/core/group/completeness.js +83 -0
- package/dist/core/group/config-parser.js +0 -5
- package/dist/core/group/cross-impact.d.ts +46 -1
- package/dist/core/group/cross-impact.js +220 -19
- package/dist/core/group/cross-trace.d.ts +32 -11
- package/dist/core/group/cross-trace.js +114 -20
- package/dist/core/group/extractors/http-patterns/java.js +278 -22
- package/dist/core/group/extractors/http-patterns/node.js +332 -216
- package/dist/core/group/extractors/http-patterns/types.d.ts +8 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +1 -0
- package/dist/core/group/extractors/http-route-extractor.js +122 -48
- package/dist/core/group/extractors/include-extractor.js +20 -2
- package/dist/core/group/extractors/manifest-extractor.d.ts +1 -1
- package/dist/core/group/extractors/manifest-extractor.js +7 -7
- package/dist/core/group/extractors/python-workspace-extractor.js +5 -2
- package/dist/core/group/group-lock.d.ts +31 -0
- package/dist/core/group/group-lock.js +166 -0
- package/dist/core/group/service.js +297 -16
- package/dist/core/group/storage.d.ts +3 -0
- package/dist/core/group/storage.js +7 -39
- package/dist/core/group/sync.d.ts +49 -3
- package/dist/core/group/sync.js +0 -0
- package/dist/core/group/types.d.ts +183 -8
- package/dist/core/group/types.js +28 -1
- package/dist/core/incremental/subgraph-extract.d.ts +9 -6
- package/dist/core/incremental/subgraph-extract.js +33 -10
- package/dist/core/index-freshness.d.ts +77 -2
- package/dist/core/index-freshness.js +109 -2
- package/dist/core/ingestion/call-processor.d.ts +13 -1
- package/dist/core/ingestion/call-processor.js +110 -3
- package/dist/core/ingestion/cfg/callee-cell-format.d.ts +42 -0
- package/dist/core/ingestion/cfg/callee-cell-format.js +42 -0
- package/dist/core/ingestion/cfg/emit.d.ts +8 -23
- package/dist/core/ingestion/cfg/emit.js +9 -23
- package/dist/core/ingestion/class-extractors/configs/go.js +10 -5
- package/dist/core/ingestion/cluster-enricher.js +8 -4
- package/dist/core/ingestion/community-processor.d.ts +21 -3
- package/dist/core/ingestion/community-processor.js +81 -82
- package/dist/core/ingestion/di-extractors/index.d.ts +9 -33
- package/dist/core/ingestion/di-extractors/spring.d.ts +1 -1
- package/dist/core/ingestion/di-extractors/spring.js +8 -1
- package/dist/core/ingestion/di-extractors/types.d.ts +60 -0
- package/dist/core/ingestion/di-extractors/types.js +15 -0
- package/dist/core/ingestion/filesystem-walker.js +30 -14
- package/dist/core/ingestion/finalize-orchestrator.js +1 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +6 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.js +35 -1
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.d.ts +13 -0
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +227 -0
- package/dist/core/ingestion/frameworks/spring/aop-candidates.d.ts +15 -0
- package/dist/core/ingestion/frameworks/spring/aop-candidates.js +85 -0
- package/dist/core/ingestion/frameworks/spring/aop.d.ts +118 -0
- package/dist/core/ingestion/frameworks/spring/aop.js +476 -0
- package/dist/core/ingestion/frameworks/spring/auto-configuration.d.ts +8 -0
- package/dist/core/ingestion/frameworks/spring/auto-configuration.js +16 -0
- package/dist/core/ingestion/frameworks/spring/bean-catalog.js +1 -0
- package/dist/core/ingestion/frameworks/spring/bean-factories.d.ts +33 -0
- package/dist/core/ingestion/frameworks/spring/bean-factories.js +65 -0
- package/dist/core/ingestion/frameworks/spring/conditionals.d.ts +25 -0
- package/dist/core/ingestion/frameworks/spring/conditionals.js +319 -0
- package/dist/core/ingestion/frameworks/spring/di-metadata.d.ts +3 -0
- package/dist/core/ingestion/frameworks/spring/di-metadata.js +162 -13
- package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +28 -0
- package/dist/core/ingestion/frameworks/spring/non-http-handlers.js +152 -0
- package/dist/core/ingestion/frameworks/spring/resource-injection.d.ts +5 -0
- package/dist/core/ingestion/frameworks/spring/resource-injection.js +91 -0
- package/dist/core/ingestion/import-resolvers/configs/csharp.js +5 -1
- package/dist/core/ingestion/import-resolvers/configs/swift.js +13 -0
- package/dist/core/ingestion/import-resolvers/csharp.d.ts +9 -1
- package/dist/core/ingestion/import-resolvers/csharp.js +234 -27
- package/dist/core/ingestion/import-resolvers/go.d.ts +11 -1
- package/dist/core/ingestion/import-resolvers/go.js +44 -17
- package/dist/core/ingestion/import-resolvers/jvm.d.ts +2 -2
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.d.ts +90 -0
- package/dist/core/ingestion/import-resolvers/node-workspace-packages.js +429 -0
- package/dist/core/ingestion/import-resolvers/package-dir-index.d.ts +124 -0
- package/dist/core/ingestion/import-resolvers/package-dir-index.js +223 -0
- package/dist/core/ingestion/import-resolvers/pass-cache.d.ts +50 -0
- package/dist/core/ingestion/import-resolvers/pass-cache.js +60 -0
- package/dist/core/ingestion/import-resolvers/per-file-set.d.ts +71 -0
- package/dist/core/ingestion/import-resolvers/per-file-set.js +11 -0
- package/dist/core/ingestion/import-resolvers/php.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/php.js +65 -19
- package/dist/core/ingestion/import-resolvers/python-file-index.d.ts +201 -0
- package/dist/core/ingestion/import-resolvers/python-file-index.js +302 -0
- package/dist/core/ingestion/import-resolvers/python.js +25 -6
- package/dist/core/ingestion/import-resolvers/ruby.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/rust.d.ts +2 -2
- package/dist/core/ingestion/import-resolvers/standard.d.ts +1 -1
- package/dist/core/ingestion/import-resolvers/types.d.ts +1 -12
- package/dist/core/ingestion/import-resolvers/utils.d.ts +65 -6
- package/dist/core/ingestion/import-resolvers/utils.js +215 -36
- package/dist/core/ingestion/import-resolvers/workspace-file-index.d.ts +72 -0
- package/dist/core/ingestion/import-resolvers/workspace-file-index.js +58 -0
- package/dist/core/ingestion/language-config.d.ts +29 -1
- package/dist/core/ingestion/language-config.js +110 -35
- package/dist/core/ingestion/language-provider.d.ts +209 -7
- package/dist/core/ingestion/language-provider.js +37 -0
- package/dist/core/ingestion/languages/c/captures.js +7 -0
- package/dist/core/ingestion/languages/c/import-target.js +12 -16
- package/dist/core/ingestion/languages/c/scope-resolver.js +20 -19
- package/dist/core/ingestion/languages/c/static-linkage.js +12 -16
- package/dist/core/ingestion/languages/c-cpp.js +13 -0
- package/dist/core/ingestion/languages/cobol/scope-resolver.js +27 -20
- package/dist/core/ingestion/languages/cobol.js +13 -0
- package/dist/core/ingestion/languages/cpp/adl.d.ts +2 -2
- package/dist/core/ingestion/languages/cpp/adl.js +67 -44
- package/dist/core/ingestion/languages/cpp/captures.js +14 -2
- package/dist/core/ingestion/languages/cpp/file-local-linkage.js +11 -15
- package/dist/core/ingestion/languages/cpp/inline-namespaces.d.ts +28 -8
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +242 -68
- package/dist/core/ingestion/languages/cpp/interpret.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/interpret.js +126 -6
- package/dist/core/ingestion/languages/cpp/query.js +106 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +48 -20
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.d.ts +2 -15
- package/dist/core/ingestion/languages/csharp/accessor-unwrap.js +18 -10
- package/dist/core/ingestion/languages/csharp/captures.js +19 -0
- package/dist/core/ingestion/languages/csharp/import-target.js +46 -72
- package/dist/core/ingestion/languages/csharp/namespace-siblings.js +5 -1
- package/dist/core/ingestion/languages/csharp/query.js +25 -5
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +75 -2
- package/dist/core/ingestion/languages/dart/captures.d.ts +10 -0
- package/dist/core/ingestion/languages/dart/captures.js +598 -16
- package/dist/core/ingestion/languages/dart/import-target.js +32 -9
- package/dist/core/ingestion/languages/dart/index.d.ts +1 -1
- package/dist/core/ingestion/languages/dart/index.js +1 -1
- package/dist/core/ingestion/languages/dart/query.js +98 -1
- package/dist/core/ingestion/languages/dart/scope-resolver.js +14 -3
- package/dist/core/ingestion/languages/dart/simple-hooks.js +9 -0
- package/dist/core/ingestion/languages/dart.js +6 -1
- package/dist/core/ingestion/languages/go/captures.js +108 -3
- package/dist/core/ingestion/languages/go/generic-type-parameters.d.ts +31 -0
- package/dist/core/ingestion/languages/go/generic-type-parameters.js +140 -0
- package/dist/core/ingestion/languages/go/import-target.d.ts +3 -10
- package/dist/core/ingestion/languages/go/import-target.js +34 -30
- package/dist/core/ingestion/languages/go/index.d.ts +1 -1
- package/dist/core/ingestion/languages/go/interface-impls.d.ts +9 -1
- package/dist/core/ingestion/languages/go/interface-impls.js +693 -87
- package/dist/core/ingestion/languages/go/interpret.d.ts +2 -0
- package/dist/core/ingestion/languages/go/interpret.js +22 -8
- package/dist/core/ingestion/languages/go/method-owners.js +31 -12
- package/dist/core/ingestion/languages/go/package-clause.d.ts +46 -0
- package/dist/core/ingestion/languages/go/package-clause.js +75 -0
- package/dist/core/ingestion/languages/go/package-siblings.js +7 -11
- package/dist/core/ingestion/languages/go/query.js +56 -3
- package/dist/core/ingestion/languages/go/scope-resolver.js +39 -0
- package/dist/core/ingestion/languages/go.js +9 -0
- package/dist/core/ingestion/languages/java/analysis-features.d.ts +4 -0
- package/dist/core/ingestion/languages/java/analysis-features.js +12 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +12 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +48 -0
- package/dist/core/ingestion/languages/java/captures.js +77 -32
- package/dist/core/ingestion/languages/java/import-target.d.ts +49 -9
- package/dist/core/ingestion/languages/java/import-target.js +68 -88
- package/dist/core/ingestion/languages/java/interpret.js +3 -2
- package/dist/core/ingestion/languages/java/module-resolution.d.ts +62 -0
- package/dist/core/ingestion/languages/java/module-resolution.js +142 -0
- package/dist/core/ingestion/languages/java/query.js +15 -3
- package/dist/core/ingestion/languages/java/record-components.d.ts +14 -0
- package/dist/core/ingestion/languages/java/record-components.js +185 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +10 -2
- package/dist/core/ingestion/languages/java/spring-aop.d.ts +12 -0
- package/dist/core/ingestion/languages/java/spring-aop.js +49 -0
- package/dist/core/ingestion/languages/java/spring-conditionals.d.ts +11 -0
- package/dist/core/ingestion/languages/java/spring-conditionals.js +48 -0
- package/dist/core/ingestion/languages/java/spring-di.d.ts +4 -1
- package/dist/core/ingestion/languages/java/spring-di.js +41 -9
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +7 -0
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +32 -0
- package/dist/core/ingestion/languages/java.js +94 -3
- package/dist/core/ingestion/languages/javascript/captures.d.ts +6 -1
- package/dist/core/ingestion/languages/javascript/captures.js +262 -4
- package/dist/core/ingestion/languages/javascript/import-target.d.ts +17 -21
- package/dist/core/ingestion/languages/javascript/import-target.js +24 -41
- package/dist/core/ingestion/languages/javascript/index.d.ts +54 -5
- package/dist/core/ingestion/languages/javascript/index.js +54 -5
- package/dist/core/ingestion/languages/javascript/query.d.ts +1 -0
- package/dist/core/ingestion/languages/javascript/query.js +211 -5
- package/dist/core/ingestion/languages/javascript/scope-resolver.js +12 -0
- package/dist/core/ingestion/languages/javascript/simple-hooks.js +2 -1
- package/dist/core/ingestion/languages/jvm/package-siblings.js +77 -9
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +48 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +43 -1
- package/dist/core/ingestion/languages/kotlin/import-target.d.ts +8 -1
- package/dist/core/ingestion/languages/kotlin/import-target.js +20 -123
- package/dist/core/ingestion/languages/kotlin/index.d.ts +1 -0
- package/dist/core/ingestion/languages/kotlin/index.js +1 -0
- package/dist/core/ingestion/languages/kotlin/module-resolution.d.ts +22 -0
- package/dist/core/ingestion/languages/kotlin/module-resolution.js +108 -0
- package/dist/core/ingestion/languages/kotlin/query.js +28 -2
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +23 -2
- package/dist/core/ingestion/languages/kotlin/spring-aop.d.ts +12 -0
- package/dist/core/ingestion/languages/kotlin/spring-aop.js +64 -0
- package/dist/core/ingestion/languages/kotlin/spring-conditionals.d.ts +12 -0
- package/dist/core/ingestion/languages/kotlin/spring-conditionals.js +49 -0
- package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +2 -0
- package/dist/core/ingestion/languages/kotlin/spring-di.js +60 -11
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +10 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +41 -0
- package/dist/core/ingestion/languages/php/captures.d.ts +5 -0
- package/dist/core/ingestion/languages/php/captures.js +319 -51
- package/dist/core/ingestion/languages/php/import-target.d.ts +3 -4
- package/dist/core/ingestion/languages/php/import-target.js +192 -62
- package/dist/core/ingestion/languages/php/query.js +31 -0
- package/dist/core/ingestion/languages/python/arity-metadata.d.ts +1 -0
- package/dist/core/ingestion/languages/python/arity-metadata.js +1 -0
- package/dist/core/ingestion/languages/python/captures.js +29 -0
- package/dist/core/ingestion/languages/python/import-decomposer.js +22 -1
- package/dist/core/ingestion/languages/python/import-target.d.ts +42 -1
- package/dist/core/ingestion/languages/python/import-target.js +277 -91
- package/dist/core/ingestion/languages/python/index.d.ts +1 -1
- package/dist/core/ingestion/languages/python/index.js +1 -1
- package/dist/core/ingestion/languages/python/interpret.d.ts +52 -0
- package/dist/core/ingestion/languages/python/interpret.js +249 -7
- package/dist/core/ingestion/languages/python/query.js +9 -0
- package/dist/core/ingestion/languages/python/receiver-binding.d.ts +8 -6
- package/dist/core/ingestion/languages/python/receiver-binding.js +108 -12
- package/dist/core/ingestion/languages/python/scope-resolver.js +27 -4
- package/dist/core/ingestion/languages/python/simple-hooks.js +2 -8
- package/dist/core/ingestion/languages/python.js +13 -0
- package/dist/core/ingestion/languages/ruby/captures.js +158 -0
- package/dist/core/ingestion/languages/ruby/import-target.js +7 -5
- package/dist/core/ingestion/languages/ruby/query.js +93 -0
- package/dist/core/ingestion/languages/ruby/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/ruby/simple-hooks.d.ts +1 -1
- package/dist/core/ingestion/languages/ruby/simple-hooks.js +22 -1
- package/dist/core/ingestion/languages/rust/captures.js +39 -1
- package/dist/core/ingestion/languages/rust/method-owners.js +11 -1
- package/dist/core/ingestion/languages/rust/module-path.d.ts +126 -0
- package/dist/core/ingestion/languages/rust/module-path.js +214 -0
- package/dist/core/ingestion/languages/rust/qualified-call.d.ts +41 -0
- package/dist/core/ingestion/languages/rust/qualified-call.js +481 -0
- package/dist/core/ingestion/languages/rust/query.js +39 -6
- package/dist/core/ingestion/languages/rust/scope-resolver.js +21 -2
- package/dist/core/ingestion/languages/rust.js +17 -0
- package/dist/core/ingestion/languages/swift/captures.js +87 -3
- package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.d.ts +48 -0
- package/dist/core/ingestion/languages/swift/conditional-directive-preprocess.js +253 -0
- package/dist/core/ingestion/languages/swift/import-target.js +4 -9
- package/dist/core/ingestion/languages/swift/query.js +17 -0
- package/dist/core/ingestion/languages/swift/receiver-binding.d.ts +8 -0
- package/dist/core/ingestion/languages/swift/receiver-binding.js +6 -2
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript/captures.d.ts +90 -1
- package/dist/core/ingestion/languages/typescript/captures.js +267 -5
- package/dist/core/ingestion/languages/typescript/cjs-export-assignment.d.ts +134 -0
- package/dist/core/ingestion/languages/typescript/cjs-export-assignment.js +523 -0
- package/dist/core/ingestion/languages/typescript/cjs-module-exports.d.ts +54 -0
- package/dist/core/ingestion/languages/typescript/cjs-module-exports.js +191 -0
- package/dist/core/ingestion/languages/typescript/convex-endpoint-metadata.d.ts +7 -0
- package/dist/core/ingestion/languages/typescript/convex-endpoint-metadata.js +92 -0
- package/dist/core/ingestion/languages/typescript/file-candidates.d.ts +29 -0
- package/dist/core/ingestion/languages/typescript/file-candidates.js +70 -0
- package/dist/core/ingestion/languages/typescript/import-decomposer.d.ts +28 -1
- package/dist/core/ingestion/languages/typescript/import-decomposer.js +125 -10
- package/dist/core/ingestion/languages/typescript/import-target.d.ts +25 -37
- package/dist/core/ingestion/languages/typescript/import-target.js +24 -26
- package/dist/core/ingestion/languages/typescript/interpret.d.ts +21 -1
- package/dist/core/ingestion/languages/typescript/interpret.js +45 -5
- package/dist/core/ingestion/languages/typescript/module-resolution.d.ts +48 -0
- package/dist/core/ingestion/languages/typescript/module-resolution.js +171 -0
- package/dist/core/ingestion/languages/typescript/query.d.ts +1 -0
- package/dist/core/ingestion/languages/typescript/query.js +303 -14
- package/dist/core/ingestion/languages/typescript/receiver-binding.d.ts +17 -0
- package/dist/core/ingestion/languages/typescript/receiver-binding.js +27 -0
- package/dist/core/ingestion/languages/typescript/scope-resolver.js +27 -40
- package/dist/core/ingestion/languages/typescript/simple-hooks.d.ts +0 -10
- package/dist/core/ingestion/languages/typescript/simple-hooks.js +22 -22
- package/dist/core/ingestion/languages/typescript/tsconfig.d.ts +59 -0
- package/dist/core/ingestion/languages/typescript/tsconfig.js +292 -0
- package/dist/core/ingestion/languages/typescript.js +93 -2
- package/dist/core/ingestion/languages/vue/import-target.d.ts +6 -22
- package/dist/core/ingestion/languages/vue/import-target.js +11 -47
- package/dist/core/ingestion/languages/vue/scope-resolver.js +4 -2
- package/dist/core/ingestion/local-symbol-pruner.js +11 -11
- package/dist/core/ingestion/method-extractors/configs/csharp.js +1 -0
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/di.d.ts +1 -1
- package/dist/core/ingestion/pipeline-phases/di.js +136 -21
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +15 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +154 -4
- package/dist/core/ingestion/pipeline-phases/parse.js +5 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +3 -2
- package/dist/core/ingestion/pipeline-phases/processes.js +91 -6
- package/dist/core/ingestion/pipeline-phases/routes.js +116 -25
- package/dist/core/ingestion/pipeline-phases/spring-aop.d.ts +34 -0
- package/dist/core/ingestion/pipeline-phases/spring-aop.js +439 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +34 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +255 -0
- package/dist/core/ingestion/pipeline-phases/types.d.ts +7 -0
- package/dist/core/ingestion/pipeline.d.ts +26 -2
- package/dist/core/ingestion/pipeline.js +60 -11
- package/dist/core/ingestion/process-processor.d.ts +109 -2
- package/dist/core/ingestion/process-processor.js +464 -52
- package/dist/core/ingestion/resolve-references.d.ts +6 -1
- package/dist/core/ingestion/resolve-references.js +66 -6
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +8 -2
- package/dist/core/ingestion/route-extractors/constant-resolver.js +11 -7
- package/dist/core/ingestion/route-extractors/data-route-table.d.ts +40 -0
- package/dist/core/ingestion/route-extractors/data-route-table.js +876 -0
- package/dist/core/ingestion/route-extractors/dispatch-guard.d.ts +96 -0
- package/dist/core/ingestion/route-extractors/dispatch-guard.js +1184 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +114 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +569 -0
- package/dist/core/ingestion/route-extractors/js-const-resolver.d.ts +192 -0
- package/dist/core/ingestion/route-extractors/js-const-resolver.js +1033 -0
- package/dist/core/ingestion/route-extractors/nest.d.ts +53 -0
- package/dist/core/ingestion/route-extractors/nest.js +523 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +11 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +89 -0
- package/dist/core/ingestion/route-extractors/spring.js +214 -29
- package/dist/core/ingestion/scope-extractor-bridge.js +9 -1
- package/dist/core/ingestion/scope-extractor.d.ts +22 -7
- package/dist/core/ingestion/scope-extractor.js +309 -15
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +311 -21
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.js +16 -5
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +47 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +20 -15
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +280 -42
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.d.ts +123 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +171 -17
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.d.ts +58 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +137 -34
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +30 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +39 -1
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +18 -1
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +113 -18
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +107 -5
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +590 -33
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +101 -41
- package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.d.ts +34 -0
- package/dist/core/ingestion/scope-resolution/passes/imported-value-refs.js +62 -0
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.d.ts +5 -1
- package/dist/core/ingestion/scope-resolution/passes/property-dispatch.js +8 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +105 -5
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +931 -92
- package/dist/core/ingestion/scope-resolution/passes/return-shape-members.d.ts +58 -0
- package/dist/core/ingestion/scope-resolution/passes/return-shape-members.js +458 -0
- package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.d.ts +180 -0
- package/dist/core/ingestion/scope-resolution/passes/unique-name-properties.js +418 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +29 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +57 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +55 -1
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +241 -19
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +112 -2
- package/dist/core/ingestion/scope-resolution/resolution-outcome.js +38 -1
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.d.ts +25 -3
- package/dist/core/ingestion/scope-resolution/scope/namespace-targets.js +36 -16
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +210 -9
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +905 -22
- package/dist/core/ingestion/scope-resolution/summary-maps.d.ts +28 -0
- package/dist/core/ingestion/scope-resolution/summary-maps.js +50 -0
- package/dist/core/ingestion/scope-resolution/undecided-satisfaction.d.ts +55 -0
- package/dist/core/ingestion/scope-resolution/undecided-satisfaction.js +37 -0
- package/dist/core/ingestion/scope-resolution/unresolved-receivers.d.ts +109 -0
- package/dist/core/ingestion/scope-resolution/unresolved-receivers.js +204 -0
- package/dist/core/ingestion/scope-resolution/utils/definition-id.d.ts +13 -0
- package/dist/core/ingestion/scope-resolution/utils/definition-id.js +19 -0
- package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.d.ts +171 -0
- package/dist/core/ingestion/scope-resolution/utils/generic-instantiation.js +218 -0
- package/dist/core/ingestion/scope-resolution/workspace-index-types.d.ts +34 -0
- package/dist/core/ingestion/scope-resolution/workspace-index-types.js +15 -0
- package/dist/core/ingestion/scope-resolution/workspace-index.d.ts +7 -20
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +678 -4
- package/dist/core/ingestion/type-env.js +11 -7
- package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
- package/dist/core/ingestion/type-extractors/shared.js +16 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +12 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +20 -0
- package/dist/core/ingestion/type-extractors/typescript.js +26 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +297 -15
- package/dist/core/ingestion/utils/ast-helpers.js +578 -46
- package/dist/core/ingestion/utils/call-analysis.d.ts +27 -22
- package/dist/core/ingestion/utils/call-analysis.js +202 -7
- package/dist/core/ingestion/utils/callable-flow-captures.d.ts +59 -0
- package/dist/core/ingestion/utils/callable-flow-captures.js +51 -1
- package/dist/core/ingestion/utils/callable-labels.d.ts +25 -0
- package/dist/core/ingestion/utils/callable-labels.js +31 -0
- package/dist/core/ingestion/utils/effective-ram.d.ts +40 -0
- package/dist/core/ingestion/utils/effective-ram.js +58 -0
- package/dist/core/ingestion/utils/line-base.d.ts +14 -0
- package/dist/core/ingestion/utils/line-base.js +14 -0
- package/dist/core/ingestion/utils/method-props.d.ts +27 -4
- package/dist/core/ingestion/utils/method-props.js +29 -4
- package/dist/core/ingestion/utils/receiver-chain-captures.d.ts +31 -0
- package/dist/core/ingestion/utils/receiver-chain-captures.js +57 -0
- package/dist/core/ingestion/utils/receiver-chain-codec.d.ts +89 -0
- package/dist/core/ingestion/utils/receiver-chain-codec.js +182 -0
- package/dist/core/ingestion/utils/scope-tree-walk.d.ts +22 -0
- package/dist/core/ingestion/utils/scope-tree-walk.js +33 -0
- package/dist/core/ingestion/utils/template-arguments.d.ts +96 -0
- package/dist/core/ingestion/utils/template-arguments.js +196 -0
- package/dist/core/ingestion/utils/type-parameters.d.ts +49 -0
- package/dist/core/ingestion/utils/type-parameters.js +206 -0
- package/dist/core/ingestion/workers/callable-id.d.ts +80 -0
- package/dist/core/ingestion/workers/callable-id.js +125 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +14 -1
- package/dist/core/ingestion/workers/parse-worker.js +493 -54
- package/dist/core/ingestion/workers/worker-pool.d.ts +20 -27
- package/dist/core/ingestion/workers/worker-pool.js +91 -3
- package/dist/core/lbug/csv-generator.d.ts +27 -0
- package/dist/core/lbug/csv-generator.js +72 -17
- package/dist/core/lbug/extension-load-error.js +13 -2
- package/dist/core/lbug/extension-loader.d.ts +29 -0
- package/dist/core/lbug/extension-loader.js +31 -6
- package/dist/core/lbug/graph-emit-sink.d.ts +264 -0
- package/dist/core/lbug/graph-emit-sink.js +581 -0
- package/dist/core/lbug/lbug-adapter.d.ts +290 -11
- package/dist/core/lbug/lbug-adapter.js +652 -69
- package/dist/core/lbug/native-check.d.ts +21 -0
- package/dist/core/lbug/native-check.js +248 -32
- package/dist/core/lbug/pdg-emit-sink.d.ts +13 -4
- package/dist/core/lbug/pdg-emit-sink.js +21 -111
- package/dist/core/lbug/pool-adapter.d.ts +9 -0
- package/dist/core/lbug/pool-adapter.js +164 -14
- package/dist/core/lbug/query-batch.d.ts +75 -0
- package/dist/core/lbug/query-batch.js +102 -0
- package/dist/core/lbug/rel-pair-routing.d.ts +160 -2
- package/dist/core/lbug/rel-pair-routing.js +228 -10
- package/dist/core/lbug/schema.d.ts +163 -4
- package/dist/core/lbug/schema.js +404 -159
- package/dist/core/lbug/sync-csv-writer.d.ts +47 -0
- package/dist/core/lbug/sync-csv-writer.js +108 -0
- package/dist/core/run-analyze.d.ts +147 -0
- package/dist/core/run-analyze.js +1215 -179
- package/dist/core/search/bm25-index.d.ts +11 -0
- package/dist/core/search/bm25-index.js +60 -26
- package/dist/core/search/fts-indexes.d.ts +150 -9
- package/dist/core/search/fts-indexes.js +241 -23
- package/dist/core/wiki/graph-queries.js +145 -112
- package/dist/core/wiki/llm-client.d.ts +29 -3
- package/dist/core/wiki/llm-client.js +47 -12
- package/dist/core/wiki/prompts.d.ts +19 -0
- package/dist/core/wiki/prompts.js +20 -1
- package/dist/lib/utils.d.ts +113 -0
- package/dist/lib/utils.js +185 -0
- package/dist/mcp/local/aop-metadata.d.ts +77 -0
- package/dist/mcp/local/aop-metadata.js +227 -0
- package/dist/mcp/local/bean-metadata.d.ts +2 -1
- package/dist/mcp/local/bean-metadata.js +22 -1
- package/dist/mcp/local/convex-metadata.d.ts +8 -0
- package/dist/mcp/local/convex-metadata.js +45 -0
- package/dist/mcp/local/local-backend.d.ts +174 -11
- package/dist/mcp/local/local-backend.js +1678 -331
- package/dist/mcp/local/path-predicate.d.ts +19 -0
- package/dist/mcp/local/path-predicate.js +21 -0
- package/dist/mcp/local/pdg-impact.d.ts +132 -10
- package/dist/mcp/local/pdg-impact.js +391 -98
- package/dist/mcp/repository-policy.d.ts +1 -0
- package/dist/mcp/repository-policy.js +5 -0
- package/dist/mcp/resources.js +26 -4
- package/dist/mcp/server.js +23 -13
- package/dist/mcp/tools.d.ts +8 -0
- package/dist/mcp/tools.js +76 -23
- package/dist/server/analyze-job.d.ts +37 -2
- package/dist/server/analyze-job.js +13 -2
- package/dist/server/analyze-launch.d.ts +1 -1
- package/dist/server/analyze-launch.js +79 -10
- package/dist/server/analyze-worker-core.d.ts +1 -1
- package/dist/server/analyze-worker-core.js +10 -1
- package/dist/server/analyze-worker-ipc.d.ts +1 -1
- package/dist/server/analyze-worker-ipc.js +4 -0
- package/dist/server/analyze-worker-protocol.d.ts +59 -0
- package/dist/server/analyze-worker-protocol.js +1 -0
- package/dist/server/analyze-worker.d.ts +1 -17
- package/dist/server/api.d.ts +2 -11
- package/dist/server/api.js +164 -163
- package/dist/server/embed-run-outcome.d.ts +89 -0
- package/dist/server/embed-run-outcome.js +88 -0
- package/dist/server/git-clone.js +6 -2
- package/dist/server/middleware.d.ts +116 -9
- package/dist/server/middleware.js +291 -22
- package/dist/server/sse-progress.d.ts +41 -0
- package/dist/server/sse-progress.js +125 -0
- package/dist/storage/branch-index.d.ts +11 -6
- package/dist/storage/branch-index.js +15 -7
- package/dist/storage/file-hash.js +2 -2
- package/dist/storage/fs-atomic.d.ts +25 -0
- package/dist/storage/fs-atomic.js +42 -0
- package/dist/storage/git.d.ts +131 -0
- package/dist/storage/git.js +236 -2
- package/dist/storage/index-lock.d.ts +102 -0
- package/dist/storage/index-lock.js +660 -0
- package/dist/storage/parse-cache.js +588 -6
- package/dist/storage/parsedfile-store.js +76 -6
- package/dist/storage/repo-manager.d.ts +54 -441
- package/dist/storage/repo-manager.js +312 -221
- package/dist/storage/repo-meta.d.ts +530 -0
- package/dist/storage/repo-meta.js +100 -0
- package/dist/types/pipeline.d.ts +37 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +1 -1
- package/hooks/claude/gitnexus-hook.cjs +1 -1
- package/hooks/claude/resolve-analyze-cmd.cjs +80 -13
- package/package.json +11 -2
- package/scripts/cross-platform-shard.ts +153 -0
- package/scripts/cross-platform-tests.ts +122 -0
- package/scripts/run-cross-platform.ts +16 -5
- package/skills/gitnexus-cli.md +3 -3
- package/skills/gitnexus-debugging.md +37 -7
- package/skills/gitnexus-exploring.md +26 -4
- package/skills/gitnexus-impact-analysis.md +71 -10
- package/skills/gitnexus-plan/README.md +11 -6
- package/skills/gitnexus-plan/references/evidence-provenance.md +49 -12
- package/skills/gitnexus-plan/scripts/evidence-provenance.mjs +654 -372
- package/skills/gitnexus-refactoring.md +48 -5
- package/skills/gitnexus-review/SKILL.md +12 -6
- package/skills/gitnexus-review/ci-personas/ci-adversarial-lens.md +1 -1
- package/skills/gitnexus-review/ci-personas/ci-blast-radius-lens.md +1 -1
- package/skills/gitnexus-review/ci-personas/ci-correctness-lens.md +1 -1
- package/skills/gitnexus-review/ci-personas/ci-coverage-lens.md +1 -1
- package/skills/gitnexus-review/ci-personas/ci-critic-lens.md +1 -1
- package/skills/gitnexus-review/ci-personas/ci-security-lens.md +1 -1
- package/skills/gitnexus-taint-analysis.md +13 -7
- package/skills/gitnexus-work/SKILL.md +4 -1
- package/skills/gitnexus-work/references/evidence-provenance.md +49 -12
- package/skills/gitnexus-work/scripts/evidence-provenance.mjs +654 -372
- package/vendor/leiden/utils.cjs +28 -1
- package/web/assets/ProcessFlowModal-Badh2Pbg.js +24 -0
- package/web/assets/{__vite-browser-external-bMKyYQkO.js → __vite-browser-external-COxE8F0G.js} +1 -1
- package/web/assets/abnfDiagram-QNN2D4NM-Cq2djWWe.js +1 -0
- package/web/assets/agent-Dr4l5EOp.js +662 -0
- package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +1 -0
- package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-DBefcjof.js → blockDiagram-54WLDVWM-Dg_hrRWE.js} +4 -4
- package/web/assets/{c4Diagram-Y2BXMSZH-EjWnjEJl.js → c4Diagram-X2LASM7V-CnhoT4aV.js} +2 -2
- package/web/assets/chunk-224SPVON-B8gxtXpy.js +1 -0
- package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +127 -0
- package/web/assets/chunk-2SREHG4O-Dip42xOq.js +1 -0
- package/web/assets/chunk-4RFN2BYJ-DHHWuC-n.js +1 -0
- package/web/assets/chunk-66DQ2XMT-CM0xjFAN.js +1 -0
- package/web/assets/chunk-6BELYETK-ChsYtfHW.js +1 -0
- package/web/assets/{chunk-LII3EMHJ-BL6lKJ_P.js → chunk-6L755F7B-CMhLBxi2.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-DVXPOIxe.js → chunk-7CWYLC5S-DRgGkkpF.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-C3v-4Fsw.js → chunk-A7VWPJGB-B4O3oQdQ.js} +1 -1
- package/web/assets/chunk-BI6VK774-D0oaWk8V.js +1 -0
- package/web/assets/chunk-D6VWDJW2-BxcBdZ9c.js +2 -0
- package/web/assets/chunk-DMV4VAQV-Bn6WXDSX.js +4 -0
- package/web/assets/chunk-E5QJAATJ-dh-5CtIV.js +2 -0
- package/web/assets/chunk-EQFTRU2I-1ix7TzW9.js +2 -0
- package/web/assets/chunk-FWYVLQTC-BgDItRzJ.js +1 -0
- package/web/assets/chunk-HNC4WDU7-DoU3KnA7.js +1 -0
- package/web/assets/chunk-L5GCOVLC-CE2w8Nm_.js +161 -0
- package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +321 -0
- package/web/assets/chunk-LORZLZWG-B57uGw2N.js +88 -0
- package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +62 -0
- package/web/assets/chunk-NV3KIAZN-5yvcpMiu.js +1 -0
- package/web/assets/chunk-OIYT25JQ-CQ6KOZGX.js +1 -0
- package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +10 -0
- package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +156 -0
- package/web/assets/chunk-RRFMTAIC-CMN0wcYA.js +1 -0
- package/web/assets/{chunk-RLI5ZMPA-CkXZ0y0U.js → chunk-SATU7PGQ-B1zhgOvc.js} +1 -1
- package/web/assets/chunk-STOV2HOB-GnExfzIX.js +1 -0
- package/web/assets/{chunk-INKRHTLW-Ckl3_KkO.js → chunk-SZD42YQK-ijyKKVr1.js} +21 -21
- package/web/assets/{chunk-LCXTWHL2-C162wE5z.js → chunk-TFPGFQPA-Dj6a-0pC.js} +3 -3
- package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +2 -0
- package/web/assets/chunk-TZUXEDM2-B7p9AcTw.js +1 -0
- package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +1 -0
- package/web/assets/{chunk-6764PJDD-C7xxk063.js → chunk-VE5CLXGZ-C1vB7wpY.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-ZNgyLFq6.js → chunk-VJFVEZTB-CX66F2ep.js} +3 -3
- package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +1 -0
- package/web/assets/chunk-WJBAP47W-BckNkuEN.js +1 -0
- package/web/assets/chunk-XNMVGMAZ-Cg_LnPtj.js +1 -0
- package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +1 -0
- package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +1 -0
- package/web/assets/{cose-bilkent-UX7MHV2Q-BmnZ7Mvj.js → cose-bilkent-RZA4XIPR-BHpK4Crp.js} +1 -1
- package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +1 -0
- package/web/assets/cynefinDiagram-TR7LZUCG-C3Iar9lZ.js +62 -0
- package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +4 -0
- package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +30 -0
- package/web/assets/diagram-JCD7PQ7C-DC5-9FRr.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-DVRltWbL.js → diagram-KFBZAVDW-Dfmf_4ti.js} +3 -3
- package/web/assets/diagram-L7DVE3PN-GIRyVUtt.js +41 -0
- package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +24 -0
- package/web/assets/ebnfDiagram-DRHRQJWO-D8_CI4ZF.js +1 -0
- package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +85 -0
- package/web/assets/error-D9MtuG9s.js +1 -0
- package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +1 -0
- package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +1 -0
- package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +292 -0
- package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +1 -0
- package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +106 -0
- package/web/assets/{index-D7mTFcvQ.js → index-2zdvEdzg.js} +51 -51
- package/web/assets/index-CJDpyH-n.css +2 -0
- package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +1 -0
- package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-XpjJRU9h.js → ishikawaDiagram-4JDSWWDM-DmqWtjkl.js} +2 -2
- package/web/assets/{journeyDiagram-M6C3CM3L-C6wjooas.js → journeyDiagram-QUS3FAKT-XAzVzrQW.js} +3 -3
- package/web/assets/{kanban-definition-75IXJCU3-5_UVY79g.js → kanban-definition-D4L7SQKK-YurRnmYn.js} +6 -6
- package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +257 -0
- package/web/assets/{mindmap-definition-2TDM6QVE-8xDLzk2e.js → mindmap-definition-66QP5TD6-ChrWWnil.js} +5 -5
- package/web/assets/node.browser-U-KyT5AW.js +1 -0
- package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +1 -0
- package/web/assets/pegDiagram-YKIO5C6U-QGeQLEq2.js +1 -0
- package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +1 -0
- package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +39 -0
- package/web/assets/{quadrantDiagram-VICAPDV7-DR54jOeR.js → quadrantDiagram-VO5QLCXA-B7098wSc.js} +3 -3
- package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +1 -0
- package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +1 -0
- package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +1 -0
- package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +1 -0
- package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +1 -0
- package/web/assets/railroadDiagram-M77LTMSB-AXu4vxk_.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-PEkg7sKC.js → requirementDiagram-RASO5HTO-B-l8InpK.js} +4 -4
- package/web/assets/{sankeyDiagram-URQDO5SZ-DDWEtjcs.js → sankeyDiagram-F32LOJ6L-Kd7zjJgX.js} +3 -3
- package/web/assets/{sequenceDiagram-VS2MUI6T-Bv-uwANx.js → sequenceDiagram-URATNSBD-BlcdY9DS.js} +7 -7
- package/web/assets/sizeCapture-5GORAGP4-BEqQzsxs.js +1 -0
- package/web/assets/src-C5sx_gMp.js +1 -0
- package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +1 -0
- package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +1 -0
- package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +2 -0
- package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +8 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Qmsuo7qS.js → timeline-definition-WJXWRH4I-DUfp1R3E.js} +3 -3
- package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +1 -0
- package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +1 -0
- package/web/assets/vennDiagram-K3VCMQLI-Bj-FN-XS.js +34 -0
- package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +1 -0
- package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +78 -0
- package/web/assets/xychartDiagram-5VRUVLCW-CK_rbSWE.js +7 -0
- package/web/index.html +16 -15
- package/dist/core/ingestion/languages/python/index-stats.d.ts +0 -18
- package/dist/core/ingestion/languages/python/index-stats.js +0 -25
- package/web/assets/ProcessFlowModal-CwkpVkv-.js +0 -22
- package/web/assets/agent-Ds-lkZjX.js +0 -626
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BVWFvYz2.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-CmG3uPWf.js +0 -36
- package/web/assets/chunk-2T2R6R2M-Bb6Sy3_j.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CXlhF3Pt.js +0 -1
- package/web/assets/chunk-3SSMPTDK-rPsRM1x0.js +0 -321
- package/web/assets/chunk-4R4BOZG6-Be_F0JoX.js +0 -159
- package/web/assets/chunk-67TQ5CYL-CA7OhIEf.js +0 -124
- package/web/assets/chunk-7J6CGLKN-BlKwgXKs.js +0 -10
- package/web/assets/chunk-C62D2QBJ-3gdL8EKc.js +0 -1
- package/web/assets/chunk-CEXFNPSA-CjwtMDkc.js +0 -1
- package/web/assets/chunk-J5EP6P6S-L19oz55R.js +0 -1
- package/web/assets/chunk-KGFNY3KK-BZeP-55u.js +0 -62
- package/web/assets/chunk-KGYTTC2M-zm6n242I.js +0 -1
- package/web/assets/chunk-KNLZD3CH-BGlP6X9R.js +0 -1
- package/web/assets/chunk-LRIF4GLE-n59lhprL.js +0 -1
- package/web/assets/chunk-RERM46MO-B5dAdWpP.js +0 -1
- package/web/assets/chunk-RKZBBQEN-MyIQbGgD.js +0 -1
- package/web/assets/chunk-T2UQINTJ-BEyXhZWe.js +0 -1
- package/web/assets/chunk-UP6H54XL-BG-n7qDp.js +0 -1
- package/web/assets/chunk-UXSXWOXI-CW1_r5cS.js +0 -1
- package/web/assets/chunk-UY5QBCOK-ByqTVLvh.js +0 -1
- package/web/assets/chunk-VU6ZFW4Y-BLeasdkU.js +0 -2
- package/web/assets/classDiagram-KGZ6W3CR-olpO3fMe.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-olpO3fMe.js +0 -1
- package/web/assets/dagre-ND4H6XIP-CY4iIF7v.js +0 -4
- package/web/assets/diagram-3NCE3AQN-CO6QpzEE.js +0 -43
- package/web/assets/diagram-GF46GFSD-B2atbzmU.js +0 -24
- package/web/assets/diagram-QXG6HAR7-DR6AHD2h.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-DkpoMwTL.js +0 -10
- package/web/assets/erDiagram-L5TCEMPS-n8g_90bc.js +0 -85
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-CgFBM7e-.js +0 -1
- package/web/assets/flowDiagram-H6V6AXG4-SGMDiCkB.js +0 -162
- package/web/assets/ganttDiagram-JCBTUEKG-Cb3UBqRS.js +0 -292
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DCJv7SII.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-Bs9CR7tq.js +0 -106
- package/web/assets/index-CX_fADmQ.css +0 -2
- package/web/assets/info-J43DQDTF-KCYPFFUO-BfNy-9Nd.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-T368on4D.js +0 -2
- package/web/assets/katex-K3KEBU37-qRQBy-EZ.js +0 -261
- package/web/assets/node-Ct_zna8U.js +0 -8
- package/web/assets/packet-YPE3B663-LP52Z2RK-D4qkeG9k.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-B_yMKyQa.js +0 -1
- package/web/assets/pieDiagram-CU6KROY3-Bv4LpAbr.js +0 -30
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DeD6yN_0.js +0 -1
- package/web/assets/sdk-CzHHH8tD.js +0 -37
- package/web/assets/src-Cv1eWg0o.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-DVNjmkEq.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-CyN4Fh7_.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CDuSYnd4.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-imazucyJ.js +0 -1
- package/web/assets/vennDiagram-MWXL3ELB-BD1iK1SX.js +0 -34
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-DqrT9tB8.js +0 -1
- package/web/assets/wardleyDiagram-CUQ6CDDI-D6TzLVob.js +0 -78
- package/web/assets/xychartDiagram-N2JHSOCM-CoZ_Tits.js +0 -7
- /package/web/assets/{chunk-7W6UQGC5-pyKGssV3.js → chunk-LIEV3EAG-pyKGssV3.js} +0 -0
|
@@ -0,0 +1,1184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-rolled dispatch-guard route extractor (JavaScript / TypeScript).
|
|
3
|
+
*
|
|
4
|
+
* Every route extractor before this one recognises a route because a FRAMEWORK
|
|
5
|
+
* declares it: a decorator, a `Route::get()` call, a filesystem convention. A
|
|
6
|
+
* server written against raw `node:http` declares its routes the only way the
|
|
7
|
+
* language offers — by COMPARING the request path to a literal:
|
|
8
|
+
*
|
|
9
|
+
* if (req.method === 'GET' && pathname === '/api/live/portfolio') { … }
|
|
10
|
+
*
|
|
11
|
+
* That is a route definition in every sense that matters to this graph: it has a
|
|
12
|
+
* path, a verb, and a handler. GitNexus simply had no rule that could see it, so
|
|
13
|
+
* `route_map` answered "No routes found in this project" for a repo with
|
|
14
|
+
* seventeen route modules and 113 such comparisons — the same confident-empty
|
|
15
|
+
* failure this whole change set is about, one tool wide.
|
|
16
|
+
*
|
|
17
|
+
* PRECISION OVER RECALL, deliberately. A missed route is a coverage limit; an
|
|
18
|
+
* invented route is a false fact, and `route_map` presents its output as fact.
|
|
19
|
+
* So every rule here requires the comparison to be against something that is
|
|
20
|
+
* demonstrably a request path, and anything that cannot be converted cleanly is
|
|
21
|
+
* dropped rather than guessed at. Specifically NOT extracted:
|
|
22
|
+
*
|
|
23
|
+
* - `pathname.startsWith('/api/')` — a namespace test ("do I own this?"),
|
|
24
|
+
* not a route. Minting `/api` would claim a route nobody serves.
|
|
25
|
+
* - a bare `pathname === '/'` with no verb — far more often a normalisation
|
|
26
|
+
* branch (`pathname === '/' ? '/index.html' : pathname`) than a route. With
|
|
27
|
+
* a verb alongside it the intent is unambiguous, so that form IS extracted.
|
|
28
|
+
* - any regex whose body is not a literal path plus single-segment wildcards.
|
|
29
|
+
*
|
|
30
|
+
* One consequence worth stating rather than discovering: a single-page app that
|
|
31
|
+
* branches on `location.pathname === '/settings'` mints a Route too. That is
|
|
32
|
+
* intentional — it is the same claim a Next.js filesystem route makes, that this
|
|
33
|
+
* file serves this path — and it keeps the rule from needing to guess whether a
|
|
34
|
+
* comparison is "backend enough". It does mean `route_map` on a SPA reports
|
|
35
|
+
* client routes alongside API ones, distinguishable by their `source`.
|
|
36
|
+
*
|
|
37
|
+
* @module route-extractors/dispatch-guard
|
|
38
|
+
*/
|
|
39
|
+
/** Provenance stamped on the Route node, in place of `decorator-<name>`. */
|
|
40
|
+
export const DISPATCH_GUARD_SOURCE = 'dispatch-guard-route';
|
|
41
|
+
const HTTP_VERBS = new Set([
|
|
42
|
+
'GET',
|
|
43
|
+
'POST',
|
|
44
|
+
'PUT',
|
|
45
|
+
'PATCH',
|
|
46
|
+
'DELETE',
|
|
47
|
+
'HEAD',
|
|
48
|
+
'OPTIONS',
|
|
49
|
+
]);
|
|
50
|
+
const EQUALITY_OPERATORS = new Set(['===', '==']);
|
|
51
|
+
/**
|
|
52
|
+
* Expressions that denote the request path. Kept deliberately narrow: this is
|
|
53
|
+
* the predicate standing between "a string comparison" and "a route", so a loose
|
|
54
|
+
* match here is how invented routes would get in. `path` alone is excluded — in
|
|
55
|
+
* Node it is overwhelmingly the `node:path` module or a filesystem path.
|
|
56
|
+
*/
|
|
57
|
+
const PATH_IDENTIFIERS = new Set([
|
|
58
|
+
'pathname',
|
|
59
|
+
'pathName',
|
|
60
|
+
'urlPath',
|
|
61
|
+
'routePath',
|
|
62
|
+
'reqPath',
|
|
63
|
+
'requestPath',
|
|
64
|
+
]);
|
|
65
|
+
/** `req.url` / `request.url` — the raw form, before a URL parse. */
|
|
66
|
+
const RAW_URL_RECEIVERS = new Set(['req', 'request']);
|
|
67
|
+
/**
|
|
68
|
+
* Cheap pre-filter, so this costs nothing on the overwhelming majority of files.
|
|
69
|
+
*
|
|
70
|
+
* Sound by construction rather than by luck: every rule below reaches a route
|
|
71
|
+
* only through {@link isPathExpression}, which returns true only for one of the
|
|
72
|
+
* {@link PATH_IDENTIFIERS} or for a member access whose property is `pathname` /
|
|
73
|
+
* `url`. A file whose source contains none of those substrings cannot produce a
|
|
74
|
+
* route, so skipping the walk cannot change the output. Keep this alternation in
|
|
75
|
+
* step with those two predicates — widening one without the other would silently
|
|
76
|
+
* re-introduce the empty answer this module exists to remove.
|
|
77
|
+
*/
|
|
78
|
+
const PATH_TOKEN_HINT = /pathname|pathName|urlPath|routePath|reqPath|requestPath|\.\s*url\b/;
|
|
79
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
80
|
+
'function_declaration',
|
|
81
|
+
'generator_function_declaration',
|
|
82
|
+
'function_expression',
|
|
83
|
+
'generator_function',
|
|
84
|
+
'arrow_function',
|
|
85
|
+
'method_definition',
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* A string literal that could be a URL path: leading slash, no whitespace, and
|
|
89
|
+
* no scheme. The character class is permissive about what a path may CONTAIN
|
|
90
|
+
* (`{id}`, `:id`, `%20`, `.json` are all legitimate) because the leading slash
|
|
91
|
+
* plus a path-denoting operand already carries the discrimination.
|
|
92
|
+
*/
|
|
93
|
+
function isPathLiteral(value) {
|
|
94
|
+
if (!value.startsWith('/'))
|
|
95
|
+
return false;
|
|
96
|
+
if (value.includes('://'))
|
|
97
|
+
return false;
|
|
98
|
+
return /^\/[\w\-./{}:$*%~@]*$/.test(value);
|
|
99
|
+
}
|
|
100
|
+
/** Follow `a = b = 'literal'` chains, with a cap so a cycle cannot hang. */
|
|
101
|
+
const MAX_CONSTANT_HOPS = 4;
|
|
102
|
+
function buildConstantMap(root) {
|
|
103
|
+
const direct = new Map(); // name -> literal
|
|
104
|
+
const alias = new Map(); // name -> other name
|
|
105
|
+
const ambiguous = new Set();
|
|
106
|
+
const record = (map, name, value) => {
|
|
107
|
+
const existing = map.get(name);
|
|
108
|
+
if (existing !== undefined && existing !== value)
|
|
109
|
+
ambiguous.add(name);
|
|
110
|
+
else
|
|
111
|
+
map.set(name, value);
|
|
112
|
+
};
|
|
113
|
+
const visit = (node) => {
|
|
114
|
+
if (node.type === 'variable_declarator') {
|
|
115
|
+
const name = node.childForFieldName('name');
|
|
116
|
+
const value = unparenthesize(node.childForFieldName('value'));
|
|
117
|
+
if (name !== null && name.type === 'identifier' && value !== null) {
|
|
118
|
+
if (value.type === 'string' || value.type === 'template_string') {
|
|
119
|
+
const raw = plainLiteralValue(value);
|
|
120
|
+
if (raw !== null)
|
|
121
|
+
record(direct, name.text, raw);
|
|
122
|
+
}
|
|
123
|
+
else if (value.type === 'identifier') {
|
|
124
|
+
record(alias, name.text, value.text);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const child of node.namedChildren)
|
|
129
|
+
visit(child);
|
|
130
|
+
};
|
|
131
|
+
visit(root);
|
|
132
|
+
const resolved = new Map();
|
|
133
|
+
for (const name of [...direct.keys(), ...alias.keys()]) {
|
|
134
|
+
if (ambiguous.has(name))
|
|
135
|
+
continue;
|
|
136
|
+
let current = name;
|
|
137
|
+
for (let hop = 0; hop < MAX_CONSTANT_HOPS; hop++) {
|
|
138
|
+
if (ambiguous.has(current))
|
|
139
|
+
break;
|
|
140
|
+
const literal = direct.get(current);
|
|
141
|
+
if (literal !== undefined) {
|
|
142
|
+
resolved.set(name, literal);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
const next = alias.get(current);
|
|
146
|
+
if (next === undefined)
|
|
147
|
+
break;
|
|
148
|
+
current = next;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return resolved;
|
|
152
|
+
}
|
|
153
|
+
/** Unquote a plain string / substitution-free template literal. */
|
|
154
|
+
function plainLiteralValue(node) {
|
|
155
|
+
if (node.type !== 'string' && node.type !== 'template_string')
|
|
156
|
+
return null;
|
|
157
|
+
if (node.type === 'template_string' &&
|
|
158
|
+
node.namedChildren.some((c) => c.type !== 'string_fragment')) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const text = node.text;
|
|
162
|
+
if (text.length < 2)
|
|
163
|
+
return null;
|
|
164
|
+
return text.slice(1, -1);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The string this expression denotes, folding same-file constants where it can.
|
|
168
|
+
*
|
|
169
|
+
* Handles a plain literal, a template string whose substitutions all resolve to
|
|
170
|
+
* known constants, and `+` concatenation of those. Returns `null` the moment any
|
|
171
|
+
* part is unknown — a partially-folded path would be a wrong route, and a route
|
|
172
|
+
* that is missing is the cheaper of the two failures.
|
|
173
|
+
*/
|
|
174
|
+
function literalValue(node, constants = new Map()) {
|
|
175
|
+
const plain = plainLiteralValue(node);
|
|
176
|
+
if (plain !== null)
|
|
177
|
+
return plain;
|
|
178
|
+
if (node.type === 'identifier')
|
|
179
|
+
return constants.get(node.text) ?? null;
|
|
180
|
+
if (node.type === 'template_string') {
|
|
181
|
+
let out = '';
|
|
182
|
+
for (const child of node.namedChildren) {
|
|
183
|
+
if (child.type === 'string_fragment') {
|
|
184
|
+
out += child.text;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (child.type !== 'template_substitution')
|
|
188
|
+
return null;
|
|
189
|
+
const inner = unparenthesize(child.namedChildren[0] ?? null);
|
|
190
|
+
if (inner === null)
|
|
191
|
+
return null;
|
|
192
|
+
const value = literalValue(inner, constants);
|
|
193
|
+
if (value === null)
|
|
194
|
+
return null;
|
|
195
|
+
out += value;
|
|
196
|
+
}
|
|
197
|
+
return out;
|
|
198
|
+
}
|
|
199
|
+
if (node.type === 'binary_expression' && node.childForFieldName('operator')?.text === '+') {
|
|
200
|
+
const left = unparenthesize(node.childForFieldName('left'));
|
|
201
|
+
const right = unparenthesize(node.childForFieldName('right'));
|
|
202
|
+
if (left === null || right === null)
|
|
203
|
+
return null;
|
|
204
|
+
const leftValue = literalValue(left, constants);
|
|
205
|
+
const rightValue = literalValue(right, constants);
|
|
206
|
+
if (leftValue === null || rightValue === null)
|
|
207
|
+
return null;
|
|
208
|
+
return leftValue + rightValue;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Does this expression denote the request path? Accepts a bare identifier from
|
|
214
|
+
* {@link PATH_IDENTIFIERS}, any member access ending in `.pathname`, and the raw
|
|
215
|
+
* `req.url` / `request.url` forms.
|
|
216
|
+
*/
|
|
217
|
+
function isPathExpression(node) {
|
|
218
|
+
if (node.type === 'identifier')
|
|
219
|
+
return PATH_IDENTIFIERS.has(node.text);
|
|
220
|
+
if (node.type === 'member_expression') {
|
|
221
|
+
const property = node.childForFieldName('property');
|
|
222
|
+
if (property === null)
|
|
223
|
+
return false;
|
|
224
|
+
if (PATH_IDENTIFIERS.has(property.text))
|
|
225
|
+
return true;
|
|
226
|
+
if (property.text === 'url') {
|
|
227
|
+
const object = node.childForFieldName('object');
|
|
228
|
+
return object !== null && RAW_URL_RECEIVERS.has(object.text);
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
/** A logical `!`. `-` and `~` are unary too and are not negation. */
|
|
235
|
+
function isNegation(node) {
|
|
236
|
+
return node.type === 'unary_expression' && node.childForFieldName('operator')?.text === '!';
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Is this comparison reached only when it is FALSE?
|
|
240
|
+
*
|
|
241
|
+
* The module already refuses to inherit a verb from an `if` whose `else` branch
|
|
242
|
+
* holds the comparison, for the reason stated in `governingVerb`: the branch runs
|
|
243
|
+
* precisely when the condition did NOT hold, so attributing it is backwards.
|
|
244
|
+
* `!` is the same fact written as an operator, and it was not handled — a stated
|
|
245
|
+
* invariant with half an implementation, which is worse than an absent one
|
|
246
|
+
* because the doc comment reads as though it were covered.
|
|
247
|
+
*
|
|
248
|
+
* Measured before fixing. `if (!(pathname === '/api/admin'))` INVENTED
|
|
249
|
+
* `/api/admin`; `if (!(req.method === 'GET') && pathname === '/api/x')` emitted
|
|
250
|
+
* `GET /api/x`, the one verb the branch guarantees the request does not have.
|
|
251
|
+
*
|
|
252
|
+
* PARITY, not presence: `!!x` is `x`, and a rule keyed on "is there a `!` above
|
|
253
|
+
* me" would refuse a positive condition.
|
|
254
|
+
*
|
|
255
|
+
* The walk stops at the FUNCTION boundary and nowhere else. An earlier draft
|
|
256
|
+
* also broke at `statement_block`, reasoning that `if (!cond) { … }` must not
|
|
257
|
+
* negate a comparison written in its body — true, but already guaranteed by the
|
|
258
|
+
* tree shape: the `!` lives in the if's CONDITION, which is a sibling of the
|
|
259
|
+
* block, never an ancestor of anything inside it. So that break could only ever
|
|
260
|
+
* fire where a `!` genuinely IS an ancestor across a block, i.e. an IIFE — which
|
|
261
|
+
* the function-boundary stop catches first. Unreachable, and unreachable in the
|
|
262
|
+
* UNSAFE direction: stopping early under-counts negations, and an under-count
|
|
263
|
+
* reads a negated guard as positive and invents the route. Removed rather than
|
|
264
|
+
* kept for symmetry.
|
|
265
|
+
*/
|
|
266
|
+
function isNegatedContext(node) {
|
|
267
|
+
let negations = 0;
|
|
268
|
+
let current = node;
|
|
269
|
+
let parent = current.parent;
|
|
270
|
+
while (parent !== null && !FUNCTION_NODE_TYPES.has(parent.type)) {
|
|
271
|
+
if (isNegation(parent))
|
|
272
|
+
negations += 1;
|
|
273
|
+
current = parent;
|
|
274
|
+
parent = current.parent;
|
|
275
|
+
}
|
|
276
|
+
return negations % 2 === 1;
|
|
277
|
+
}
|
|
278
|
+
/** Strip redundant parentheses, which the grammar keeps as real nodes. */
|
|
279
|
+
function unparenthesize(node) {
|
|
280
|
+
let current = node;
|
|
281
|
+
while (current !== null && current.type === 'parenthesized_expression') {
|
|
282
|
+
current = current.namedChildren[0] ?? null;
|
|
283
|
+
}
|
|
284
|
+
return current;
|
|
285
|
+
}
|
|
286
|
+
/** Does this expression denote the request METHOD (`req.method`, `method`)? */
|
|
287
|
+
function isMethodExpression(node) {
|
|
288
|
+
if (node.type === 'identifier')
|
|
289
|
+
return node.text === 'method' || node.text === 'httpMethod';
|
|
290
|
+
if (node.type === 'member_expression') {
|
|
291
|
+
const property = node.childForFieldName('property');
|
|
292
|
+
return property !== null && (property.text === 'method' || property.text === 'httpMethod');
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* The HTTP verb an equality comparison asserts, if it is one — `req.method ===
|
|
298
|
+
* 'GET'` → `GET`. Case-normalised, so `'get'` works too.
|
|
299
|
+
*/
|
|
300
|
+
function verbFromComparison(node) {
|
|
301
|
+
if (node.type !== 'binary_expression')
|
|
302
|
+
return null;
|
|
303
|
+
const operator = node.childForFieldName('operator')?.text ?? '';
|
|
304
|
+
if (!EQUALITY_OPERATORS.has(operator))
|
|
305
|
+
return null;
|
|
306
|
+
const left = node.childForFieldName('left');
|
|
307
|
+
const right = node.childForFieldName('right');
|
|
308
|
+
if (left === null || right === null)
|
|
309
|
+
return null;
|
|
310
|
+
for (const [expr, literal] of [
|
|
311
|
+
[left, right],
|
|
312
|
+
[right, left],
|
|
313
|
+
]) {
|
|
314
|
+
if (!isMethodExpression(expr))
|
|
315
|
+
continue;
|
|
316
|
+
const value = literalValue(literal);
|
|
317
|
+
if (value === null)
|
|
318
|
+
continue;
|
|
319
|
+
const verb = value.toUpperCase();
|
|
320
|
+
if (HTTP_VERBS.has(verb))
|
|
321
|
+
return verb;
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Find the verbs that govern a path comparison, by walking outward.
|
|
327
|
+
*
|
|
328
|
+
* Two idioms, both common and both handled:
|
|
329
|
+
* `if (req.method === 'GET' && pathname === '/x')` — a sibling in the same
|
|
330
|
+
* condition; and
|
|
331
|
+
* `if (req.method === 'GET') { if (pathname === '/x') … }` — an enclosing
|
|
332
|
+
* guard.
|
|
333
|
+
*
|
|
334
|
+
* The walk stops at the function boundary, and REFUSES to inherit a verb from an
|
|
335
|
+
* `if` whose `else` branch we are standing in: in
|
|
336
|
+
* `if (req.method === 'POST') {…} else if (pathname === '/x')` the path
|
|
337
|
+
* comparison is reached precisely when the method is NOT POST, so attributing
|
|
338
|
+
* POST to it would be exactly backwards.
|
|
339
|
+
*
|
|
340
|
+
* Returns a LIST because one guard can serve several methods:
|
|
341
|
+
* `if ((req.method === 'GET' || req.method === 'POST') && pathname === '/x')` is
|
|
342
|
+
* two routes, and returning the first verb reported it as GET-only — a route
|
|
343
|
+
* that silently loses its other methods reads as a narrower contract than the
|
|
344
|
+
* code implements. Empty means "no verb is guaranteed", which stays verb-less.
|
|
345
|
+
*/
|
|
346
|
+
function governingVerbs(comparison) {
|
|
347
|
+
let current = comparison;
|
|
348
|
+
let parent = current.parent;
|
|
349
|
+
while (parent !== null && !FUNCTION_NODE_TYPES.has(parent.type)) {
|
|
350
|
+
if (parent.type === 'binary_expression' &&
|
|
351
|
+
parent.childForFieldName('operator')?.text === '&&') {
|
|
352
|
+
const sibling = parent.childForFieldName('left')?.id === current.id
|
|
353
|
+
? parent.childForFieldName('right')
|
|
354
|
+
: parent.childForFieldName('left');
|
|
355
|
+
const verbs = sibling === null ? [] : findVerbsInSubtree(sibling);
|
|
356
|
+
if (verbs.length > 0)
|
|
357
|
+
return verbs;
|
|
358
|
+
}
|
|
359
|
+
if (parent.type === 'if_statement') {
|
|
360
|
+
const alternative = parent.childForFieldName('alternative');
|
|
361
|
+
const inElseBranch = alternative !== null && alternative.id === current.id;
|
|
362
|
+
const condition = parent.childForFieldName('condition');
|
|
363
|
+
// A comparison inside the condition itself is handled by the `&&` rule
|
|
364
|
+
// above; here we only inherit from an ENCLOSING if we are governed by.
|
|
365
|
+
if (!inElseBranch && condition !== null && condition.id !== current.id) {
|
|
366
|
+
const verbs = findVerbsInSubtree(condition);
|
|
367
|
+
if (verbs.length > 0)
|
|
368
|
+
return verbs;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
current = parent;
|
|
372
|
+
parent = current.parent;
|
|
373
|
+
}
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The verb a subtree GUARANTEES when it evaluates truthy.
|
|
378
|
+
*
|
|
379
|
+
* `negated` counts whether an odd number of `!` stands between the question and
|
|
380
|
+
* this node. It is PARITY, the same rule `isNegatedContext` states — and the
|
|
381
|
+
* rule the previous presence-based check contradicted: it returned null at the
|
|
382
|
+
* first `!` it saw, so `!!(req.method === 'GET')` lost a verb the source states
|
|
383
|
+
* outright. A stated invariant with half an implementation, in the same module
|
|
384
|
+
* that had already been fixed for exactly that once.
|
|
385
|
+
*
|
|
386
|
+
* A verb reached at odd parity is the verb the branch EXCLUDES, so it yields
|
|
387
|
+
* nothing — the route survives, verb-less, which is the honest answer: this
|
|
388
|
+
* branch does not say which method it serves. Siblings are still searched,
|
|
389
|
+
* because excluding one verb says nothing about the next.
|
|
390
|
+
*/
|
|
391
|
+
function findVerbsInSubtree(node, negated = false) {
|
|
392
|
+
if (isNegation(node)) {
|
|
393
|
+
const operand = node.childForFieldName('argument');
|
|
394
|
+
return operand === null ? [] : findVerbsInSubtree(operand, !negated);
|
|
395
|
+
}
|
|
396
|
+
// A ternary SELECTS between its arms, so a verb inside one is not reached
|
|
397
|
+
// merely because the whole is truthy — see `verbsFromTernary`.
|
|
398
|
+
if (node.type === 'ternary_expression')
|
|
399
|
+
return verbsFromTernary(node, negated);
|
|
400
|
+
if (isDisjunction(node))
|
|
401
|
+
return verbsFromDisjunction(node, negated);
|
|
402
|
+
const direct = verbFromComparison(node);
|
|
403
|
+
if (direct !== null)
|
|
404
|
+
return negated ? [] : [direct];
|
|
405
|
+
// Generic descent keeps FIRST-match rather than unioning across children: an
|
|
406
|
+
// arbitrary node says nothing about how its children combine, and two verbs
|
|
407
|
+
// found under one are far more likely to be unrelated than alternatives. The
|
|
408
|
+
// one construct that genuinely means "either of these" is `||`, handled above.
|
|
409
|
+
for (const child of node.namedChildren) {
|
|
410
|
+
const found = findVerbsInSubtree(child, negated);
|
|
411
|
+
if (found.length > 0)
|
|
412
|
+
return found;
|
|
413
|
+
}
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
/** A logical `||`. */
|
|
417
|
+
function isDisjunction(node) {
|
|
418
|
+
return node.type === 'binary_expression' && node.childForFieldName('operator')?.text === '||';
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The verbs a disjunction guarantees — ALL of them, or none.
|
|
422
|
+
*
|
|
423
|
+
* `req.method === 'GET' || req.method === 'POST'` is the multi-method guard, and
|
|
424
|
+
* every operand names a verb, so the guard serves exactly those two.
|
|
425
|
+
*
|
|
426
|
+
* `req.method === 'GET' || isAdmin` is not: the branch is reached for ANY method
|
|
427
|
+
* when `isAdmin` holds, so the honest answer is no verb at all. Reporting `GET`
|
|
428
|
+
* — which is what taking the first match did — presents a route open to every
|
|
429
|
+
* method as one restricted to a single method, and this module's whole bar is
|
|
430
|
+
* that a wrong answer costs more than a missing one.
|
|
431
|
+
*
|
|
432
|
+
* So: every operand must yield at least one verb, or the whole disjunction
|
|
433
|
+
* yields none. At odd parity `!(A || B)` is `!A && !B`, which excludes verbs
|
|
434
|
+
* rather than offering them, so nothing is guaranteed either.
|
|
435
|
+
*/
|
|
436
|
+
function verbsFromDisjunction(node, negated) {
|
|
437
|
+
if (negated)
|
|
438
|
+
return [];
|
|
439
|
+
const operands = [node.childForFieldName('left'), node.childForFieldName('right')];
|
|
440
|
+
const collected = [];
|
|
441
|
+
for (const operand of operands) {
|
|
442
|
+
if (operand === null)
|
|
443
|
+
return [];
|
|
444
|
+
const verbs = findVerbsInSubtree(operand, false);
|
|
445
|
+
if (verbs.length === 0)
|
|
446
|
+
return [];
|
|
447
|
+
for (const verb of verbs)
|
|
448
|
+
if (!collected.includes(verb))
|
|
449
|
+
collected.push(verb);
|
|
450
|
+
}
|
|
451
|
+
return collected;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The verbs BOTH operands of a conjunction guarantee — their INTERSECTION.
|
|
455
|
+
*
|
|
456
|
+
* `A && B` is reached only when each side holds, so the methods it serves are
|
|
457
|
+
* the methods they agree on. Taking the first non-empty side instead — which is
|
|
458
|
+
* what {@link verbsFromTernary} did — reports one operand's set unintersected:
|
|
459
|
+
* `(GET || POST) ? (POST || PUT) : false` emitted GET and POST where only POST
|
|
460
|
+
* can reach the body, so the GET route was invented outright.
|
|
461
|
+
*
|
|
462
|
+
* An EMPTY side is "this operand names no method", not "this operand admits
|
|
463
|
+
* none", so it yields to the other rather than annihilating it — that is the
|
|
464
|
+
* `isAdmin && req.method === 'POST'` shape, and it is the whole reason the
|
|
465
|
+
* fallthrough existed. An empty INTERSECTION of two non-empty sides is the
|
|
466
|
+
* opposite: two conflicting method assertions, a guard nothing can satisfy. No
|
|
467
|
+
* verb is honest there, and the route survives verb-less, which is this
|
|
468
|
+
* module's stated direction for "cannot prove it".
|
|
469
|
+
*/
|
|
470
|
+
function intersectVerbs(a, b) {
|
|
471
|
+
if (a.length === 0)
|
|
472
|
+
return b;
|
|
473
|
+
if (b.length === 0)
|
|
474
|
+
return a;
|
|
475
|
+
return a.filter((verb) => b.includes(verb));
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* The verb a ternary guarantees — which is one only when an arm is a boolean
|
|
479
|
+
* literal, because that is what collapses the selection into a conjunction:
|
|
480
|
+
*
|
|
481
|
+
* c ? A : false ≡ c && A both hold, so INTERSECT both
|
|
482
|
+
* c ? false : B ≡ !c && B c must NOT hold, so search it at flipped parity
|
|
483
|
+
* c ? true : B ≡ c || B a disjunction guarantees neither operand
|
|
484
|
+
* c ? A : true ≡ !c || A likewise
|
|
485
|
+
*
|
|
486
|
+
* With two non-literal arms the verb is chosen by a condition whose value is
|
|
487
|
+
* unknown, so the ternary guarantees nothing.
|
|
488
|
+
*
|
|
489
|
+
* The two conjunctions intersect rather than take the first side that names a
|
|
490
|
+
* verb — see {@link intersectVerbs} for the route that mistake invented. The
|
|
491
|
+
* `||` rule below is the mirror image and already had it right: a disjunction
|
|
492
|
+
* UNIONS its operands, all-or-nothing.
|
|
493
|
+
*
|
|
494
|
+
* Measured before fixing: `(req.method === 'GET' ? false : true) && pathname ===
|
|
495
|
+
* '/api/i'` emitted `GET /api/i` — the one method that branch guarantees the
|
|
496
|
+
* request does NOT have, the same inversion `!` produced before `d4dcba8c`. The
|
|
497
|
+
* three shapes that were already right stay right; refusing every ternary would
|
|
498
|
+
* have been safe but would have dropped them.
|
|
499
|
+
*
|
|
500
|
+
* At odd parity every conjunction above becomes a disjunction (De Morgan) and
|
|
501
|
+
* guarantees nothing, so a negated ternary yields no verb. `!(c ? false : true)`
|
|
502
|
+
* is really `c` and could be read, but it needs BOTH arms folded as literals to
|
|
503
|
+
* see that, and no such condition has been observed in a real dispatcher.
|
|
504
|
+
* Declining is the safe direction: a missing verb, not an inverted one.
|
|
505
|
+
*/
|
|
506
|
+
function verbsFromTernary(node, negated) {
|
|
507
|
+
if (negated)
|
|
508
|
+
return [];
|
|
509
|
+
const condition = unparenthesize(node.childForFieldName('condition'));
|
|
510
|
+
const consequence = unparenthesize(node.childForFieldName('consequence'));
|
|
511
|
+
const alternative = unparenthesize(node.childForFieldName('alternative'));
|
|
512
|
+
if (condition === null || consequence === null || alternative === null)
|
|
513
|
+
return [];
|
|
514
|
+
if (alternative.type === 'false') {
|
|
515
|
+
return intersectVerbs(findVerbsInSubtree(condition, false), findVerbsInSubtree(consequence, false));
|
|
516
|
+
}
|
|
517
|
+
if (consequence.type === 'false') {
|
|
518
|
+
return intersectVerbs(findVerbsInSubtree(condition, true), findVerbsInSubtree(alternative, false));
|
|
519
|
+
}
|
|
520
|
+
return [];
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* The name of the function containing this comparison — the route's handler.
|
|
524
|
+
*
|
|
525
|
+
* Covers the declared forms and the two anonymous ones that carry a name from
|
|
526
|
+
* their binding site: `const handle = (req) => …` and the object-literal method
|
|
527
|
+
* shorthand (`{ async handle(req, res) {…} }`), which is how the reporting
|
|
528
|
+
* repo's route modules are written.
|
|
529
|
+
*/
|
|
530
|
+
function enclosingHandlerName(node) {
|
|
531
|
+
const fn = enclosingFunction(node);
|
|
532
|
+
if (fn === null)
|
|
533
|
+
return undefined;
|
|
534
|
+
const own = fn.childForFieldName('name');
|
|
535
|
+
if (own !== null)
|
|
536
|
+
return own.text;
|
|
537
|
+
const parent = fn.parent;
|
|
538
|
+
if (parent === null)
|
|
539
|
+
return undefined;
|
|
540
|
+
if (parent.type === 'variable_declarator' || parent.type === 'pair') {
|
|
541
|
+
const bound = parent.childForFieldName('name') ?? parent.childForFieldName('key');
|
|
542
|
+
return bound?.text;
|
|
543
|
+
}
|
|
544
|
+
if (parent.type === 'assignment_expression') {
|
|
545
|
+
const left = parent.childForFieldName('left');
|
|
546
|
+
if (left === null)
|
|
547
|
+
return undefined;
|
|
548
|
+
return left.type === 'member_expression'
|
|
549
|
+
? (left.childForFieldName('property')?.text ?? undefined)
|
|
550
|
+
: left.text;
|
|
551
|
+
}
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* The function this node sits in, or `null` at module scope.
|
|
556
|
+
*
|
|
557
|
+
* ONE traversal, three readers: the handler name above, the scope half of a
|
|
558
|
+
* match-binding key, and the chain an assignment can rebind. They have to agree
|
|
559
|
+
* on where a function begins or "the same name in the same function" stops
|
|
560
|
+
* meaning one thing, so they share the walk rather than each re-deriving it.
|
|
561
|
+
*/
|
|
562
|
+
function enclosingFunction(node) {
|
|
563
|
+
let current = node.parent;
|
|
564
|
+
while (current !== null) {
|
|
565
|
+
if (FUNCTION_NODE_TYPES.has(current.type))
|
|
566
|
+
return current;
|
|
567
|
+
current = current.parent;
|
|
568
|
+
}
|
|
569
|
+
return null;
|
|
570
|
+
}
|
|
571
|
+
/** Names bound outside every function share this scope. */
|
|
572
|
+
const MODULE_SCOPE_ID = -1;
|
|
573
|
+
/** The scope half of a binding key: the id of the function this node lives in. */
|
|
574
|
+
function enclosingScopeId(node) {
|
|
575
|
+
return enclosingFunction(node)?.id ?? MODULE_SCOPE_ID;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Every scope an assignment written here could be rebinding — its own function,
|
|
579
|
+
* then outward. `m = x` inside a callback rebinds the `m` of whichever enclosing
|
|
580
|
+
* function declared it, and this module does not resolve which, so an assignment
|
|
581
|
+
* is taken to reach all of them.
|
|
582
|
+
*/
|
|
583
|
+
function enclosingScopeIds(node) {
|
|
584
|
+
const ids = [];
|
|
585
|
+
for (let fn = enclosingFunction(node); fn !== null; fn = enclosingFunction(fn))
|
|
586
|
+
ids.push(fn.id);
|
|
587
|
+
ids.push(MODULE_SCOPE_ID);
|
|
588
|
+
return ids;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* A binding key: the function a name is bound in, plus the name.
|
|
592
|
+
*
|
|
593
|
+
* The scope id is a number and NUL cannot appear in an identifier, so the two
|
|
594
|
+
* halves cannot run together into a collision. Written as the `\u0000` ESCAPE,
|
|
595
|
+
* never a raw NUL byte: a literal NUL makes the source a binary file to git,
|
|
596
|
+
* grep and every other line-oriented tool.
|
|
597
|
+
*/
|
|
598
|
+
function bindingKey(scopeId, name) {
|
|
599
|
+
return `${scopeId}\u0000${name}`;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Every name a binding pattern introduces — `m`, `{ m }`, `{ a: m }`, `[m]`,
|
|
603
|
+
* `...m`, `m = fallback`.
|
|
604
|
+
*
|
|
605
|
+
* Destructuring is here because it SHADOWS: `{ const { m } = req.body }` in a
|
|
606
|
+
* block below a real `const m = pathname.match(…)` binds a different `m` in the
|
|
607
|
+
* same function scope, and a shadow this module cannot see is a shadow it would
|
|
608
|
+
* mint a route from. Over-collecting a name only ever refuses one, so the
|
|
609
|
+
* recursion is deliberately blunt about the shapes it does not name.
|
|
610
|
+
*/
|
|
611
|
+
function patternNames(pattern, out = []) {
|
|
612
|
+
if (pattern.type === 'identifier' || pattern.type === 'shorthand_property_identifier_pattern') {
|
|
613
|
+
out.push(pattern.text);
|
|
614
|
+
return out;
|
|
615
|
+
}
|
|
616
|
+
if (pattern.type === 'pair_pattern' || pattern.type === 'assignment_pattern') {
|
|
617
|
+
const bound = pattern.childForFieldName('value') ?? pattern.childForFieldName('left');
|
|
618
|
+
if (bound !== null)
|
|
619
|
+
patternNames(bound, out);
|
|
620
|
+
return out;
|
|
621
|
+
}
|
|
622
|
+
for (const child of pattern.namedChildren)
|
|
623
|
+
patternNames(child, out);
|
|
624
|
+
return out;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* A single URL segment, capturing or not: `[^/]+`, `[^\/]*`, `([^/]+)`.
|
|
628
|
+
*
|
|
629
|
+
* The CAPTURING form is the one real dispatchers write, and it was the one form
|
|
630
|
+
* this converter refused. `(` fell through to the metacharacter bail below, so
|
|
631
|
+
* `^\/api\/research-runs\/([^/]+)$` translated to nothing — while the
|
|
632
|
+
* non-capturing twin translated fine, which is why every test for this rule
|
|
633
|
+
* passed. The tests were written against the implementation instead of against
|
|
634
|
+
* the corpus, and the reporting repo does not contain a single non-capturing
|
|
635
|
+
* path wildcard: a dispatcher captures the segment because it needs the id.
|
|
636
|
+
*
|
|
637
|
+
* The alternatives are balanced on purpose — `([^/]+` unclosed is not a segment,
|
|
638
|
+
* and matching it would leave a stray `)` to be read as a literal.
|
|
639
|
+
*/
|
|
640
|
+
const SEGMENT_WILDCARD = /^(?:\(\[\^\\?\/\][+*]\)|\[\^\\?\/\][+*])/;
|
|
641
|
+
/**
|
|
642
|
+
* Convert an anchored regex used as a path test into a route path, or `null` if
|
|
643
|
+
* any part of it is not cleanly representable.
|
|
644
|
+
*
|
|
645
|
+
* `^\/api\/research-runs\/([^/]+)$` → `/api/research-runs/{param1}`
|
|
646
|
+
*
|
|
647
|
+
* Only single-segment wildcards are recognised — see {@link SEGMENT_WILDCARD}.
|
|
648
|
+
* Anything else — an optional group, an alternation, a bare `.*` — bails,
|
|
649
|
+
* because a route path is a claim about what the server serves and a
|
|
650
|
+
* mistranslated pattern is a wrong one. A capture group around anything OTHER
|
|
651
|
+
* than a segment wildcard still bails: `(.+)` spans slashes, so it is not one
|
|
652
|
+
* segment and cannot be one `{param}`.
|
|
653
|
+
*/
|
|
654
|
+
export function regexToRoutePath(source) {
|
|
655
|
+
if (!source.startsWith('^') || !source.endsWith('$'))
|
|
656
|
+
return null;
|
|
657
|
+
const body = source.slice(1, -1);
|
|
658
|
+
if (body.length === 0)
|
|
659
|
+
return null;
|
|
660
|
+
let out = '';
|
|
661
|
+
let i = 0;
|
|
662
|
+
let paramIndex = 0;
|
|
663
|
+
while (i < body.length) {
|
|
664
|
+
const rest = body.slice(i);
|
|
665
|
+
const wildcard = SEGMENT_WILDCARD.exec(rest);
|
|
666
|
+
if (wildcard !== null) {
|
|
667
|
+
paramIndex += 1;
|
|
668
|
+
out += `{param${paramIndex}}`;
|
|
669
|
+
i += wildcard[0].length;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const char = body[i] ?? '';
|
|
673
|
+
if (char === '\\') {
|
|
674
|
+
const escaped = body[i + 1];
|
|
675
|
+
if (escaped === undefined)
|
|
676
|
+
return null;
|
|
677
|
+
// Only escapes of literal path punctuation are meaningful here; an escape
|
|
678
|
+
// class (`\d`, `\w`, `\s`) is a pattern, not a literal.
|
|
679
|
+
if (/[A-Za-z0-9]/.test(escaped))
|
|
680
|
+
return null;
|
|
681
|
+
out += escaped;
|
|
682
|
+
i += 2;
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if ('[](){}|+*?^$.'.includes(char))
|
|
686
|
+
return null;
|
|
687
|
+
out += char;
|
|
688
|
+
i += 1;
|
|
689
|
+
}
|
|
690
|
+
return out.startsWith('/') ? out : null;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Extract routes declared by path-comparison dispatch from one JS/TS file.
|
|
694
|
+
*
|
|
695
|
+
* Returns the same {@link ExtractedDecoratorRoute} transport every AST-level
|
|
696
|
+
* route extractor returns — a route is a route once it has a path, a verb and a
|
|
697
|
+
* handler, and reusing the transport means the routes phase, the `(method, url)`
|
|
698
|
+
* dedup and the handler-symbol resolution all apply unchanged. `source`
|
|
699
|
+
* distinguishes the provenance, which is the part that actually differs: a
|
|
700
|
+
* decorator route is DECLARED, a dispatch-guard route is INFERRED from a
|
|
701
|
+
* comparison.
|
|
702
|
+
*/
|
|
703
|
+
export function extractDispatchGuardRoutes(tree, filePath, lineOffset = 0) {
|
|
704
|
+
// Every JS/TS file in every repo reaches this hook, so the walk is gated on a
|
|
705
|
+
// substring test first — see PATH_TOKEN_HINT for why skipping is sound.
|
|
706
|
+
if (!PATH_TOKEN_HINT.test(tree.rootNode.text))
|
|
707
|
+
return [];
|
|
708
|
+
const found = [];
|
|
709
|
+
const constants = buildConstantMap(tree.rootNode);
|
|
710
|
+
const regexes = buildRegexConstantMap(tree.rootNode);
|
|
711
|
+
const matches = { bindings: new Map(), declarations: new Map() };
|
|
712
|
+
// Declarations and assignments are noted on the SAME walk that records the
|
|
713
|
+
// bindings, and every emission happens after it, so a shadow or a rebinding
|
|
714
|
+
// written below the match still refuses the name it would have poisoned.
|
|
715
|
+
const visit = (node) => {
|
|
716
|
+
if (node.type === 'binary_expression')
|
|
717
|
+
collectFromComparison(node, found, constants);
|
|
718
|
+
else if (node.type === 'call_expression')
|
|
719
|
+
collectFromRegexDispatch(node, found, regexes, matches);
|
|
720
|
+
else if (node.type === 'switch_statement')
|
|
721
|
+
collectFromSwitch(node, found, constants);
|
|
722
|
+
else if (node.type === 'variable_declarator')
|
|
723
|
+
noteDeclaration(node, matches);
|
|
724
|
+
else if (node.type === 'assignment_expression' ||
|
|
725
|
+
node.type === 'augmented_assignment_expression')
|
|
726
|
+
noteReassignment(node, matches);
|
|
727
|
+
for (const child of node.namedChildren)
|
|
728
|
+
visit(child);
|
|
729
|
+
};
|
|
730
|
+
visit(tree.rootNode);
|
|
731
|
+
collectFromMatchBindings(tree.rootNode, matches.bindings, found);
|
|
732
|
+
return dedupeWithinFile(found).map((route) => ({
|
|
733
|
+
filePath,
|
|
734
|
+
routePath: route.url,
|
|
735
|
+
httpMethod: route.verb ?? '',
|
|
736
|
+
decoratorName: DISPATCH_GUARD_SOURCE,
|
|
737
|
+
source: DISPATCH_GUARD_SOURCE,
|
|
738
|
+
lineNumber: route.line + lineOffset,
|
|
739
|
+
...(route.handlerName ? { handlerName: route.handlerName } : {}),
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
function collectFromComparison(node, out, constants) {
|
|
743
|
+
// Reached only when the comparison is FALSE — claiming the path would be
|
|
744
|
+
// exactly backwards. See `isNegatedContext`.
|
|
745
|
+
if (isNegatedContext(node))
|
|
746
|
+
return;
|
|
747
|
+
const operator = node.childForFieldName('operator')?.text ?? '';
|
|
748
|
+
if (!EQUALITY_OPERATORS.has(operator))
|
|
749
|
+
return;
|
|
750
|
+
const left = node.childForFieldName('left');
|
|
751
|
+
const right = node.childForFieldName('right');
|
|
752
|
+
if (left === null || right === null)
|
|
753
|
+
return;
|
|
754
|
+
for (const [expr, literal] of [
|
|
755
|
+
[left, right],
|
|
756
|
+
[right, left],
|
|
757
|
+
]) {
|
|
758
|
+
if (!isPathExpression(expr))
|
|
759
|
+
continue;
|
|
760
|
+
const value = literalValue(literal, constants);
|
|
761
|
+
if (value === null || !isPathLiteral(value))
|
|
762
|
+
continue;
|
|
763
|
+
const verbs = governingVerbs(node);
|
|
764
|
+
// A bare `/` is only a route when a verb says so — see the module header.
|
|
765
|
+
if (value === '/' && verbs.length === 0)
|
|
766
|
+
continue;
|
|
767
|
+
pushPerVerb(out, verbs, {
|
|
768
|
+
url: value,
|
|
769
|
+
handlerName: enclosingHandlerName(node),
|
|
770
|
+
line: node.startPosition.row + 1,
|
|
771
|
+
});
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Emit one route per governing verb, or a single verb-less route when the guard
|
|
777
|
+
* guarantees none. A multi-method guard is genuinely several routes: they share
|
|
778
|
+
* a path and a handler but not a method, and `(method, url)` is the key every
|
|
779
|
+
* downstream consumer dedups and looks up on.
|
|
780
|
+
*/
|
|
781
|
+
function pushPerVerb(out, verbs, route) {
|
|
782
|
+
if (verbs.length === 0) {
|
|
783
|
+
out.push({ ...route, verb: null });
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
for (const verb of verbs)
|
|
787
|
+
out.push({ ...route, verb });
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* `switch (pathname) { case '/api/health': … }` — the other way to write the
|
|
791
|
+
* same dispatch, and the reason this module is not a rule about `if`. The
|
|
792
|
+
* discriminant carries the path signal for every arm at once, so each
|
|
793
|
+
* string-literal case is a route with no further evidence needed.
|
|
794
|
+
*
|
|
795
|
+
* Not reported by anyone; included because it is the same shape wearing
|
|
796
|
+
* different syntax, and waiting for a bug report per shape is how a graph stays
|
|
797
|
+
* permanently one idiom behind the code it indexes.
|
|
798
|
+
*/
|
|
799
|
+
function collectFromSwitch(node, out, constants) {
|
|
800
|
+
// The grammar wraps a switch discriminant in `parenthesized_expression`,
|
|
801
|
+
// unlike a comparison operand.
|
|
802
|
+
const discriminant = unparenthesize(node.childForFieldName('value'));
|
|
803
|
+
if (discriminant === null || !isPathExpression(discriminant))
|
|
804
|
+
return;
|
|
805
|
+
const body = node.childForFieldName('body');
|
|
806
|
+
if (body === null)
|
|
807
|
+
return;
|
|
808
|
+
// The verbs governing the whole switch, if any (`if (req.method === 'GET')
|
|
809
|
+
// switch (pathname) { … }`). Read once — every arm shares them.
|
|
810
|
+
const verbs = governingVerbs(node);
|
|
811
|
+
for (const arm of body.namedChildren) {
|
|
812
|
+
if (arm.type !== 'switch_case')
|
|
813
|
+
continue;
|
|
814
|
+
const caseValue = arm.childForFieldName('value');
|
|
815
|
+
if (caseValue === null)
|
|
816
|
+
continue;
|
|
817
|
+
const value = literalValue(caseValue, constants);
|
|
818
|
+
if (value === null || !isPathLiteral(value))
|
|
819
|
+
continue;
|
|
820
|
+
if (value === '/' && verbs.length === 0)
|
|
821
|
+
continue;
|
|
822
|
+
pushPerVerb(out, verbs, {
|
|
823
|
+
url: value,
|
|
824
|
+
handlerName: enclosingHandlerName(arm),
|
|
825
|
+
line: arm.startPosition.row + 1,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Count a declarator against its key, and refuse the key once a second one
|
|
831
|
+
* binds it.
|
|
832
|
+
*
|
|
833
|
+
* Refusing rather than ordering costs the real route in
|
|
834
|
+
* `const m = pathname.match(RE); { const m = other() }` — the honest GET is
|
|
835
|
+
* dropped alongside the shadow that would have fabricated a DELETE. That is the
|
|
836
|
+
* cheaper failure by this module's own bar, and it is the same trade
|
|
837
|
+
* {@link buildConstantMap} makes for a name declared twice.
|
|
838
|
+
*/
|
|
839
|
+
function noteDeclaration(node, matches) {
|
|
840
|
+
const name = node.childForFieldName('name');
|
|
841
|
+
if (name === null)
|
|
842
|
+
return;
|
|
843
|
+
const scopeId = enclosingScopeId(node);
|
|
844
|
+
for (const bound of patternNames(name)) {
|
|
845
|
+
const key = bindingKey(scopeId, bound);
|
|
846
|
+
const count = (matches.declarations.get(key) ?? 0) + 1;
|
|
847
|
+
matches.declarations.set(key, count);
|
|
848
|
+
if (count > 1)
|
|
849
|
+
matches.bindings.set(key, null);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Refuse a name that is ASSIGNED anywhere it could reach.
|
|
854
|
+
*
|
|
855
|
+
* `let m = pathname.match(RE); m = fallback()` leaves `m` holding something this
|
|
856
|
+
* walk never saw, and the declaration alone is no longer evidence of what the
|
|
857
|
+
* later `if (m)` tests. Poisoning pre-emptively — before the binding is even
|
|
858
|
+
* recorded — is what makes the order of the two statements not matter.
|
|
859
|
+
*
|
|
860
|
+
* Only a REBINDING counts. `m.index = 0` and `m[1] = x` assign THROUGH the name
|
|
861
|
+
* and leave it bound to the same match, so refusing on them would drop routes
|
|
862
|
+
* for writes that change nothing this module reads.
|
|
863
|
+
*/
|
|
864
|
+
const REBINDABLE_TARGETS = new Set([
|
|
865
|
+
'identifier',
|
|
866
|
+
'array_pattern',
|
|
867
|
+
'object_pattern',
|
|
868
|
+
]);
|
|
869
|
+
function noteReassignment(node, matches) {
|
|
870
|
+
const left = node.childForFieldName('left');
|
|
871
|
+
if (left === null || !REBINDABLE_TARGETS.has(left.type))
|
|
872
|
+
return;
|
|
873
|
+
const names = patternNames(left);
|
|
874
|
+
if (names.length === 0)
|
|
875
|
+
return;
|
|
876
|
+
for (const scopeId of enclosingScopeIds(node)) {
|
|
877
|
+
for (const name of names)
|
|
878
|
+
matches.bindings.set(bindingKey(scopeId, name), null);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Same-file `const NAME = /re/` bindings, so a regex named once and used by name
|
|
883
|
+
* still yields its route.
|
|
884
|
+
*
|
|
885
|
+
* Verbatim from the reporting repo: `positionReplayRoutes.js` declares
|
|
886
|
+
* `const POSITION_REPLAY_RE = /^\/api\/live\/positions\/([^/]+)\/replay$/` at
|
|
887
|
+
* module scope and then uses it BOTH ways — `POSITION_REPLAY_RE.test(pathname)`
|
|
888
|
+
* and `pathname.match(POSITION_REPLAY_RE)`. Keying only on inline literals
|
|
889
|
+
* loses the whole file.
|
|
890
|
+
*
|
|
891
|
+
* Ambiguity is refused the same way {@link buildConstantMap} refuses it: a name
|
|
892
|
+
* bound twice to different patterns is dropped rather than resolved to the
|
|
893
|
+
* first, because a half-right regex is a wrong route.
|
|
894
|
+
*
|
|
895
|
+
* That refusal only ever SAW regex literals, which left the two rebindings that
|
|
896
|
+
* matter walking straight past it. `let RE = /^\/api\/re\/([^/]+)$/` followed by
|
|
897
|
+
* `RE = buildDynamic(req)` still minted the literal's route, and so did a
|
|
898
|
+
* `const RE = new RegExp(userPrefix + '/x')` twin in another function — the map
|
|
899
|
+
* is flat, so a same-named binding anywhere in the file is exactly the ambiguity
|
|
900
|
+
* the doc claims to refuse. A name bound to ANYTHING that is not a regex
|
|
901
|
+
* literal, or assigned at all, is now dropped.
|
|
902
|
+
*/
|
|
903
|
+
function buildRegexConstantMap(root) {
|
|
904
|
+
const patterns = new Map();
|
|
905
|
+
const ambiguous = new Set();
|
|
906
|
+
const visit = (node) => {
|
|
907
|
+
if (node.type === 'variable_declarator') {
|
|
908
|
+
const name = node.childForFieldName('name');
|
|
909
|
+
const value = unparenthesize(node.childForFieldName('value'));
|
|
910
|
+
if (name !== null && name.type === 'identifier') {
|
|
911
|
+
const pattern = value !== null && value.type === 'regex'
|
|
912
|
+
? (value.childForFieldName('pattern')?.text ?? null)
|
|
913
|
+
: null;
|
|
914
|
+
if (pattern === null)
|
|
915
|
+
ambiguous.add(name.text);
|
|
916
|
+
else {
|
|
917
|
+
const existing = patterns.get(name.text);
|
|
918
|
+
if (existing !== undefined && existing !== pattern)
|
|
919
|
+
ambiguous.add(name.text);
|
|
920
|
+
else
|
|
921
|
+
patterns.set(name.text, pattern);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
if (node.type === 'assignment_expression' || node.type === 'augmented_assignment_expression') {
|
|
926
|
+
const left = node.childForFieldName('left');
|
|
927
|
+
if (left !== null && left.type === 'identifier')
|
|
928
|
+
ambiguous.add(left.text);
|
|
929
|
+
}
|
|
930
|
+
for (const child of node.namedChildren)
|
|
931
|
+
visit(child);
|
|
932
|
+
};
|
|
933
|
+
visit(root);
|
|
934
|
+
for (const name of ambiguous)
|
|
935
|
+
patterns.delete(name);
|
|
936
|
+
return patterns;
|
|
937
|
+
}
|
|
938
|
+
/** The regex pattern this expression denotes — inline literal or named const. */
|
|
939
|
+
function regexPatternOf(node, regexes) {
|
|
940
|
+
const expr = unparenthesize(node);
|
|
941
|
+
if (expr === null)
|
|
942
|
+
return null;
|
|
943
|
+
if (expr.type === 'regex')
|
|
944
|
+
return expr.childForFieldName('pattern')?.text ?? null;
|
|
945
|
+
if (expr.type === 'identifier')
|
|
946
|
+
return regexes.get(expr.text) ?? null;
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* A route declared by matching the path against an anchored regex.
|
|
951
|
+
*
|
|
952
|
+
* Two spellings of the same test, with the operands swapped:
|
|
953
|
+
*
|
|
954
|
+
* if (RE.test(pathname)) — receiver is the regex
|
|
955
|
+
* const m = pathname.match(RE) — receiver is the path
|
|
956
|
+
*
|
|
957
|
+
* Only `.test` was read, which is why 28 of the reporting repo's 75 routes still
|
|
958
|
+
* named the shared route table as their handler rather than the module that
|
|
959
|
+
* actually serves them: their modules dispatch with `.match`.
|
|
960
|
+
*
|
|
961
|
+
* `.match` differs in one way that matters. Its result is USED — it carries the
|
|
962
|
+
* captured segments — so it is almost always BOUND, and the verb then lives in a
|
|
963
|
+
* later `if` rather than around the call:
|
|
964
|
+
*
|
|
965
|
+
* const runMatch = pathname.match(/^\/api\/research-runs\/([^/]+)$/)
|
|
966
|
+
* if (req.method === 'GET' && runMatch) { … }
|
|
967
|
+
*
|
|
968
|
+
* Reading the verb off the CALL would report every one of those verb-less. So a
|
|
969
|
+
* bound match is recorded rather than emitted, and {@link collectFromMatchBindings}
|
|
970
|
+
* emits it where the binding is actually tested. An unbound match is a plain
|
|
971
|
+
* predicate and is emitted here, exactly like `.test`.
|
|
972
|
+
*/
|
|
973
|
+
function collectFromRegexDispatch(node, out, regexes, matches) {
|
|
974
|
+
const callee = node.childForFieldName('function');
|
|
975
|
+
if (callee === null || callee.type !== 'member_expression')
|
|
976
|
+
return;
|
|
977
|
+
const method = callee.childForFieldName('property')?.text;
|
|
978
|
+
if (method !== 'test' && method !== 'match')
|
|
979
|
+
return;
|
|
980
|
+
const receiver = callee.childForFieldName('object');
|
|
981
|
+
const argument = node.childForFieldName('arguments')?.namedChildren[0] ?? null;
|
|
982
|
+
if (receiver === null || argument === null)
|
|
983
|
+
return;
|
|
984
|
+
// `RE.test(pathname)` vs `pathname.match(RE)` — the regex and the path swap
|
|
985
|
+
// sides with the method, so each spelling is checked in its own orientation
|
|
986
|
+
// rather than accepting any pairing.
|
|
987
|
+
const pattern = method === 'test'
|
|
988
|
+
? isPathExpression(argument)
|
|
989
|
+
? regexPatternOf(receiver, regexes)
|
|
990
|
+
: null
|
|
991
|
+
: isPathExpression(receiver)
|
|
992
|
+
? regexPatternOf(argument, regexes)
|
|
993
|
+
: null;
|
|
994
|
+
if (pattern === null)
|
|
995
|
+
return;
|
|
996
|
+
const url = regexToRoutePath(pattern);
|
|
997
|
+
if (url === null)
|
|
998
|
+
return;
|
|
999
|
+
const boundName = boundDeclaratorName(node);
|
|
1000
|
+
if (boundName !== null) {
|
|
1001
|
+
// Keyed by the function this name is bound in — see MatchBindingState for
|
|
1002
|
+
// the routes the bare name invented. Already-refused keys stay refused, and
|
|
1003
|
+
// a key bound twice to DIFFERENT routes is poisoned rather than resolved to
|
|
1004
|
+
// the first.
|
|
1005
|
+
const key = bindingKey(enclosingScopeId(node), boundName);
|
|
1006
|
+
const existing = matches.bindings.get(key);
|
|
1007
|
+
if (existing !== undefined && (existing === null || existing.url !== url)) {
|
|
1008
|
+
matches.bindings.set(key, null);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
matches.bindings.set(key, {
|
|
1012
|
+
name: boundName,
|
|
1013
|
+
url,
|
|
1014
|
+
line: node.startPosition.row + 1,
|
|
1015
|
+
handlerName: enclosingHandlerName(node),
|
|
1016
|
+
});
|
|
1017
|
+
return;
|
|
1018
|
+
}
|
|
1019
|
+
// Unbound: the call IS the predicate, so its own context carries the verb.
|
|
1020
|
+
if (isNegatedContext(node))
|
|
1021
|
+
return;
|
|
1022
|
+
pushPerVerb(out, governingVerbs(node), {
|
|
1023
|
+
url,
|
|
1024
|
+
handlerName: enclosingHandlerName(node),
|
|
1025
|
+
line: node.startPosition.row + 1,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
/** The name this call's result is bound to by `const NAME = <call>`, if any. */
|
|
1029
|
+
function boundDeclaratorName(call) {
|
|
1030
|
+
const parent = call.parent;
|
|
1031
|
+
if (parent === null || parent.type !== 'variable_declarator')
|
|
1032
|
+
return null;
|
|
1033
|
+
if (parent.childForFieldName('value')?.id !== call.id)
|
|
1034
|
+
return null;
|
|
1035
|
+
const name = parent.childForFieldName('name');
|
|
1036
|
+
return name !== null && name.type === 'identifier' ? name.text : null;
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Emit a route wherever a recorded match binding is TESTED.
|
|
1040
|
+
*
|
|
1041
|
+
* The binding's declaration proves a path; the test site proves the method, and
|
|
1042
|
+
* one binding can be tested more than once. A reference counts only in a
|
|
1043
|
+
* truthiness position — see {@link isTruthinessPosition} — which is what
|
|
1044
|
+
* separates `if (m && …)` from `m[1]`, a read of the captured segment that says
|
|
1045
|
+
* nothing about dispatch and would otherwise mint a duplicate route per capture
|
|
1046
|
+
* group used.
|
|
1047
|
+
*
|
|
1048
|
+
* A binding that is never tested still emits ONE verb-less route: the code did
|
|
1049
|
+
* compute an anchored match against the request path, which is the same evidence
|
|
1050
|
+
* an unbound `.test` carries, and dropping it would trade a known path for
|
|
1051
|
+
* nothing.
|
|
1052
|
+
*
|
|
1053
|
+
* The DECLARATION's own name identifier needs no special case: its parent is a
|
|
1054
|
+
* `variable_declarator`, which is not a truthiness position, so the same
|
|
1055
|
+
* predicate that rejects `m[1]` rejects it. An explicit skip was written here
|
|
1056
|
+
* first and removed once it proved unreachable — it read as though the
|
|
1057
|
+
* declaration were a hazard, which sends the next reader looking for one.
|
|
1058
|
+
*
|
|
1059
|
+
* A use counts only against a binding in ITS OWN function. `tested` is keyed the
|
|
1060
|
+
* same way, and that half matters as much as the emission: keyed by bare name, a
|
|
1061
|
+
* same-named local in another handler marked the name tested and SUPPRESSED the
|
|
1062
|
+
* real binding's own verb-less route from the tail loop below — so the honest
|
|
1063
|
+
* route was not merely joined by a fabricated one, it was replaced by it, down
|
|
1064
|
+
* to reporting the wrong handler and the wrong line.
|
|
1065
|
+
*/
|
|
1066
|
+
function collectFromMatchBindings(root, matchBindings, out) {
|
|
1067
|
+
// Resolving a scope costs a walk to the function boundary, and this visits
|
|
1068
|
+
// every identifier in the file. Names that no live binding uses are rejected
|
|
1069
|
+
// on a set lookup first, so files without a bound match pay nothing.
|
|
1070
|
+
const liveNames = new Set();
|
|
1071
|
+
for (const binding of matchBindings.values())
|
|
1072
|
+
if (binding !== null)
|
|
1073
|
+
liveNames.add(binding.name);
|
|
1074
|
+
if (liveNames.size === 0)
|
|
1075
|
+
return;
|
|
1076
|
+
const tested = new Set();
|
|
1077
|
+
const visit = (node) => {
|
|
1078
|
+
if (node.type === 'identifier' && liveNames.has(node.text)) {
|
|
1079
|
+
const key = bindingKey(enclosingScopeId(node), node.text);
|
|
1080
|
+
const binding = matchBindings.get(key);
|
|
1081
|
+
if (binding !== undefined &&
|
|
1082
|
+
binding !== null &&
|
|
1083
|
+
isTruthinessPosition(node) &&
|
|
1084
|
+
!isNegatedContext(node)) {
|
|
1085
|
+
tested.add(key);
|
|
1086
|
+
pushPerVerb(out, governingVerbs(node), {
|
|
1087
|
+
url: binding.url,
|
|
1088
|
+
handlerName: enclosingHandlerName(node) ?? binding.handlerName,
|
|
1089
|
+
line: node.startPosition.row + 1,
|
|
1090
|
+
});
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
for (const child of node.namedChildren)
|
|
1094
|
+
visit(child);
|
|
1095
|
+
};
|
|
1096
|
+
visit(root);
|
|
1097
|
+
for (const [key, binding] of matchBindings) {
|
|
1098
|
+
if (binding === null || tested.has(key))
|
|
1099
|
+
continue;
|
|
1100
|
+
out.push({
|
|
1101
|
+
url: binding.url,
|
|
1102
|
+
verb: null,
|
|
1103
|
+
handlerName: binding.handlerName,
|
|
1104
|
+
line: binding.line,
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Is this reference read for its TRUTH — an operand of `&&`/`||`, or the whole
|
|
1110
|
+
* condition of an `if`?
|
|
1111
|
+
*
|
|
1112
|
+
* Deliberately narrow. `runMatch[1]` (a `subscript_expression` parent) reads a
|
|
1113
|
+
* captured segment, and `validate(runMatch)` passes it along; neither asserts
|
|
1114
|
+
* that the request took this route, and counting them would emit one duplicate
|
|
1115
|
+
* route per use of the captured id. Parentheses are transparent, so
|
|
1116
|
+
* `if ((runMatch))` and `if (verb && (runMatch))` both count.
|
|
1117
|
+
*/
|
|
1118
|
+
function isTruthinessPosition(node) {
|
|
1119
|
+
let current = node;
|
|
1120
|
+
let parent = current.parent;
|
|
1121
|
+
while (parent !== null && parent.type === 'parenthesized_expression') {
|
|
1122
|
+
current = parent;
|
|
1123
|
+
parent = current.parent;
|
|
1124
|
+
}
|
|
1125
|
+
if (parent === null)
|
|
1126
|
+
return false;
|
|
1127
|
+
if (parent.type === 'binary_expression') {
|
|
1128
|
+
const operator = parent.childForFieldName('operator')?.text;
|
|
1129
|
+
return operator === '&&' || operator === '||';
|
|
1130
|
+
}
|
|
1131
|
+
if (parent.type === 'if_statement') {
|
|
1132
|
+
return parent.childForFieldName('condition')?.id === current.id;
|
|
1133
|
+
}
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
/**
|
|
1137
|
+
* Collapse duplicate `(url, verb)` findings within one file, keeping the first —
|
|
1138
|
+
* matching the routes phase's own first-writer-wins. The same comparison can
|
|
1139
|
+
* legitimately appear more than once (an early-return guard and the branch that
|
|
1140
|
+
* serves it), and each occurrence is the same route.
|
|
1141
|
+
*
|
|
1142
|
+
* The verb-less/verb-qualified reconciliation is deliberately NOT here — see
|
|
1143
|
+
* {@link reconcileDispatchGuardRoutes}, which needs the whole repo to do it.
|
|
1144
|
+
*/
|
|
1145
|
+
function dedupeWithinFile(routes) {
|
|
1146
|
+
const seen = new Set();
|
|
1147
|
+
const out = [];
|
|
1148
|
+
for (const route of routes) {
|
|
1149
|
+
const key = `${route.verb ?? ''} ${route.url}`;
|
|
1150
|
+
if (seen.has(key))
|
|
1151
|
+
continue;
|
|
1152
|
+
seen.add(key);
|
|
1153
|
+
out.push(route);
|
|
1154
|
+
}
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Drop a dispatch-guard route whose URL is claimed WITH a verb somewhere in the
|
|
1159
|
+
* repository.
|
|
1160
|
+
*
|
|
1161
|
+
* The idiom that makes this necessary is the split route table: one module lists
|
|
1162
|
+
* every path it recognises (`isKnownApiPath`, or a `match(method, pathname)`
|
|
1163
|
+
* that ORs them all) so the dispatcher can 404 early, and separate modules
|
|
1164
|
+
* handle each path by verb. Both are path comparisons and both are real, but
|
|
1165
|
+
* only the second is a route in the sense `route_map` reports — the first is a
|
|
1166
|
+
* membership test.
|
|
1167
|
+
*
|
|
1168
|
+
* Left alone this doubles the map: measured on the reporting repo, 94 routes of
|
|
1169
|
+
* which 34 were the table's verb-less shadow of a route already listed with its
|
|
1170
|
+
* verb and its true handler. Reconciling per-FILE cannot see it, because the
|
|
1171
|
+
* table and the handlers are different files; only the whole registry can.
|
|
1172
|
+
*
|
|
1173
|
+
* Applies to dispatch-guard routes only. A framework route with no verb is
|
|
1174
|
+
* method-agnostic BY DECLARATION (a Django function view, a Laravel resource),
|
|
1175
|
+
* which is a fact rather than a weaker observation, and must not be dropped.
|
|
1176
|
+
*/
|
|
1177
|
+
export function reconcileDispatchGuardRoutes(routes) {
|
|
1178
|
+
const verbedUrls = new Set(routes
|
|
1179
|
+
.filter((r) => r.source === DISPATCH_GUARD_SOURCE && r.httpMethod !== '')
|
|
1180
|
+
.map((r) => r.routePath));
|
|
1181
|
+
if (verbedUrls.size === 0)
|
|
1182
|
+
return [...routes];
|
|
1183
|
+
return routes.filter((r) => !(r.source === DISPATCH_GUARD_SOURCE && r.httpMethod === '' && verbedUrls.has(r.routePath)));
|
|
1184
|
+
}
|