code-auditor-mcp 3.1.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +665 -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 -4
- 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 -5
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +0 -3
- package/dist/analyzers/cross-language/DependencyGraphBuilder.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 -5
- 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 -103
- 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 +483 -3
- 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 +261 -0
- package/dist/churn/churnExtractor.js.map +1 -0
- package/dist/cli.js +1215 -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 +661 -5
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +95 -1
- 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 +59 -24
- 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/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
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
|
|
12
12
|
import picomatch from 'picomatch';
|
|
13
|
+
import { buildProvenanceContext, isDBProvenanced, DB_CALL_METHODS, } from '../provenance.js';
|
|
14
|
+
import { OrmAdapterRegistry } from '../orm/index.js';
|
|
13
15
|
export const DEFAULT_SCHEMA_CONFIG = {
|
|
14
16
|
enableTableUsageTracking: true,
|
|
15
17
|
checkMissingReferences: true,
|
|
@@ -34,6 +36,7 @@ export const DEFAULT_SCHEMA_CONFIG = {
|
|
|
34
36
|
fileGateGlobs: ['**/*.sql', '**/migrations/**'],
|
|
35
37
|
};
|
|
36
38
|
import { promises as fs } from 'fs';
|
|
39
|
+
import { CodeIndexDB } from '../../codeIndexDB.js';
|
|
37
40
|
export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
38
41
|
name = 'schema';
|
|
39
42
|
description = 'Analyzes code against database schemas and validates JSON schemas';
|
|
@@ -61,8 +64,20 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
61
64
|
async analyzeAST(ast, adapter, config, sourceCode) {
|
|
62
65
|
const violations = [];
|
|
63
66
|
const finalConfig = { ...DEFAULT_SCHEMA_CONFIG, ...config };
|
|
64
|
-
//
|
|
65
|
-
|
|
67
|
+
// Spec 21: Build provenance context for this file (R1 — provenance-primary detection)
|
|
68
|
+
const detectionMode = config.detection?.mode ?? 'hybrid';
|
|
69
|
+
const p0 = performance.now();
|
|
70
|
+
const provenanceContext = buildProvenanceContext(ast, adapter, sourceCode, {
|
|
71
|
+
mode: detectionMode,
|
|
72
|
+
dbReceiverNames: finalConfig.dbReceiverNames ?? DEFAULT_SCHEMA_CONFIG.dbReceiverNames,
|
|
73
|
+
dbBindingNames: finalConfig.dbBindingNames ?? DEFAULT_SCHEMA_CONFIG.dbBindingNames,
|
|
74
|
+
dbCallMethods: finalConfig.dbCallMethods ?? DEFAULT_SCHEMA_CONFIG.dbCallMethods,
|
|
75
|
+
});
|
|
76
|
+
const timingAcc = config._provenanceTiming;
|
|
77
|
+
if (timingAcc)
|
|
78
|
+
timingAcc.totalMs += performance.now() - p0;
|
|
79
|
+
// R2.2 — File gate: only analyze files with DB context (Spec 21: provenance-based)
|
|
80
|
+
if (!this.passesFileGate(ast.filePath, sourceCode, finalConfig, provenanceContext)) {
|
|
66
81
|
return violations;
|
|
67
82
|
}
|
|
68
83
|
// Get available schemas
|
|
@@ -77,12 +92,18 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
77
92
|
}
|
|
78
93
|
}
|
|
79
94
|
if (finalConfig.requiredSchemas && finalConfig.requiredSchemas.length > 0 && schemas.length === 0) {
|
|
80
|
-
violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'No database schemas loaded for analysis', 'warning', 'missing-schemas'));
|
|
95
|
+
violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'No database schemas loaded for analysis', 'warning', 'missing-schemas', undefined, 'top-level:missing-schemas'));
|
|
81
96
|
return violations;
|
|
82
97
|
}
|
|
83
98
|
// R2.1 — AST-based table reference extraction (replaces legacy regex scan-all-strings)
|
|
84
|
-
|
|
99
|
+
// Spec 21: Uses provenance context for DB-call pattern detection
|
|
100
|
+
const tableRefs = this.findTableReferences(ast, adapter, sourceCode, finalConfig, provenanceContext);
|
|
85
101
|
const columnRefs = this.findColumnReferences(ast, adapter, sourceCode);
|
|
102
|
+
// Spec 15 R1 — Record schema usage for cross-domain lifecycle analysis.
|
|
103
|
+
// Idempotent per-file: clear stale entries before inserting fresh references.
|
|
104
|
+
if (finalConfig.enableTableUsageTracking) {
|
|
105
|
+
this.recordTableUsage(ast, adapter, ast.filePath, tableRefs);
|
|
106
|
+
}
|
|
86
107
|
// Check for missing table references — R2.4: Levenshtein suggestions
|
|
87
108
|
if (finalConfig.checkMissingReferences) {
|
|
88
109
|
for (const ref of tableRefs) {
|
|
@@ -92,13 +113,13 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
92
113
|
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|
|
93
114
|
: `Reference to unknown table '${ref.table}' (${ref.type})`;
|
|
94
115
|
violations.push(this.createViolation(ast.filePath, ref.location, msg, 'suggestion', // R7
|
|
95
|
-
'unknown-table'));
|
|
116
|
+
'unknown-table', undefined, ref.table));
|
|
96
117
|
}
|
|
97
118
|
}
|
|
98
119
|
for (const ref of columnRefs) {
|
|
99
120
|
const columnSet = tableColumns.get(ref.table);
|
|
100
121
|
if (columnSet && !columnSet.has(ref.column)) {
|
|
101
|
-
violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown column '${ref.column}' in table '${ref.table}'`, 'warning', 'unknown-column'));
|
|
122
|
+
violations.push(this.createViolation(ast.filePath, ref.location, `Reference to unknown column '${ref.column}' in table '${ref.table}'`, 'warning', 'unknown-column', undefined, `${ref.table}.${ref.column}`));
|
|
102
123
|
}
|
|
103
124
|
}
|
|
104
125
|
}
|
|
@@ -121,7 +142,7 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
121
142
|
* Pre-filter: only analyze files that show DB usage.
|
|
122
143
|
* Checks: .sql/migration glob, D1/SQL imports, env-binding patterns, DB calls.
|
|
123
144
|
*/
|
|
124
|
-
passesFileGate(filePath, sourceCode, config) {
|
|
145
|
+
passesFileGate(filePath, sourceCode, config, provenanceContext) {
|
|
125
146
|
// Always pass .sql files and migration directories
|
|
126
147
|
const gateGlobs = config.fileGateGlobs ?? ['**/*.sql', '**/migrations/**'];
|
|
127
148
|
for (const glob of gateGlobs) {
|
|
@@ -129,38 +150,49 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
129
150
|
return true;
|
|
130
151
|
}
|
|
131
152
|
}
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
/import\s+.*from\s+['"].*mysql['"]/,
|
|
138
|
-
/import\s+.*from\s+['"].*sqlite['"]/,
|
|
139
|
-
/import\s+.*from\s+['"].*knex['"]/,
|
|
140
|
-
/import\s+.*from\s+['"].*drizzle['"]/,
|
|
141
|
-
/import\s+.*from\s+['"].*prisma['"]/,
|
|
142
|
-
];
|
|
143
|
-
for (const pat of importPatterns) {
|
|
144
|
-
if (pat.test(sourceCode))
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
// Check for env-binding patterns (e.g., env.DB in Cloudflare Workers)
|
|
148
|
-
const bindingNames = config.dbBindingNames ?? ['env.DB'];
|
|
149
|
-
for (const binding of bindingNames) {
|
|
150
|
-
if (sourceCode.includes(binding))
|
|
153
|
+
// Spec 21: Provenance-first DB detection — if any identifier is DB-provenanced,
|
|
154
|
+
// this file passes the gate. This replaces the regex patterns for import/environment/
|
|
155
|
+
// receiver.method checks in hybrid and provenance modes.
|
|
156
|
+
if (provenanceContext && provenanceContext.mode !== 'names') {
|
|
157
|
+
if (provenanceContext.dbProvenanced.size > 0) {
|
|
151
158
|
return true;
|
|
159
|
+
}
|
|
152
160
|
}
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
161
|
+
// Legacy name-based detection — used in 'names' mode or when no provenance context
|
|
162
|
+
if (!provenanceContext || provenanceContext.mode === 'names') {
|
|
163
|
+
// Check for D1 or SQL API imports
|
|
164
|
+
const importPatterns = [
|
|
165
|
+
/import\s+.*\b(D1Database|D1PreparedStatement|D1Result)\b/,
|
|
166
|
+
/import\s+.*from\s+['"].*d1['"]/,
|
|
167
|
+
/import\s+.*from\s+['"].*pg['"]/,
|
|
168
|
+
/import\s+.*from\s+['"].*mysql['"]/,
|
|
169
|
+
/import\s+.*from\s+['"].*sqlite['"]/,
|
|
170
|
+
/import\s+.*from\s+['"].*knex['"]/,
|
|
171
|
+
/import\s+.*from\s+['"].*drizzle['"]/,
|
|
172
|
+
/import\s+.*from\s+['"].*prisma['"]/,
|
|
173
|
+
];
|
|
174
|
+
for (const pat of importPatterns) {
|
|
175
|
+
if (pat.test(sourceCode))
|
|
160
176
|
return true;
|
|
161
177
|
}
|
|
178
|
+
// Check for env-binding patterns (e.g., env.DB in Cloudflare Workers)
|
|
179
|
+
const bindingNames = config.dbBindingNames ?? ['env.DB'];
|
|
180
|
+
for (const binding of bindingNames) {
|
|
181
|
+
if (sourceCode.includes(binding))
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
// Check for DB call patterns (receiver.method)
|
|
185
|
+
const receivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
|
|
186
|
+
const methods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first'];
|
|
187
|
+
for (const receiver of receivers) {
|
|
188
|
+
for (const method of methods) {
|
|
189
|
+
const pattern = new RegExp(`\\b${escapeRegex(receiver)}\\.${escapeRegex(method)}\\s*\\(`);
|
|
190
|
+
if (pattern.test(sourceCode))
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
162
194
|
}
|
|
163
|
-
// Check for SQL tagged template literals
|
|
195
|
+
// Check for SQL tagged template literals (syntax feature, not naming convention)
|
|
164
196
|
const sqlTags = config.sqlTagNames ?? ['sql', 'db'];
|
|
165
197
|
for (const tag of sqlTags) {
|
|
166
198
|
const pattern = new RegExp(`\\b${escapeRegex(tag)}\`\\s*SELECT|\\b${escapeRegex(tag)}\`\\s*INSERT|\\b${escapeRegex(tag)}\`\\s*UPDATE|\\b${escapeRegex(tag)}\`\\s*DELETE|\\b${escapeRegex(tag)}\`\\s*CREATE`, 'i');
|
|
@@ -177,12 +209,11 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
177
209
|
* R2.1: Only tagged template SQL and DB-call patterns produce candidates.
|
|
178
210
|
* R2.3: Template expressions (${var}) resolved to wildcards.
|
|
179
211
|
*/
|
|
180
|
-
findTableReferences(ast, adapter, sourceCode, config) {
|
|
212
|
+
findTableReferences(ast, adapter, sourceCode, config, provenanceContext) {
|
|
181
213
|
const references = [];
|
|
182
214
|
const sqlTags = config.sqlTagNames ?? ['sql', 'db'];
|
|
183
|
-
const dbMethods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first', 'query'];
|
|
184
|
-
const dbReceivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
|
|
185
215
|
// (1) Tagged template SQL — e.g. sql`SELECT * FROM heroes`
|
|
216
|
+
// This is a syntax feature, not a naming convention — keep the sqlTagNames gate.
|
|
186
217
|
const taggedTemplates = adapter.findNodes(ast, {
|
|
187
218
|
custom: (node) => {
|
|
188
219
|
if (node.type !== 'call_expression')
|
|
@@ -204,14 +235,21 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
204
235
|
references.push(...tableRefs);
|
|
205
236
|
}
|
|
206
237
|
// (2) DB-call patterns — e.g. db.exec("SELECT * FROM heroes")
|
|
238
|
+
// Spec 21: Replace name-based isDbMemberCall with provenance-based isDBProvenanced.
|
|
207
239
|
const dbCalls = adapter.findNodes(ast, {
|
|
208
240
|
custom: (node) => {
|
|
209
241
|
if (node.type !== 'call_expression')
|
|
210
242
|
return false;
|
|
243
|
+
// Spec 21: Use provenance when available, fall back to name-based check
|
|
244
|
+
if (provenanceContext && provenanceContext.mode !== 'names') {
|
|
245
|
+
return isDBProvenanced(node, adapter, sourceCode, provenanceContext, DB_CALL_METHODS);
|
|
246
|
+
}
|
|
247
|
+
// Legacy name-based check for names mode / no context
|
|
211
248
|
const callee = this.getCallee(node, adapter, sourceCode);
|
|
212
249
|
if (!callee)
|
|
213
250
|
return false;
|
|
214
|
-
|
|
251
|
+
const dbMethods = config.dbCallMethods ?? ['exec', 'prepare', 'batch', 'run', 'all', 'first', 'query'];
|
|
252
|
+
const dbReceivers = config.dbReceiverNames ?? ['db', 'database', 'sql', 'stmt'];
|
|
215
253
|
return this.isDbMemberCall(node, callee, dbMethods, dbReceivers, adapter, sourceCode);
|
|
216
254
|
},
|
|
217
255
|
});
|
|
@@ -230,8 +268,67 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
230
268
|
const fileRefs = this.parseSqlTables(sourceCode, { line: 1, column: 1 }, sourceCode);
|
|
231
269
|
references.push(...fileRefs);
|
|
232
270
|
}
|
|
271
|
+
// (4) Spec 15 R2 — ORM-aware extraction (Drizzle + Prisma)
|
|
272
|
+
// Run ORM adapter extraction for files that match a registered adapter.
|
|
273
|
+
// This complements raw-SQL extraction by picking up ORM-specific patterns
|
|
274
|
+
// like db.select().from(users) and prisma.user.findMany().
|
|
275
|
+
const ormRegistry = OrmAdapterRegistry.getInstance();
|
|
276
|
+
const ormAdapter = ormRegistry.getAdapterForFile(ast.filePath);
|
|
277
|
+
if (ormAdapter) {
|
|
278
|
+
try {
|
|
279
|
+
const ormRefs = ormAdapter.extractTableReferences(ast, adapter, sourceCode);
|
|
280
|
+
for (const ormRef of ormRefs) {
|
|
281
|
+
references.push({
|
|
282
|
+
table: ormRef.table,
|
|
283
|
+
type: ormRef.type,
|
|
284
|
+
location: ormRef.location,
|
|
285
|
+
context: ormRef.context,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// ORM extraction is best-effort — failures don't block raw-SQL extraction.
|
|
291
|
+
}
|
|
292
|
+
}
|
|
233
293
|
return references;
|
|
234
294
|
}
|
|
295
|
+
/**
|
|
296
|
+
* Spec 15 R1 — Record extracted table references to schema_usage for
|
|
297
|
+
* cross-domain lifecycle analysis (written-never-read, read-never-written,
|
|
298
|
+
* transaction-boundary risk).
|
|
299
|
+
*
|
|
300
|
+
* Idempotent per-file: stale entries are cleared before fresh references
|
|
301
|
+
* are inserted. For .sql/migration files, uses "schema-file" as the
|
|
302
|
+
* function name since there's no AST function context.
|
|
303
|
+
*/
|
|
304
|
+
recordTableUsage(ast, adapter, filePath, references) {
|
|
305
|
+
try {
|
|
306
|
+
const db = CodeIndexDB.getInstance();
|
|
307
|
+
db.clearSchemaUsageForFile(filePath);
|
|
308
|
+
for (const ref of references) {
|
|
309
|
+
// Find enclosing function from the AST position
|
|
310
|
+
const node = this.findClosestNodeAt(ast.root, ref.location, adapter);
|
|
311
|
+
const functionName = node
|
|
312
|
+
? this.findEnclosingFunctionName(node, adapter)
|
|
313
|
+
: ast.filePath.endsWith('.sql') || ast.filePath.includes('/migrations/')
|
|
314
|
+
? 'schema-file'
|
|
315
|
+
: 'top-level';
|
|
316
|
+
const usage = {
|
|
317
|
+
tableName: ref.table,
|
|
318
|
+
filePath,
|
|
319
|
+
functionName,
|
|
320
|
+
usageType: ref.type,
|
|
321
|
+
line: ref.location.line,
|
|
322
|
+
column: ref.location.column,
|
|
323
|
+
rawQuery: ref.context,
|
|
324
|
+
};
|
|
325
|
+
db.recordSchemaUsage(usage);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// Schema recording is best-effort — failures don't block analysis.
|
|
330
|
+
}
|
|
331
|
+
}
|
|
235
332
|
/**
|
|
236
333
|
* Parse SQL table names from a SQL text string.
|
|
237
334
|
* R2.3: Template expressions (${...}) resolve portions to wildcards.
|
|
@@ -241,14 +338,16 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
241
338
|
// R2.3: Strip template expressions — `${prefix}_builds` → `_builds`
|
|
242
339
|
// (the prefix is replaced with empty, the suffix remains for matching)
|
|
243
340
|
const cleaned = this.resolveTemplateExpressions(sqlText);
|
|
244
|
-
// SQL patterns anchored to SQL keywords (not arbitrary substrings)
|
|
341
|
+
// SQL patterns anchored to SQL keywords (not arbitrary substrings).
|
|
342
|
+
// Uses Unicode-aware \p{L} so non-Latin table names (日, 注文, пользователи)
|
|
343
|
+
// are correctly matched — \w is ASCII-only. Spec 21 R5.
|
|
245
344
|
const sqlPatterns = [
|
|
246
|
-
{ regex: /\bFROM\s+([`"']?)([
|
|
247
|
-
{ regex: /\bJOIN\s+([`"']?)([
|
|
248
|
-
{ regex: /\bINSERT\s+INTO\s+([`"']?)([
|
|
249
|
-
{ regex: /\bUPDATE\s+([`"']?)([
|
|
250
|
-
{ regex: /\bDELETE\s+FROM\s+([`"']?)([
|
|
251
|
-
{ regex: /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"']?)([
|
|
345
|
+
{ regex: /\bFROM\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'select' },
|
|
346
|
+
{ regex: /\bJOIN\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'select' },
|
|
347
|
+
{ regex: /\bINSERT\s+INTO\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'insert' },
|
|
348
|
+
{ regex: /\bUPDATE\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'update' },
|
|
349
|
+
{ regex: /\bDELETE\s+FROM\s+([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'delete' },
|
|
350
|
+
{ regex: /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([`"']?)([\p{L}_][\p{L}\p{N}_]*)\1\b/giu, type: 'create' },
|
|
252
351
|
];
|
|
253
352
|
for (const { regex, type } of sqlPatterns) {
|
|
254
353
|
let match;
|
|
@@ -334,9 +433,9 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
334
433
|
findColumnReferences(ast, adapter, sourceCode) {
|
|
335
434
|
const references = [];
|
|
336
435
|
const patterns = [
|
|
337
|
-
/(\
|
|
338
|
-
/SELECT\s+.*?(\
|
|
339
|
-
/WHERE\s+.*?(\
|
|
436
|
+
/([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)\s*[=<>]/giu,
|
|
437
|
+
/SELECT\s+.*?([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)/giu,
|
|
438
|
+
/WHERE\s+.*?([\p{L}\p{N}_]+)\.([\p{L}\p{N}_]+)/giu,
|
|
340
439
|
];
|
|
341
440
|
for (const pattern of patterns) {
|
|
342
441
|
let match;
|
|
@@ -363,11 +462,11 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
363
462
|
const violations = [];
|
|
364
463
|
for (const ref of references) {
|
|
365
464
|
if (/[A-Z]/.test(ref.table) && !ref.table.endsWith('Table')) {
|
|
366
|
-
violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' should use snake_case convention`, 'suggestion', 'naming-convention'));
|
|
465
|
+
violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' should use snake_case convention`, 'suggestion', 'naming-convention', undefined, ref.table));
|
|
367
466
|
}
|
|
368
467
|
const reserved = ['user', 'order', 'group', 'table', 'column', 'index'];
|
|
369
468
|
if (reserved.includes(ref.table.toLowerCase())) {
|
|
370
|
-
violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' is a reserved word. Consider using a different name.`, 'warning', 'reserved-word'));
|
|
469
|
+
violations.push(this.createViolation(filePath, ref.location, `Table name '${ref.table}' is a reserved word. Consider using a different name.`, 'warning', 'reserved-word', undefined, ref.table));
|
|
371
470
|
}
|
|
372
471
|
}
|
|
373
472
|
return violations;
|
|
@@ -385,30 +484,43 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
385
484
|
const funcText = adapter.getNodeText(funcNode, sourceCode);
|
|
386
485
|
const queryCount = this.countQueries(funcText);
|
|
387
486
|
if (queryCount > (config.maxQueriesPerFunction || 5)) {
|
|
388
|
-
violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' has ${queryCount} queries, exceeding the maximum of ${config.maxQueriesPerFunction}`, 'warning', 'too-many-queries'));
|
|
487
|
+
violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' has ${queryCount} queries, exceeding the maximum of ${config.maxQueriesPerFunction}`, 'warning', 'too-many-queries', undefined, func.name));
|
|
389
488
|
}
|
|
390
489
|
}
|
|
391
|
-
// N+1 query
|
|
392
|
-
|
|
393
|
-
violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'Potential N+1 query pattern detected. Consider using a join or batch query.', 'warning', 'n-plus-one'));
|
|
394
|
-
}
|
|
490
|
+
// N+1 query detection is handled by the data-access analyzer (loop-query rule).
|
|
491
|
+
// The two were consolidated in Spec-19 Corrective Batch Item 3 — see CHANGELOG.
|
|
395
492
|
return violations;
|
|
396
493
|
}
|
|
397
494
|
// ---------------------------------------------------------------------------
|
|
398
495
|
// SQL injection — R7: severity changed from critical to warning
|
|
399
496
|
// ---------------------------------------------------------------------------
|
|
497
|
+
// ---------------------------------------------------------------------------
|
|
498
|
+
// SQL injection — per-call-site with enclosing-function + ordinal symbols
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
400
500
|
checkSQLInjection(ast, adapter, sourceCode) {
|
|
401
501
|
const violations = [];
|
|
502
|
+
const symbolOrdinals = new Map();
|
|
503
|
+
// Use regex with global flag to find individual call sites
|
|
402
504
|
const dangerousPatterns = [
|
|
403
505
|
/query\s*\(\s*`[^`]*\$\{[^}]+\}[^`]*`/g,
|
|
404
506
|
/query\s*\(\s*['"][^'"]*['"]?\s*\+/g,
|
|
405
507
|
/execute\s*\(\s*['"][^'"]*['"]?\s*\+/g,
|
|
406
508
|
];
|
|
407
509
|
for (const pattern of dangerousPatterns) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
510
|
+
// Clone regex to reset state (global regexes track lastIndex)
|
|
511
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
512
|
+
let match;
|
|
513
|
+
while ((match = re.exec(sourceCode)) !== null) {
|
|
514
|
+
const location = this.offsetToLocation(sourceCode, match.index, { line: 1, column: 1 });
|
|
515
|
+
// Find enclosing function from the AST at this position
|
|
516
|
+
const node = this.findClosestNodeAt(ast.root, location, adapter);
|
|
517
|
+
const enclosingFn = node ? this.findEnclosingFunctionName(node, adapter) : 'top-level';
|
|
518
|
+
const baseSymbol = `${enclosingFn}:sql-injection`;
|
|
519
|
+
const ordinal = (symbolOrdinals.get(baseSymbol) ?? 0) + 1;
|
|
520
|
+
symbolOrdinals.set(baseSymbol, ordinal);
|
|
521
|
+
const symbol = ordinal > 1 ? `${baseSymbol}:${ordinal}` : baseSymbol;
|
|
522
|
+
violations.push(this.createViolation(ast.filePath, location, 'Potential SQL injection vulnerability. Use parameterized queries.', 'suggestion', // Spec 11 R4 blanket demotion: all survivors → suggestion
|
|
523
|
+
'sql-injection', undefined, symbol));
|
|
412
524
|
}
|
|
413
525
|
}
|
|
414
526
|
return violations;
|
|
@@ -599,6 +711,85 @@ export class UniversalSchemaAnalyzer extends UniversalAnalyzer {
|
|
|
599
711
|
}
|
|
600
712
|
return null;
|
|
601
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Find the nearest AST node at a source location — walks the tree looking
|
|
716
|
+
* for the deepest node that contains the given line/column.
|
|
717
|
+
*/
|
|
718
|
+
findClosestNodeAt(root, location, adapter) {
|
|
719
|
+
let best = null;
|
|
720
|
+
let bestDepth = -1;
|
|
721
|
+
const walk = (node, depth) => {
|
|
722
|
+
const start = node.location.start;
|
|
723
|
+
const end = node.location.end;
|
|
724
|
+
// Check if node contains the location
|
|
725
|
+
if ((start.line < location.line ||
|
|
726
|
+
(start.line === location.line && start.column <= location.column)) &&
|
|
727
|
+
(end.line > location.line ||
|
|
728
|
+
(end.line === location.line && end.column >= location.column))) {
|
|
729
|
+
if (depth > bestDepth) {
|
|
730
|
+
best = node;
|
|
731
|
+
bestDepth = depth;
|
|
732
|
+
}
|
|
733
|
+
if (node.children) {
|
|
734
|
+
for (const child of node.children) {
|
|
735
|
+
walk(child, depth + 1);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
walk(root, 0);
|
|
741
|
+
return best;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Walk up the AST from a node to find the enclosing function or method name.
|
|
745
|
+
* Matches the same scheme as UniversalDataAccessAnalyzer.findEnclosingFunctionName.
|
|
746
|
+
*/
|
|
747
|
+
findEnclosingFunctionName(node, adapter) {
|
|
748
|
+
let current = node;
|
|
749
|
+
while (current) {
|
|
750
|
+
const type = adapter.getNodeType(current);
|
|
751
|
+
if (type === 'arrow_function' ||
|
|
752
|
+
type === 'function_declaration' ||
|
|
753
|
+
type === 'function_expression' ||
|
|
754
|
+
type === 'generator_function_declaration' ||
|
|
755
|
+
type === 'generator_function_expression' ||
|
|
756
|
+
type === 'method_definition') {
|
|
757
|
+
const name = this.getNodeName(current, adapter);
|
|
758
|
+
if (name)
|
|
759
|
+
return name;
|
|
760
|
+
}
|
|
761
|
+
if (adapter.isMethod(current)) {
|
|
762
|
+
const name = this.getNodeName(current, adapter);
|
|
763
|
+
if (name)
|
|
764
|
+
return name;
|
|
765
|
+
}
|
|
766
|
+
current = adapter.getParent(current);
|
|
767
|
+
}
|
|
768
|
+
return 'top-level';
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Extract a human-readable name from an AST node.
|
|
772
|
+
* Matches the same scheme as UniversalDataAccessAnalyzer.getNodeName.
|
|
773
|
+
*/
|
|
774
|
+
getNodeName(node, adapter) {
|
|
775
|
+
if (node.name && typeof node.name === 'string') {
|
|
776
|
+
return node.name;
|
|
777
|
+
}
|
|
778
|
+
if (node.text && typeof node.text === 'string') {
|
|
779
|
+
return node.text;
|
|
780
|
+
}
|
|
781
|
+
if (node.children) {
|
|
782
|
+
for (const child of node.children) {
|
|
783
|
+
const childType = adapter.getNodeType(child);
|
|
784
|
+
if (childType === 'identifier' || childType === 'property_identifier') {
|
|
785
|
+
const name = this.getNodeName(child, adapter);
|
|
786
|
+
if (name)
|
|
787
|
+
return name;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return '';
|
|
792
|
+
}
|
|
602
793
|
// ---------------------------------------------------------------------------
|
|
603
794
|
// JSON Schema validation (unchanged from original)
|
|
604
795
|
// ---------------------------------------------------------------------------
|