code-auditor-mcp 3.1.1 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +724 -1
- package/README.md +85 -11
- package/dist/__test_tmp__/spec18_test.d.ts +2 -0
- package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
- package/dist/__test_tmp__/spec18_test.js +5 -0
- package/dist/__test_tmp__/spec18_test.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
- package/dist/analyzers/analyzerUtils.js +2 -2
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -2
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -3
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +2 -2
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +8 -4
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
- package/dist/analyzers/orm/adapterRegistry.js +73 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.js +166 -0
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
- package/dist/analyzers/orm/index.d.ts +14 -0
- package/dist/analyzers/orm/index.d.ts.map +1 -0
- package/dist/analyzers/orm/index.js +24 -0
- package/dist/analyzers/orm/index.js.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.js +170 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
- package/dist/analyzers/orm/types.d.ts +63 -0
- package/dist/analyzers/orm/types.d.ts.map +1 -0
- package/dist/analyzers/orm/types.js +10 -0
- package/dist/analyzers/orm/types.js.map +1 -0
- package/dist/analyzers/provenance.d.ts +133 -0
- package/dist/analyzers/provenance.d.ts.map +1 -0
- package/dist/analyzers/provenance.js +1041 -0
- package/dist/analyzers/provenance.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +117 -1
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +38 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
- package/dist/analyzers/ruleRegistry.js +123 -0
- package/dist/analyzers/ruleRegistry.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -53
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +499 -4
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +98 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +184 -0
- package/dist/baseline.js.map +1 -0
- package/dist/churn/churnExtractor.d.ts +32 -0
- package/dist/churn/churnExtractor.d.ts.map +1 -0
- package/dist/churn/churnExtractor.js +264 -0
- package/dist/churn/churnExtractor.js.map +1 -0
- package/dist/cli.js +1218 -11
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.js +1 -1
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/codeIndexDB.d.ts +101 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +665 -5
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +104 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +72 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +146 -15
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/pathProfiles.d.ts +38 -0
- package/dist/config/pathProfiles.d.ts.map +1 -0
- package/dist/config/pathProfiles.js +47 -0
- package/dist/config/pathProfiles.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts +63 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -0
- package/dist/conventions/conventionMiner.js +696 -0
- package/dist/conventions/conventionMiner.js.map +1 -0
- package/dist/coverage/coverageService.d.ts +49 -0
- package/dist/coverage/coverageService.d.ts.map +1 -0
- package/dist/coverage/coverageService.js +117 -0
- package/dist/coverage/coverageService.js.map +1 -0
- package/dist/coverage/istanbulParser.d.ts +84 -0
- package/dist/coverage/istanbulParser.d.ts.map +1 -0
- package/dist/coverage/istanbulParser.js +120 -0
- package/dist/coverage/istanbulParser.js.map +1 -0
- package/dist/coverage/lcovParser.d.ts +40 -0
- package/dist/coverage/lcovParser.d.ts.map +1 -0
- package/dist/coverage/lcovParser.js +168 -0
- package/dist/coverage/lcovParser.js.map +1 -0
- package/dist/fingerprint.d.ts +17 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +42 -0
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-css.wasm +0 -0
- package/dist/graph/blastRadius.d.ts +27 -0
- package/dist/graph/blastRadius.d.ts.map +1 -0
- package/dist/graph/blastRadius.js +150 -0
- package/dist/graph/blastRadius.js.map +1 -0
- package/dist/graph/callGraph.d.ts +78 -0
- package/dist/graph/callGraph.d.ts.map +1 -0
- package/dist/graph/callGraph.js +484 -0
- package/dist/graph/callGraph.js.map +1 -0
- package/dist/graph/importGraph.d.ts +91 -0
- package/dist/graph/importGraph.d.ts.map +1 -0
- package/dist/graph/importGraph.js +602 -0
- package/dist/graph/importGraph.js.map +1 -0
- package/dist/graph/outputFormatter.d.ts +55 -0
- package/dist/graph/outputFormatter.d.ts.map +1 -0
- package/dist/graph/outputFormatter.js +283 -0
- package/dist/graph/outputFormatter.js.map +1 -0
- package/dist/hooks/codex.d.ts +36 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +88 -54
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hotspots/hotspotScorer.d.ts +20 -0
- package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
- package/dist/hotspots/hotspotScorer.js +202 -0
- package/dist/hotspots/hotspotScorer.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +42 -1
- package/dist/invariants/ruleEngine.d.ts +4 -1
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +141 -5
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +62 -1
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/invariants/types.d.ts +23 -2
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +61 -4
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +3 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +2 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +128 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +345 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +9 -14
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp.js +0 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +3 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +7 -1
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +10 -2
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +17 -17
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/scripts/runBench.d.ts +94 -0
- package/dist/scripts/runBench.d.ts.map +1 -0
- package/dist/scripts/runBench.js +1559 -0
- package/dist/scripts/runBench.js.map +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +51 -4
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +33 -0
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +404 -0
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/styles/normalizer.d.ts +37 -0
- package/dist/styles/normalizer.d.ts.map +1 -0
- package/dist/styles/normalizer.js +301 -0
- package/dist/styles/normalizer.js.map +1 -0
- package/dist/styles/styleExtractor.d.ts +38 -0
- package/dist/styles/styleExtractor.d.ts.map +1 -0
- package/dist/styles/styleExtractor.js +630 -0
- package/dist/styles/styleExtractor.js.map +1 -0
- package/dist/styles/styleIndexer.d.ts +35 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -0
- package/dist/styles/styleIndexer.js +266 -0
- package/dist/styles/styleIndexer.js.map +1 -0
- package/dist/styles/tailwindConfigLoader.d.ts +38 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
- package/dist/styles/tailwindConfigLoader.js +374 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -0
- package/dist/styles/tailwindExpander.d.ts +38 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -0
- package/dist/styles/tailwindExpander.js +637 -0
- package/dist/styles/tailwindExpander.js.map +1 -0
- package/dist/styles/types.d.ts +92 -0
- package/dist/styles/types.d.ts.map +1 -0
- package/dist/styles/types.js +10 -0
- package/dist/styles/types.js.map +1 -0
- package/dist/symbols.d.ts +21 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +31 -0
- package/dist/symbols.js.map +1 -0
- package/dist/types.d.ts +326 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts +1 -0
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +8 -2
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-audit.sh +5 -1
- package/plugin/skills/code-auditor/SKILL.md +51 -0
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.js +0 -474
- package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
- package/dist/analyzers/dryAnalyzer.d.ts +0 -31
- package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzer.js +0 -734
- package/dist/analyzers/dryAnalyzer.js.map +0 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
- package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzer.js +0 -564
- package/dist/analyzers/schemaAnalyzer.js.map +0 -1
- package/dist/analyzers/solidAnalyzer.d.ts +0 -33
- package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzer.js +0 -847
- package/dist/analyzers/solidAnalyzer.js.map +0 -1
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provenance Resolver — Spec 21
|
|
3
|
+
*
|
|
4
|
+
* Language-neutral DB and validator receiver detection through
|
|
5
|
+
* provenance tracking instead of English name matching.
|
|
6
|
+
*
|
|
7
|
+
* "Where did this variable's value come from?" rather than
|
|
8
|
+
* "Is this variable named 'db'?"
|
|
9
|
+
*
|
|
10
|
+
* ─── Architecture ───
|
|
11
|
+
* 1. extractDBProvenancedImports → seed identifiers from known package imports
|
|
12
|
+
* 2. propagateProvenance → follow assignments, destructuring, params
|
|
13
|
+
* 3. buildProvenanceContext → combine seeds + propagation + fallbacks (R3)
|
|
14
|
+
* 4. isDBProvenanced → answer: is this call-expression DB-bound?
|
|
15
|
+
*
|
|
16
|
+
* ─── Modes (R3) ───
|
|
17
|
+
* hybrid (default) — provenance-primary + conjunctive name-fallback
|
|
18
|
+
* provenance — strict provenance only, never consults name lists
|
|
19
|
+
* names — legacy English-only name matching (opt-in escape hatch)
|
|
20
|
+
*/
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
// Constants — the universal vocabulary (package names are language-invariant)
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
24
|
+
/** Database packages — spec R1.1 */
|
|
25
|
+
export const DB_PACKAGES = new Set([
|
|
26
|
+
'better-sqlite3',
|
|
27
|
+
'drizzle-orm',
|
|
28
|
+
'@prisma/client',
|
|
29
|
+
'pg',
|
|
30
|
+
'mysql2',
|
|
31
|
+
'postgres',
|
|
32
|
+
'kysely',
|
|
33
|
+
'knex',
|
|
34
|
+
'mongodb',
|
|
35
|
+
'mongoose',
|
|
36
|
+
'@libsql/client',
|
|
37
|
+
'@planetscale/database',
|
|
38
|
+
'@neondatabase/serverless',
|
|
39
|
+
'@vercel/postgres',
|
|
40
|
+
'bun:sqlite',
|
|
41
|
+
'node:sqlite',
|
|
42
|
+
]);
|
|
43
|
+
/** Validator packages — spec R4.1 */
|
|
44
|
+
export const VALIDATOR_PACKAGES = new Set([
|
|
45
|
+
'zod',
|
|
46
|
+
'joi',
|
|
47
|
+
'ajv',
|
|
48
|
+
'valibot',
|
|
49
|
+
'yup',
|
|
50
|
+
'superstruct',
|
|
51
|
+
'arktype',
|
|
52
|
+
'@sinclair/typebox',
|
|
53
|
+
'class-validator',
|
|
54
|
+
]);
|
|
55
|
+
/** Known DB type names — spec R1.1 (propagation rule 8) */
|
|
56
|
+
export const DB_TYPES = new Set([
|
|
57
|
+
'D1Database',
|
|
58
|
+
'D1PreparedStatement',
|
|
59
|
+
'D1Result',
|
|
60
|
+
'Database',
|
|
61
|
+
'Pool',
|
|
62
|
+
'PrismaClient',
|
|
63
|
+
'Kysely',
|
|
64
|
+
'Connection',
|
|
65
|
+
'SqliteDatabase',
|
|
66
|
+
'BetterSQLite3Database',
|
|
67
|
+
]);
|
|
68
|
+
/** DB call methods — the fixed API surface (language-invariant) */
|
|
69
|
+
export const DB_CALL_METHODS = new Set([
|
|
70
|
+
'exec',
|
|
71
|
+
'prepare',
|
|
72
|
+
'batch',
|
|
73
|
+
'run',
|
|
74
|
+
'all',
|
|
75
|
+
'first',
|
|
76
|
+
'query',
|
|
77
|
+
'get',
|
|
78
|
+
'each',
|
|
79
|
+
'raw',
|
|
80
|
+
'values',
|
|
81
|
+
]);
|
|
82
|
+
/** ORM method patterns — fixed API surface for ORM recognition (Spec 21 R1) */
|
|
83
|
+
export const ORM_METHODS = new Set([
|
|
84
|
+
'find',
|
|
85
|
+
'findOne',
|
|
86
|
+
'findMany',
|
|
87
|
+
'findFirst',
|
|
88
|
+
'findUnique',
|
|
89
|
+
'select',
|
|
90
|
+
'insert',
|
|
91
|
+
'insertMany',
|
|
92
|
+
'update',
|
|
93
|
+
'updateOne',
|
|
94
|
+
'updateMany',
|
|
95
|
+
'delete',
|
|
96
|
+
'deleteOne',
|
|
97
|
+
'deleteMany',
|
|
98
|
+
'from',
|
|
99
|
+
'where',
|
|
100
|
+
'join',
|
|
101
|
+
'leftJoin',
|
|
102
|
+
'rightJoin',
|
|
103
|
+
'innerJoin',
|
|
104
|
+
'create',
|
|
105
|
+
'createMany',
|
|
106
|
+
'aggregate',
|
|
107
|
+
'count',
|
|
108
|
+
'distinct',
|
|
109
|
+
'execute',
|
|
110
|
+
'query',
|
|
111
|
+
]);
|
|
112
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
113
|
+
// Package matching
|
|
114
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
115
|
+
/** Check if a module specifier matches a DB package (exact or subpath). */
|
|
116
|
+
function matchesDBPackage(specifier) {
|
|
117
|
+
return [...DB_PACKAGES].some((pkg) => specifier === pkg || specifier.startsWith(pkg + '/'));
|
|
118
|
+
}
|
|
119
|
+
/** Check if a module specifier matches a validator package. */
|
|
120
|
+
function matchesValidatorPackage(specifier) {
|
|
121
|
+
return [...VALIDATOR_PACKAGES].some((pkg) => specifier === pkg || specifier.startsWith(pkg + '/'));
|
|
122
|
+
}
|
|
123
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
124
|
+
// Import extraction (R1 seed phase)
|
|
125
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
126
|
+
/**
|
|
127
|
+
* Extract all DB-provenanced identifiers from a file's import statements.
|
|
128
|
+
*
|
|
129
|
+
* An import like `import Database from 'better-sqlite3'` produces
|
|
130
|
+
* `Database` as DB-provenanced with reason "package".
|
|
131
|
+
*/
|
|
132
|
+
export function extractDBProvenancedImports(ast, adapter) {
|
|
133
|
+
const seedMap = new Map();
|
|
134
|
+
const imports = adapter.extractImports(ast);
|
|
135
|
+
for (const imp of imports) {
|
|
136
|
+
const specifier = imp.source;
|
|
137
|
+
if (!matchesDBPackage(specifier))
|
|
138
|
+
continue;
|
|
139
|
+
for (const spec of imp.specifiers) {
|
|
140
|
+
const localName = spec.alias ?? spec.name;
|
|
141
|
+
const label = spec.isDefault
|
|
142
|
+
? `default import from ${specifier}`
|
|
143
|
+
: spec.isNamespace
|
|
144
|
+
? `namespace import from ${specifier}`
|
|
145
|
+
: `named import from ${specifier}`;
|
|
146
|
+
// Don't overwrite existing evidence (first import wins for dedup)
|
|
147
|
+
if (!seedMap.has(localName)) {
|
|
148
|
+
seedMap.set(localName, {
|
|
149
|
+
identifier: localName,
|
|
150
|
+
reason: 'package',
|
|
151
|
+
source: label,
|
|
152
|
+
chain: [],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return seedMap;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Extract all validator-provenanced identifiers from a file's imports.
|
|
161
|
+
* Same pattern as extractDBProvenancedImports but for validator packages.
|
|
162
|
+
*/
|
|
163
|
+
export function extractValidatorProvenancedImports(ast, adapter) {
|
|
164
|
+
const seedMap = new Map();
|
|
165
|
+
const imports = adapter.extractImports(ast);
|
|
166
|
+
for (const imp of imports) {
|
|
167
|
+
const specifier = imp.source;
|
|
168
|
+
if (!matchesValidatorPackage(specifier))
|
|
169
|
+
continue;
|
|
170
|
+
for (const spec of imp.specifiers) {
|
|
171
|
+
const localName = spec.alias ?? spec.name;
|
|
172
|
+
const label = spec.isDefault
|
|
173
|
+
? `default import from ${specifier}`
|
|
174
|
+
: spec.isNamespace
|
|
175
|
+
? `namespace import from ${specifier}`
|
|
176
|
+
: `named import from ${specifier}`;
|
|
177
|
+
if (!seedMap.has(localName)) {
|
|
178
|
+
seedMap.set(localName, {
|
|
179
|
+
identifier: localName,
|
|
180
|
+
reason: 'package',
|
|
181
|
+
source: label,
|
|
182
|
+
chain: [],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return seedMap;
|
|
188
|
+
}
|
|
189
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
190
|
+
// Provenance propagation (R1 core)
|
|
191
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
192
|
+
/**
|
|
193
|
+
* Propagate provenance through assignments, destructuring, parameters,
|
|
194
|
+
* class fields, and type annotations within a single file.
|
|
195
|
+
*
|
|
196
|
+
* The 8 single-file propagation rules (spec R1):
|
|
197
|
+
* 1. new Expression → variable
|
|
198
|
+
* 2. DB-provenanced call return → variable
|
|
199
|
+
* 3. member expression on DB receiver → variable
|
|
200
|
+
* 4. object destructuring from DB source
|
|
201
|
+
* 5. array destructuring from DB source
|
|
202
|
+
* 6. default parameter with DB value
|
|
203
|
+
* 7. class field initialized with DB value
|
|
204
|
+
* 8. type annotation with known DB type
|
|
205
|
+
*/
|
|
206
|
+
export function propagateProvenance(ast, adapter, sourceCode, seedMap) {
|
|
207
|
+
// Work on a copy so we can add newly-provenanced identifiers during the walk
|
|
208
|
+
const provenanceMap = new Map(seedMap);
|
|
209
|
+
// Keep iterating until no new identifiers are discovered (handles chains)
|
|
210
|
+
let changed = true;
|
|
211
|
+
let iterations = 0;
|
|
212
|
+
const MAX_ITERATIONS = 10; // safety valve for circular references
|
|
213
|
+
while (changed && iterations < MAX_ITERATIONS) {
|
|
214
|
+
changed = false;
|
|
215
|
+
iterations++;
|
|
216
|
+
walkAST(ast.root, (node, parent) => {
|
|
217
|
+
// ── Rule 1 & 2 & 3 & 8: variable declarations ──
|
|
218
|
+
if (node.type === 'variable_declarator') {
|
|
219
|
+
const { nameNode, valueNode, typeAnnotationNode } = splitVariableDeclarator(node, adapter);
|
|
220
|
+
if (!nameNode)
|
|
221
|
+
return;
|
|
222
|
+
// Rule 8: type annotation — let x: D1Database
|
|
223
|
+
if (typeAnnotationNode && nameNode.type === 'identifier') {
|
|
224
|
+
const typeText = adapter.getNodeText(typeAnnotationNode, sourceCode).trim();
|
|
225
|
+
if (DB_TYPES.has(typeText)) {
|
|
226
|
+
const name = adapter.getNodeText(nameNode, sourceCode);
|
|
227
|
+
if (!provenanceMap.has(name)) {
|
|
228
|
+
provenanceMap.set(name, {
|
|
229
|
+
identifier: name,
|
|
230
|
+
reason: 'type',
|
|
231
|
+
source: `type annotation ${typeText}`,
|
|
232
|
+
chain: [],
|
|
233
|
+
});
|
|
234
|
+
changed = true;
|
|
235
|
+
}
|
|
236
|
+
// Type-provenanced names count as DB-provenanced for further propagation
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (valueNode) {
|
|
240
|
+
const propagated = tryPropagateFromExpression(valueNode, adapter, sourceCode, provenanceMap);
|
|
241
|
+
if (propagated) {
|
|
242
|
+
// Extract the variable name(s) from the name node
|
|
243
|
+
const varNames = extractPatternNames(nameNode, adapter, sourceCode);
|
|
244
|
+
for (const varName of varNames) {
|
|
245
|
+
if (!provenanceMap.has(varName)) {
|
|
246
|
+
provenanceMap.set(varName, {
|
|
247
|
+
identifier: varName,
|
|
248
|
+
reason: 'propagation',
|
|
249
|
+
source: propagated.source,
|
|
250
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
251
|
+
});
|
|
252
|
+
changed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// ── Rule 6: default parameters ──
|
|
260
|
+
if (node.type === 'assignment_pattern' &&
|
|
261
|
+
parent?.type === 'formal_parameters') {
|
|
262
|
+
const children = adapter.getChildren(node);
|
|
263
|
+
// assignment_pattern has [left, right]
|
|
264
|
+
if (children.length >= 2) {
|
|
265
|
+
const leftNode = children[0];
|
|
266
|
+
const rightNode = children[1];
|
|
267
|
+
if (leftNode.type === 'identifier') {
|
|
268
|
+
const paramName = adapter.getNodeText(leftNode, sourceCode);
|
|
269
|
+
const propagated = tryPropagateFromExpression(rightNode, adapter, sourceCode, provenanceMap);
|
|
270
|
+
if (propagated && !provenanceMap.has(paramName)) {
|
|
271
|
+
provenanceMap.set(paramName, {
|
|
272
|
+
identifier: paramName,
|
|
273
|
+
reason: 'propagation',
|
|
274
|
+
source: `default parameter = ${propagated.source}`,
|
|
275
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
276
|
+
});
|
|
277
|
+
changed = true;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// ── Rule 7: class field initialization ──
|
|
284
|
+
if (node.type === 'public_field_definition' ||
|
|
285
|
+
node.type === 'field_definition') {
|
|
286
|
+
const children = adapter.getChildren(node);
|
|
287
|
+
// Typically [name, value] or [decorators..., name, value]
|
|
288
|
+
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
289
|
+
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
290
|
+
c.type !== 'decorator' &&
|
|
291
|
+
c.type !== 'private' &&
|
|
292
|
+
c.type !== 'public' &&
|
|
293
|
+
c.type !== 'protected' &&
|
|
294
|
+
c.type !== 'static' &&
|
|
295
|
+
c.type !== 'readonly' &&
|
|
296
|
+
c.type !== 'abstract');
|
|
297
|
+
if (nameChild && valueChild) {
|
|
298
|
+
const fieldName = adapter.getNodeText(nameChild, sourceCode);
|
|
299
|
+
const propagated = tryPropagateFromExpression(valueChild, adapter, sourceCode, provenanceMap);
|
|
300
|
+
if (propagated && !provenanceMap.has(fieldName)) {
|
|
301
|
+
provenanceMap.set(fieldName, {
|
|
302
|
+
identifier: fieldName,
|
|
303
|
+
reason: 'propagation',
|
|
304
|
+
source: `class field initialized from ${propagated.source}`,
|
|
305
|
+
chain: [...propagated.chain, propagated.identifier],
|
|
306
|
+
});
|
|
307
|
+
changed = true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return provenanceMap;
|
|
315
|
+
}
|
|
316
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
317
|
+
// Propagation helpers
|
|
318
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
319
|
+
/**
|
|
320
|
+
* Given a value expression node, check if it indicates DB provenance
|
|
321
|
+
* and return the evidence of the provenanced source if so.
|
|
322
|
+
*/
|
|
323
|
+
function tryPropagateFromExpression(node, adapter, sourceCode, provenanceMap) {
|
|
324
|
+
// ── Rule 1: new Database(...) ──
|
|
325
|
+
if (node.type === 'new_expression') {
|
|
326
|
+
const constructorNode = findChildOfType(node, [
|
|
327
|
+
'identifier',
|
|
328
|
+
'member_expression',
|
|
329
|
+
]);
|
|
330
|
+
if (constructorNode) {
|
|
331
|
+
const name = extractIdentifierName(constructorNode, adapter, sourceCode);
|
|
332
|
+
if (name && provenanceMap.has(name)) {
|
|
333
|
+
return provenanceMap.get(name);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
// ── Rule 2: drizzle(env.DB) — call where callee is DB-provenanced ──
|
|
338
|
+
// ── Rule 3: db.prepare(sql) — member expression call on DB receiver ──
|
|
339
|
+
if (node.type === 'call_expression') {
|
|
340
|
+
// Walk children before arguments to get the callee
|
|
341
|
+
const calleeNode = getCallExpressionCallee(node, adapter);
|
|
342
|
+
if (calleeNode) {
|
|
343
|
+
// Case: simple identifier call — drizzle(...)
|
|
344
|
+
if (calleeNode.type === 'identifier') {
|
|
345
|
+
const name = adapter.getNodeText(calleeNode, sourceCode);
|
|
346
|
+
if (name && provenanceMap.has(name)) {
|
|
347
|
+
return provenanceMap.get(name);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Case: member expression — db.prepare(...)
|
|
351
|
+
if (calleeNode.type === 'member_expression') {
|
|
352
|
+
const receiver = getMemberExpressionReceiver(calleeNode, adapter, sourceCode);
|
|
353
|
+
if (receiver && provenanceMap.has(receiver)) {
|
|
354
|
+
return provenanceMap.get(receiver);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// ── Simple identifier reference (for destructuring sources) ──
|
|
360
|
+
if (node.type === 'identifier') {
|
|
361
|
+
const name = adapter.getNodeText(node, sourceCode);
|
|
362
|
+
if (name && provenanceMap.has(name)) {
|
|
363
|
+
return provenanceMap.get(name);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// ── Member expression on DB-provenanced source (for non-call uses) ──
|
|
367
|
+
if (node.type === 'member_expression') {
|
|
368
|
+
const receiver = getMemberExpressionReceiver(node, adapter, sourceCode);
|
|
369
|
+
if (receiver && provenanceMap.has(receiver)) {
|
|
370
|
+
return provenanceMap.get(receiver);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Split a variable_declarator into its name, value, and type annotation
|
|
377
|
+
* child nodes.
|
|
378
|
+
*/
|
|
379
|
+
function splitVariableDeclarator(node, adapter) {
|
|
380
|
+
const children = adapter.getChildren(node);
|
|
381
|
+
let nameNode = null;
|
|
382
|
+
let valueNode = null;
|
|
383
|
+
let typeAnnotationNode = null;
|
|
384
|
+
let pastEquals = false;
|
|
385
|
+
for (const child of children) {
|
|
386
|
+
if (child.type === '=' || child.type === 'equals') {
|
|
387
|
+
pastEquals = true;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (child.type === ':')
|
|
391
|
+
continue;
|
|
392
|
+
if (child.type === 'type_annotation') {
|
|
393
|
+
typeAnnotationNode = child;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (!pastEquals && !nameNode) {
|
|
397
|
+
// First non-syntax child is the name/pattern
|
|
398
|
+
if (child.type === 'identifier' ||
|
|
399
|
+
child.type === 'object_pattern' ||
|
|
400
|
+
child.type === 'array_pattern') {
|
|
401
|
+
nameNode = child;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else if (pastEquals && !valueNode) {
|
|
405
|
+
// First non-syntax child after equals is the value
|
|
406
|
+
if (child.type !== 'type_annotation') {
|
|
407
|
+
valueNode = child;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else if (!pastEquals && nameNode && !valueNode) {
|
|
411
|
+
// No '=' child in this grammar (e.g., TypeScript tree-sitter);
|
|
412
|
+
// the expression AFTER the name is the value.
|
|
413
|
+
if (child.type !== 'type_annotation') {
|
|
414
|
+
valueNode = child;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return { nameNode, valueNode, typeAnnotationNode };
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Extract all variable names from a destructuring or identifier pattern.
|
|
422
|
+
* - identifier → [name]
|
|
423
|
+
* - object_pattern → [prop1, prop2, ...] rule 4
|
|
424
|
+
* - array_pattern → [elem1, elem2, ...] rule 5
|
|
425
|
+
*/
|
|
426
|
+
function extractPatternNames(node, adapter, sourceCode) {
|
|
427
|
+
const names = [];
|
|
428
|
+
function collect(node) {
|
|
429
|
+
if (node.type === 'identifier') {
|
|
430
|
+
const name = adapter.getNodeText(node, sourceCode);
|
|
431
|
+
if (name)
|
|
432
|
+
names.push(name);
|
|
433
|
+
}
|
|
434
|
+
else if (node.type === 'object_pattern') {
|
|
435
|
+
// Each child is a shorthand_property_identifier, pair_pattern, or rest_pattern
|
|
436
|
+
for (const child of adapter.getChildren(node)) {
|
|
437
|
+
if (child.type === '{' ||
|
|
438
|
+
child.type === '}' ||
|
|
439
|
+
child.type === ',') {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (child.type === 'shorthand_property_identifier') {
|
|
443
|
+
const name = adapter.getNodeText(child, sourceCode);
|
|
444
|
+
if (name)
|
|
445
|
+
names.push(name);
|
|
446
|
+
}
|
|
447
|
+
else if (child.type === 'pair_pattern') {
|
|
448
|
+
// pair_pattern children: [property, value]
|
|
449
|
+
// Extract from the value side (which might be an identifier or nested pattern)
|
|
450
|
+
const pairChildren = adapter.getChildren(child);
|
|
451
|
+
if (pairChildren.length >= 2) {
|
|
452
|
+
collect(pairChildren[1]);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (child.type === 'rest_pattern') {
|
|
456
|
+
collectChildren(child, adapter, sourceCode, names);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
collect(child);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
else if (node.type === 'array_pattern') {
|
|
464
|
+
for (const child of adapter.getChildren(node)) {
|
|
465
|
+
if (child.type === '[' || child.type === ']' || child.type === ',') {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
collect(child);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
else if (node.type === 'assignment_pattern') {
|
|
472
|
+
// Default value in destructuring — collect from the left side
|
|
473
|
+
const children = adapter.getChildren(node);
|
|
474
|
+
if (children.length >= 1) {
|
|
475
|
+
collect(children[0]);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
collect(node);
|
|
480
|
+
return names;
|
|
481
|
+
}
|
|
482
|
+
function collectChildren(node, adapter, sourceCode, names) {
|
|
483
|
+
for (const child of adapter.getChildren(node)) {
|
|
484
|
+
if (child.type === 'identifier') {
|
|
485
|
+
const name = adapter.getNodeText(child, sourceCode);
|
|
486
|
+
if (name)
|
|
487
|
+
names.push(name);
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
collectChildren(child, adapter, sourceCode, names);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Extract the "receiver" identifier from a member expression chain.
|
|
496
|
+
* For `db.prepare` → "db"
|
|
497
|
+
* For `this.db.prepare` → "db" (walk to the deepest non-member identifier)
|
|
498
|
+
*/
|
|
499
|
+
function getMemberExpressionReceiver(node, adapter, sourceCode) {
|
|
500
|
+
// Walk down the member expression chain to find the root object
|
|
501
|
+
let current = node;
|
|
502
|
+
while (current.type === 'member_expression') {
|
|
503
|
+
const children = adapter.getChildren(current);
|
|
504
|
+
const object = children.find((c) => c.type !== '.' && c.type !== 'property_identifier');
|
|
505
|
+
// The object of this member expression should be the first child
|
|
506
|
+
const firstChild = children[0];
|
|
507
|
+
if (firstChild &&
|
|
508
|
+
firstChild.type !== '.' &&
|
|
509
|
+
firstChild.type !== 'property_identifier') {
|
|
510
|
+
if (firstChild.type === 'member_expression') {
|
|
511
|
+
current = firstChild;
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
if (firstChild.type === 'identifier') {
|
|
515
|
+
return adapter.getNodeText(firstChild, sourceCode);
|
|
516
|
+
}
|
|
517
|
+
// e.g., this.db → member_expression(this, db)
|
|
518
|
+
if (firstChild.type === 'this' || firstChild.type === 'super') {
|
|
519
|
+
// This is a member expression on `this` — check the property side
|
|
520
|
+
// We need to check if `this.X` is provenanced... but `this` itself isn't.
|
|
521
|
+
// For propagation, this means looking at the full chain.
|
|
522
|
+
// For now, return null — this is handled by the caller
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Get the callee of a call expression (everything before arguments).
|
|
532
|
+
*/
|
|
533
|
+
function getCallExpressionCallee(node, adapter) {
|
|
534
|
+
for (const child of adapter.getChildren(node)) {
|
|
535
|
+
if (child.type === 'arguments')
|
|
536
|
+
break;
|
|
537
|
+
// await_expression wraps the actual callee — recurse into it
|
|
538
|
+
if (child.type === 'await_expression') {
|
|
539
|
+
const inner = getCallExpressionCallee(child, adapter);
|
|
540
|
+
if (inner)
|
|
541
|
+
return inner;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (child.type === 'identifier' ||
|
|
545
|
+
child.type === 'member_expression' ||
|
|
546
|
+
child.type === 'call_expression') {
|
|
547
|
+
return child;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
/** Find first child (recursively excluding punctuation) matching one of the types. */
|
|
553
|
+
function findChildOfType(node, types) {
|
|
554
|
+
for (const child of node.children ?? []) {
|
|
555
|
+
if (types.includes(child.type))
|
|
556
|
+
return child;
|
|
557
|
+
const found = findChildOfType(child, types);
|
|
558
|
+
if (found)
|
|
559
|
+
return found;
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
/** Extract the identifier name from a node (handles member_expression chains). */
|
|
564
|
+
function extractIdentifierName(node, adapter, sourceCode) {
|
|
565
|
+
if (node.type === 'identifier') {
|
|
566
|
+
return adapter.getNodeText(node, sourceCode);
|
|
567
|
+
}
|
|
568
|
+
if (node.type === 'member_expression') {
|
|
569
|
+
// Get the deepest identifier in the chain
|
|
570
|
+
const firstChild = node.children?.[0];
|
|
571
|
+
if (firstChild) {
|
|
572
|
+
return extractIdentifierName(firstChild, adapter, sourceCode);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return null;
|
|
576
|
+
}
|
|
577
|
+
/** Depth-first walk of the AST, calling the visitor for each node. */
|
|
578
|
+
function walkAST(root, visitor) {
|
|
579
|
+
function walk(node, parent) {
|
|
580
|
+
visitor(node, parent);
|
|
581
|
+
for (const child of node.children ?? []) {
|
|
582
|
+
walk(child, node);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
walk(root, null);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Build a ProvenanceContext for a file.
|
|
589
|
+
*
|
|
590
|
+
* This is the main entry point — call once per file before analysis.
|
|
591
|
+
* Combines import extraction, propagation, and mode-based fallback.
|
|
592
|
+
*/
|
|
593
|
+
export function buildProvenanceContext(ast, adapter, sourceCode, options) {
|
|
594
|
+
const mode = options.mode;
|
|
595
|
+
// 1. Extract seed identifiers from imports
|
|
596
|
+
const dbSeeds = extractDBProvenancedImports(ast, adapter);
|
|
597
|
+
const validatorSeeds = options.mode !== 'names'
|
|
598
|
+
? extractValidatorProvenancedImports(ast, adapter)
|
|
599
|
+
: new Map();
|
|
600
|
+
// 2. Propagate provenance through assignments
|
|
601
|
+
let dbProvenanced = propagateProvenance(ast, adapter, sourceCode, dbSeeds);
|
|
602
|
+
let validatorProvenanced = mode !== 'names'
|
|
603
|
+
? propagateProvenance(ast, adapter, sourceCode, validatorSeeds)
|
|
604
|
+
: new Map();
|
|
605
|
+
// 3. Fallback: in hybrid mode, add identifiers that match name lists
|
|
606
|
+
// but weren't caught by provenance (R3)
|
|
607
|
+
if (mode === 'hybrid') {
|
|
608
|
+
dbProvenanced = addNameListFallbacks(dbProvenanced, sourceCode, options.dbReceiverNames ?? [], options.dbBindingNames ?? []);
|
|
609
|
+
}
|
|
610
|
+
// 4. In names mode, use ONLY name lists
|
|
611
|
+
if (mode === 'names') {
|
|
612
|
+
dbProvenanced = buildNamesOnlyProvenance(sourceCode, options.dbReceiverNames ?? [], options.dbBindingNames ?? []);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
dbProvenanced,
|
|
616
|
+
validatorProvenanced,
|
|
617
|
+
mode,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Add fallback provenance entries for identifiers that match name lists
|
|
622
|
+
* but weren't caught by the provenance chain (R3 hybrid mode).
|
|
623
|
+
*
|
|
624
|
+
* These entries carry `reason: 'fallback'` — visible in config detection
|
|
625
|
+
* so users can audit and tighten their chains.
|
|
626
|
+
*/
|
|
627
|
+
function addNameListFallbacks(provenanceMap, sourceCode, dbReceiverNames, dbBindingNames) {
|
|
628
|
+
const result = new Map(provenanceMap);
|
|
629
|
+
// Scan for DB receiver names used as identifiers
|
|
630
|
+
for (const name of dbReceiverNames) {
|
|
631
|
+
if (result.has(name))
|
|
632
|
+
continue; // already provenanced — provenance wins
|
|
633
|
+
// Check if this name appears as an identifier in the source
|
|
634
|
+
if (identifierAppearsInSource(sourceCode, name)) {
|
|
635
|
+
result.set(name, {
|
|
636
|
+
identifier: name,
|
|
637
|
+
reason: 'fallback',
|
|
638
|
+
source: `name list match: dbReceiverNames contains "${name}"`,
|
|
639
|
+
chain: [],
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Scan for binding names like env.DB
|
|
644
|
+
for (const binding of dbBindingNames) {
|
|
645
|
+
if (sourceCode.includes(binding)) {
|
|
646
|
+
// Extract the property after the dot, e.g. "DB" from "env.DB"
|
|
647
|
+
const dotIdx = binding.lastIndexOf('.');
|
|
648
|
+
const shortName = dotIdx >= 0 ? binding.substring(dotIdx + 1) : binding;
|
|
649
|
+
// Also add the full binding path
|
|
650
|
+
if (!result.has(binding)) {
|
|
651
|
+
result.set(binding, {
|
|
652
|
+
identifier: binding,
|
|
653
|
+
reason: 'fallback',
|
|
654
|
+
source: `name list match: dbBindingNames contains "${binding}"`,
|
|
655
|
+
chain: [],
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
if (shortName !== binding && !result.has(shortName)) {
|
|
659
|
+
result.set(shortName, {
|
|
660
|
+
identifier: shortName,
|
|
661
|
+
reason: 'fallback',
|
|
662
|
+
source: `from binding ${binding}`,
|
|
663
|
+
chain: [],
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return result;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Build a provenance map using ONLY name lists (for names mode).
|
|
672
|
+
*/
|
|
673
|
+
function buildNamesOnlyProvenance(sourceCode, dbReceiverNames, dbBindingNames) {
|
|
674
|
+
return addNameListFallbacks(new Map(), sourceCode, dbReceiverNames, dbBindingNames);
|
|
675
|
+
}
|
|
676
|
+
/** Check if an identifier name appears as a standalone identifier in source. */
|
|
677
|
+
function identifierAppearsInSource(sourceCode, name) {
|
|
678
|
+
// Use word boundary matching to avoid partial matches
|
|
679
|
+
const pattern = new RegExp(`\\b${escapeRegex(name)}\\b`);
|
|
680
|
+
return pattern.test(sourceCode);
|
|
681
|
+
}
|
|
682
|
+
function escapeRegex(s) {
|
|
683
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
684
|
+
}
|
|
685
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
686
|
+
// Core detection — is this call DB-provenanced?
|
|
687
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
688
|
+
/**
|
|
689
|
+
* Determine if a call-expression node's callee is DB-provenanced.
|
|
690
|
+
*
|
|
691
|
+
* This replaces the old `isDBCallee()` / `isDbCallNode()` name-based
|
|
692
|
+
* pattern matching in UniversalDataAccessAnalyzer.
|
|
693
|
+
*
|
|
694
|
+
* Checks:
|
|
695
|
+
* 1. Simple identifier call → is the identifier DB-provenanced?
|
|
696
|
+
* 2. Member expression call → is the receiver DB-provenanced AND is the
|
|
697
|
+
* method in the DB call method set?
|
|
698
|
+
* 3. ORM patterns → receiver is DB-provenanced and method matches ORM API
|
|
699
|
+
*/
|
|
700
|
+
export function isDBProvenanced(node, adapter, sourceCode, context, dbCallMethods) {
|
|
701
|
+
if (node.type !== 'call_expression')
|
|
702
|
+
return false;
|
|
703
|
+
const methods = dbCallMethods ?? DB_CALL_METHODS;
|
|
704
|
+
const calleeNode = getCallExpressionCallee(node, adapter);
|
|
705
|
+
if (!calleeNode)
|
|
706
|
+
return false;
|
|
707
|
+
// Case 1: Simple identifier call — e.g. query(...)
|
|
708
|
+
if (calleeNode.type === 'identifier') {
|
|
709
|
+
const name = adapter.getNodeText(calleeNode, sourceCode);
|
|
710
|
+
if (name !== null && context.dbProvenanced.has(name)) {
|
|
711
|
+
return true;
|
|
712
|
+
}
|
|
713
|
+
// Hybrid fallback: standalone calls to dbCallMethods (query, execute, etc.)
|
|
714
|
+
// qualify as likely DB calls when provenance couldn't resolve the import.
|
|
715
|
+
// This catches patterns like `import { query } from './db'` where './db' is
|
|
716
|
+
// a local re-export of a known package — provenance can't see through it,
|
|
717
|
+
// but the method name is strong evidence.
|
|
718
|
+
if (context.mode === 'hybrid' && name !== null && methods.has(name)) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
723
|
+
// Case 2: Member expression — e.g. db.prepare(...)
|
|
724
|
+
if (calleeNode.type === 'member_expression') {
|
|
725
|
+
return isMemberExpressionDBProvenanced(calleeNode, adapter, sourceCode, context, methods);
|
|
726
|
+
}
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Check if a member_expression call is DB-provenanced.
|
|
731
|
+
*
|
|
732
|
+
* Traverses the member chain to find the root receiver,
|
|
733
|
+
* checks if it's in the provenance context, and verifies
|
|
734
|
+
* the method matches the DB call/ORM API.
|
|
735
|
+
*/
|
|
736
|
+
function isMemberExpressionDBProvenanced(node, adapter, sourceCode, context, methods) {
|
|
737
|
+
// Walk the member expression chain to find the root
|
|
738
|
+
let rootReceiver = null;
|
|
739
|
+
let current = node;
|
|
740
|
+
while (current.type === 'member_expression') {
|
|
741
|
+
const children = adapter.getChildren(current);
|
|
742
|
+
const firstChild = children[0];
|
|
743
|
+
if (!firstChild)
|
|
744
|
+
break;
|
|
745
|
+
if (firstChild.type === 'identifier') {
|
|
746
|
+
rootReceiver = adapter.getNodeText(firstChild, sourceCode);
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
if (firstChild.type === 'member_expression') {
|
|
750
|
+
current = firstChild;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
// this.db.prepare → root is a chain on `this`, check `this.xxx`
|
|
754
|
+
if (firstChild.type === 'this' || firstChild.type === 'super') {
|
|
755
|
+
rootReceiver = 'this';
|
|
756
|
+
break;
|
|
757
|
+
}
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
if (!rootReceiver)
|
|
761
|
+
return false;
|
|
762
|
+
// In names mode, check the method name directly
|
|
763
|
+
if (context.mode === 'names') {
|
|
764
|
+
return isDBMethodCall(node, adapter, sourceCode, context, methods);
|
|
765
|
+
}
|
|
766
|
+
// Check if the root receiver is DB-provenanced
|
|
767
|
+
if (!context.dbProvenanced.has(rootReceiver)) {
|
|
768
|
+
if (rootReceiver !== 'this')
|
|
769
|
+
return false;
|
|
770
|
+
// For `this.xxx`, check if the method chain itself suggests DB usage
|
|
771
|
+
return isDBMethodOnThis(node, adapter, sourceCode, context, methods);
|
|
772
|
+
}
|
|
773
|
+
// Check that the method is in the DB call/ORM API
|
|
774
|
+
return isDBMethodCall(node, adapter, sourceCode, context, methods);
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Check if a call through a member expression uses a DB method
|
|
778
|
+
* (exec, prepare, all, etc.) or an ORM method (find, insert, etc.).
|
|
779
|
+
*/
|
|
780
|
+
function isDBMethodCall(node, _adapter, _sourceCode, _context, methods) {
|
|
781
|
+
// Walk the member expression chain and check each property
|
|
782
|
+
let current = node;
|
|
783
|
+
while (current.type === 'member_expression') {
|
|
784
|
+
const children = current.children ?? [];
|
|
785
|
+
// The property is typically the second or third child
|
|
786
|
+
for (const child of children) {
|
|
787
|
+
if (child.type === 'property_identifier') {
|
|
788
|
+
const propName = child.type === 'property_identifier'
|
|
789
|
+
? _adapter.getNodeText(child, _sourceCode)
|
|
790
|
+
: null;
|
|
791
|
+
if (propName && (methods.has(propName) || ORM_METHODS.has(propName))) {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
// Go deeper if there's a nested member expression
|
|
797
|
+
const firstChild = children[0];
|
|
798
|
+
if (firstChild?.type === 'member_expression') {
|
|
799
|
+
current = firstChild;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return false;
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* For `this.xxx.method()` calls — check if the method chain suggests
|
|
809
|
+
* DB access. Used when the receiver is `this` (not directly DB-provenanced).
|
|
810
|
+
*/
|
|
811
|
+
function isDBMethodOnThis(node, adapter, sourceCode, _context, methods) {
|
|
812
|
+
// Walk the chain: this.db.prepare → check if any property matches DB methods
|
|
813
|
+
let current = node;
|
|
814
|
+
while (current.type === 'member_expression') {
|
|
815
|
+
const children = adapter.getChildren(current);
|
|
816
|
+
for (const child of children) {
|
|
817
|
+
if (child.type === 'property_identifier') {
|
|
818
|
+
const propName = adapter.getNodeText(child, sourceCode);
|
|
819
|
+
if (propName && (methods.has(propName) || ORM_METHODS.has(propName))) {
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
const firstChild = children[0];
|
|
825
|
+
if (firstChild?.type === 'member_expression') {
|
|
826
|
+
current = firstChild;
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Check if an identifier is validator-provenanced (R4 infrastructure).
|
|
836
|
+
*
|
|
837
|
+
* This will be consumed by Spec 15's validator-bypass detection.
|
|
838
|
+
*/
|
|
839
|
+
export function isValidatorProvenanced(identifier, context) {
|
|
840
|
+
return context.validatorProvenanced.has(identifier);
|
|
841
|
+
}
|
|
842
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
843
|
+
// Inference (R2 — to be wired during R2 implementation)
|
|
844
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
845
|
+
/**
|
|
846
|
+
* Infer additional DB receivers by tracing identifiers that call DB methods
|
|
847
|
+
* back through assignment chains to provenanced sources.
|
|
848
|
+
*
|
|
849
|
+
* This is the conjunctive inference guard in action: an identifier is only
|
|
850
|
+
* inferred if it (a) appears as a receiver of a known DB call method AND
|
|
851
|
+
* (b) can be traced back to a provenanced source through assignments.
|
|
852
|
+
*
|
|
853
|
+
* Deferred: full implementation in R2 step.
|
|
854
|
+
*/
|
|
855
|
+
export function inferReceivers(provenancedSet, fileAst, adapter, sourceCode) {
|
|
856
|
+
const inferred = { identifiers: [], evidence: [] };
|
|
857
|
+
const seen = new Set();
|
|
858
|
+
// Step 1: Build an assignment graph — name → source text of initializer
|
|
859
|
+
const assignmentGraph = buildAssignmentGraph(fileAst, adapter, sourceCode);
|
|
860
|
+
// Step 2: Walk all call expressions whose callee is a member expression
|
|
861
|
+
walkAST(fileAst.root, (node) => {
|
|
862
|
+
if (node.type !== 'call_expression')
|
|
863
|
+
return;
|
|
864
|
+
const callee = getCallExpressionCallee(node, adapter);
|
|
865
|
+
if (!callee || callee.type !== 'member_expression')
|
|
866
|
+
return;
|
|
867
|
+
// Extract method name (the property being called)
|
|
868
|
+
const methodName = extractMemberExpressionProperty(callee, adapter, sourceCode);
|
|
869
|
+
if (!methodName || !DB_CALL_METHODS.has(methodName))
|
|
870
|
+
return;
|
|
871
|
+
// Get the receiver identifier
|
|
872
|
+
const receiver = getMemberExpressionReceiver(callee, adapter, sourceCode);
|
|
873
|
+
if (!receiver)
|
|
874
|
+
return;
|
|
875
|
+
// Already in the provenanced set → skip (already first-class, not inferred)
|
|
876
|
+
if (provenancedSet.has(receiver))
|
|
877
|
+
return;
|
|
878
|
+
// Already added to inferred
|
|
879
|
+
if (seen.has(receiver))
|
|
880
|
+
return;
|
|
881
|
+
// Step 3: Trace receiver through assignment chain to see if it reaches
|
|
882
|
+
// a provenanced source
|
|
883
|
+
const traced = traceAssignmentChain(receiver, assignmentGraph, provenancedSet);
|
|
884
|
+
if (traced.found) {
|
|
885
|
+
seen.add(receiver);
|
|
886
|
+
inferred.identifiers.push(receiver);
|
|
887
|
+
inferred.evidence.push({
|
|
888
|
+
identifier: receiver,
|
|
889
|
+
reason: `calls .${methodName}() traced to ${traced.chains[0]} — ${traced.reason}`,
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
return inferred;
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Build a reverse assignment graph from the AST.
|
|
897
|
+
* Maps variable name → the text of its initializer expression.
|
|
898
|
+
* Handles: const/let/var declarations, parameter defaults, class fields.
|
|
899
|
+
*/
|
|
900
|
+
function buildAssignmentGraph(ast, adapter, sourceCode) {
|
|
901
|
+
const graph = new Map();
|
|
902
|
+
walkAST(ast.root, (node, parent) => {
|
|
903
|
+
// const/let/var x = <expr>
|
|
904
|
+
if (node.type === 'variable_declarator') {
|
|
905
|
+
const { nameNode, valueNode } = splitVariableDeclarator(node, adapter);
|
|
906
|
+
if (nameNode && valueNode) {
|
|
907
|
+
const names = extractPatternNames(nameNode, adapter, sourceCode);
|
|
908
|
+
const valueText = adapter.getNodeText(valueNode, sourceCode);
|
|
909
|
+
for (const name of names) {
|
|
910
|
+
if (!graph.has(name))
|
|
911
|
+
graph.set(name, valueText);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// Parameter defaults: function foo(x = <expr>)
|
|
917
|
+
if (node.type === 'assignment_pattern' &&
|
|
918
|
+
parent?.type === 'formal_parameters') {
|
|
919
|
+
const children = adapter.getChildren(node);
|
|
920
|
+
if (children.length >= 2) {
|
|
921
|
+
const leftNode = children[0];
|
|
922
|
+
const rightNode = children[1];
|
|
923
|
+
if (leftNode.type === 'identifier') {
|
|
924
|
+
const paramName = adapter.getNodeText(leftNode, sourceCode);
|
|
925
|
+
const valueText = adapter.getNodeText(rightNode, sourceCode);
|
|
926
|
+
if (!graph.has(paramName))
|
|
927
|
+
graph.set(paramName, valueText);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
// Class field: fieldName = <expr>
|
|
933
|
+
if (node.type === 'public_field_definition' ||
|
|
934
|
+
node.type === 'field_definition') {
|
|
935
|
+
const children = adapter.getChildren(node);
|
|
936
|
+
const nameChild = children.find((c) => c.type === 'property_identifier');
|
|
937
|
+
const valueChild = children.find((c) => c.type !== 'property_identifier' &&
|
|
938
|
+
c.type !== 'decorator' &&
|
|
939
|
+
c.type !== 'private' &&
|
|
940
|
+
c.type !== 'public' &&
|
|
941
|
+
c.type !== 'protected' &&
|
|
942
|
+
c.type !== 'static' &&
|
|
943
|
+
c.type !== 'readonly' &&
|
|
944
|
+
c.type !== 'abstract' &&
|
|
945
|
+
c.type !== '=');
|
|
946
|
+
if (nameChild && valueChild) {
|
|
947
|
+
const fieldName = adapter.getNodeText(nameChild, sourceCode);
|
|
948
|
+
const valueText = adapter.getNodeText(valueChild, sourceCode);
|
|
949
|
+
if (!graph.has(fieldName))
|
|
950
|
+
graph.set(fieldName, valueText);
|
|
951
|
+
}
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
return graph;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Trace a receiver identifier through the assignment graph to see if it
|
|
959
|
+
* ultimately resolves to a provenanced source.
|
|
960
|
+
*/
|
|
961
|
+
function traceAssignmentChain(receiver, assignmentGraph, provenancedSet) {
|
|
962
|
+
const visited = new Set();
|
|
963
|
+
const chain = [receiver];
|
|
964
|
+
let current = receiver;
|
|
965
|
+
let depth = 0;
|
|
966
|
+
const MAX_DEPTH = 10;
|
|
967
|
+
while (depth < MAX_DEPTH) {
|
|
968
|
+
// Check if current is directly provenanced
|
|
969
|
+
if (provenancedSet.has(current)) {
|
|
970
|
+
return {
|
|
971
|
+
found: true,
|
|
972
|
+
chains: chain,
|
|
973
|
+
reason: provenancedSet.get(current).source,
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
// Also check if any identifier in the initializer text matches a provenanced source
|
|
977
|
+
const initText = assignmentGraph.get(current);
|
|
978
|
+
if (initText) {
|
|
979
|
+
// Extract identifiers from the initializer text and check against provenanced set
|
|
980
|
+
const idents = extractTopLevelIdentifiers(initText);
|
|
981
|
+
for (const ident of idents) {
|
|
982
|
+
if (provenancedSet.has(ident)) {
|
|
983
|
+
chain.push(current);
|
|
984
|
+
return {
|
|
985
|
+
found: true,
|
|
986
|
+
chains: chain,
|
|
987
|
+
reason: provenancedSet.get(ident).source,
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
// Move to the next link in the chain
|
|
993
|
+
if (!initText || visited.has(initText))
|
|
994
|
+
break;
|
|
995
|
+
visited.add(initText);
|
|
996
|
+
// Try to find a next identifier to trace
|
|
997
|
+
const idents = extractTopLevelIdentifiers(initText);
|
|
998
|
+
let foundNext = false;
|
|
999
|
+
for (const ident of idents) {
|
|
1000
|
+
if (assignmentGraph.has(ident) && !visited.has(ident)) {
|
|
1001
|
+
chain.push(ident);
|
|
1002
|
+
current = ident;
|
|
1003
|
+
foundNext = true;
|
|
1004
|
+
break;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (!foundNext)
|
|
1008
|
+
break;
|
|
1009
|
+
depth++;
|
|
1010
|
+
}
|
|
1011
|
+
return { found: false, chains: [], reason: '' };
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Extract top-level identifier names from an expression text.
|
|
1015
|
+
* E.g., "db.prepare(sql)" → ["db"], "getConnection()" → ["getConnection"]
|
|
1016
|
+
*/
|
|
1017
|
+
function extractTopLevelIdentifiers(text) {
|
|
1018
|
+
// Strip member access and arguments to get the root identifier
|
|
1019
|
+
// Match the first identifier before any '.' or '('
|
|
1020
|
+
const match = text.match(/^[\p{L}_$][\p{L}\p{N}_$]*/u);
|
|
1021
|
+
if (match && match[0]) {
|
|
1022
|
+
return [match[0]];
|
|
1023
|
+
}
|
|
1024
|
+
return [];
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Extract the property name from a member expression node.
|
|
1028
|
+
* For `db.prepare` → "prepare", for `db.sql.prepare` → "prepare"
|
|
1029
|
+
*/
|
|
1030
|
+
function extractMemberExpressionProperty(node, adapter, sourceCode) {
|
|
1031
|
+
const children = adapter.getChildren(node);
|
|
1032
|
+
// The property is the last non-dot child
|
|
1033
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
1034
|
+
const child = children[i];
|
|
1035
|
+
if (child.type === 'property_identifier') {
|
|
1036
|
+
return adapter.getNodeText(child, sourceCode);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
//# sourceMappingURL=provenance.js.map
|