code-auditor-mcp 5.0.2 → 5.3.3
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 +326 -3
- package/README.md +24 -2
- package/configs/hhra-compat.json +12 -118
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +11 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +57 -52
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +9 -4
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +8 -25
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +35 -14
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.js +4 -4
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +1 -1
- package/dist/analyzers/ruleTiming.js +2 -2
- package/dist/analyzers/ruleTiming.js.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +22 -26
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +0 -2
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +4 -5
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +32 -15
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +189 -115
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +0 -2
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +0 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -10
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +7 -49
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +51 -3
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/config.js +0 -2
- package/dist/analyzers/universal/schema/config.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +0 -2
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/vendorSurfaces.d.ts +126 -0
- package/dist/analyzers/vendorSurfaces.d.ts.map +1 -0
- package/dist/analyzers/vendorSurfaces.js +580 -0
- package/dist/analyzers/vendorSurfaces.js.map +1 -0
- package/dist/auditRunner.d.ts +9 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +198 -146
- package/dist/auditRunner.js.map +1 -1
- package/dist/cacheGc.d.ts +88 -0
- package/dist/cacheGc.d.ts.map +1 -0
- package/dist/cacheGc.js +196 -0
- package/dist/cacheGc.js.map +1 -0
- package/dist/churn/churnExtractor.d.ts +0 -1
- package/dist/churn/churnExtractor.d.ts.map +1 -1
- package/dist/churn/churnExtractor.js +2 -4
- package/dist/churn/churnExtractor.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +260 -47
- package/dist/cli.js.map +1 -1
- package/dist/codeIndex/migrations.d.ts.map +1 -1
- package/dist/codeIndex/migrations.js +4 -3
- package/dist/codeIndex/migrations.js.map +1 -1
- package/dist/codeIndexDB.d.ts +11 -2
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +27 -2
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts +60 -7
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +252 -60
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +137 -43
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +174 -101
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +1 -1
- package/dist/config/effectiveConfig.d.ts.map +1 -1
- package/dist/config/effectiveConfig.js +4 -6
- package/dist/config/effectiveConfig.js.map +1 -1
- package/dist/config/pathProfiles.d.ts +4 -3
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +3 -2
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -1
- package/dist/daemon/main.d.ts +0 -1
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +11 -4
- package/dist/daemon/main.js.map +1 -1
- package/dist/dataPaths.d.ts +32 -9
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +69 -16
- package/dist/dataPaths.js.map +1 -1
- package/dist/hookLog.d.ts +54 -0
- package/dist/hookLog.d.ts.map +1 -0
- package/dist/hookLog.js +105 -0
- package/dist/hookLog.js.map +1 -0
- package/dist/hookLogPath.generated.d.ts +3 -0
- package/dist/hookLogPath.generated.d.ts.map +1 -0
- package/dist/hookLogPath.generated.js +8 -0
- package/dist/hookLogPath.generated.js.map +1 -0
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -7
- package/dist/index.js.map +1 -1
- package/dist/installConfig.d.ts +15 -2
- package/dist/installConfig.d.ts.map +1 -1
- package/dist/installConfig.js +17 -2
- package/dist/installConfig.js.map +1 -1
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +2 -1
- package/dist/installer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +11 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +11 -0
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/types.d.ts +8 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +13 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +135 -18
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +7 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +7 -0
- package/dist/ledger.js.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +8 -8
- package/dist/mcp-tools/workflowGuide.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +5 -2
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.d.ts +5 -0
- package/dist/mcp-ui-simple.d.ts.map +1 -1
- package/dist/mcp-ui-simple.js +9 -2
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.d.ts +0 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +36 -33
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts +10 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +10 -1
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.d.ts.map +1 -1
- package/dist/mcpAutoIndex.js +2 -1
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpDiagnostics.d.ts +2 -0
- package/dist/mcpDiagnostics.d.ts.map +1 -1
- package/dist/mcpDiagnostics.js +2 -0
- package/dist/mcpDiagnostics.js.map +1 -1
- package/dist/phase/candidates.d.ts +53 -0
- package/dist/phase/candidates.d.ts.map +1 -0
- package/dist/phase/candidates.js +67 -0
- package/dist/phase/candidates.js.map +1 -0
- package/dist/phase/colorMath.d.ts +18 -5
- package/dist/phase/colorMath.d.ts.map +1 -1
- package/dist/phase/colorMath.js +88 -5
- package/dist/phase/colorMath.js.map +1 -1
- package/dist/phase/dataAccessCallsCandidates.js +1 -1
- package/dist/phase/dataAccessCallsCandidates.js.map +1 -1
- package/dist/phase/goFunctionAnalysis.d.ts +2 -2
- package/dist/phase/goFunctionAnalysis.js +2 -2
- package/dist/phase/goFunctions.d.ts +2 -2
- package/dist/phase/goFunctions.js +2 -2
- package/dist/phase/goImports.d.ts +2 -2
- package/dist/phase/goImports.js +2 -2
- package/dist/phase/goSwitches.d.ts +2 -2
- package/dist/phase/goSwitches.js +2 -2
- package/dist/phase/imports.d.ts.map +1 -1
- package/dist/phase/imports.js +1 -0
- package/dist/phase/imports.js.map +1 -1
- package/dist/phase/loopQueryCandidates.js +1 -1
- package/dist/phase/loopQueryCandidates.js.map +1 -1
- package/dist/phase/oracles.d.ts +28 -0
- package/dist/phase/oracles.d.ts.map +1 -1
- package/dist/phase/oracles.js +28 -0
- package/dist/phase/oracles.js.map +1 -1
- package/dist/phase/phaseModel.d.ts +47 -8
- package/dist/phase/phaseModel.d.ts.map +1 -1
- package/dist/phase/phaseModel.js +44 -8
- package/dist/phase/phaseModel.js.map +1 -1
- package/dist/phase/producers.d.ts.map +1 -1
- package/dist/phase/producers.js +24 -17
- package/dist/phase/producers.js.map +1 -1
- package/dist/phase/reachability.d.ts +44 -1
- package/dist/phase/reachability.d.ts.map +1 -1
- package/dist/phase/reachability.js +79 -0
- package/dist/phase/reachability.js.map +1 -1
- package/dist/phase/rules/conventions.d.ts.map +1 -1
- package/dist/phase/rules/conventions.js +6 -2
- package/dist/phase/rules/conventions.js.map +1 -1
- package/dist/phase/rules/crossDomain.d.ts.map +1 -1
- package/dist/phase/rules/crossDomain.js +16 -6
- package/dist/phase/rules/crossDomain.js.map +1 -1
- package/dist/phase/rules/dataAccess.d.ts.map +1 -1
- package/dist/phase/rules/dataAccess.js +13 -0
- package/dist/phase/rules/dataAccess.js.map +1 -1
- package/dist/phase/rules/documentation.d.ts.map +1 -1
- package/dist/phase/rules/documentation.js +11 -1
- package/dist/phase/rules/documentation.js.map +1 -1
- package/dist/phase/rules/dry.d.ts +16 -7
- package/dist/phase/rules/dry.d.ts.map +1 -1
- package/dist/phase/rules/dry.js +51 -32
- package/dist/phase/rules/dry.js.map +1 -1
- package/dist/phase/rules/goRules.d.ts +2 -2
- package/dist/phase/rules/goRules.js +2 -2
- package/dist/phase/rules/secrets.d.ts +22 -8
- package/dist/phase/rules/secrets.d.ts.map +1 -1
- package/dist/phase/rules/secrets.js +75 -69
- package/dist/phase/rules/secrets.js.map +1 -1
- package/dist/phase/rules/styles.d.ts +23 -9
- package/dist/phase/rules/styles.d.ts.map +1 -1
- package/dist/phase/rules/styles.js +61 -15
- package/dist/phase/rules/styles.js.map +1 -1
- package/dist/phase/rules/unreferencedModule.d.ts.map +1 -1
- package/dist/phase/rules/unreferencedModule.js +32 -10
- package/dist/phase/rules/unreferencedModule.js.map +1 -1
- package/dist/phase/runner.d.ts +16 -7
- package/dist/phase/runner.d.ts.map +1 -1
- package/dist/phase/runner.js +32 -20
- package/dist/phase/runner.js.map +1 -1
- package/dist/phase/stringLiterals.d.ts.map +1 -1
- package/dist/phase/stringLiterals.js +16 -7
- package/dist/phase/stringLiterals.js.map +1 -1
- package/dist/phase/stylesCss.d.ts.map +1 -1
- package/dist/phase/stylesCss.js +4 -0
- package/dist/phase/stylesCss.js.map +1 -1
- package/dist/phase/stylesMarkup.d.ts.map +1 -1
- package/dist/phase/stylesMarkup.js +4 -0
- package/dist/phase/stylesMarkup.js.map +1 -1
- package/dist/phase/stylesSource.d.ts.map +1 -1
- package/dist/phase/stylesSource.js +6 -0
- package/dist/phase/stylesSource.js.map +1 -1
- package/dist/phase/testProgram.d.ts +57 -0
- package/dist/phase/testProgram.d.ts.map +1 -0
- package/dist/phase/testProgram.js +244 -0
- package/dist/phase/testProgram.js.map +1 -0
- package/dist/phase/typeDeclarations.d.ts +2 -2
- package/dist/phase/typeDeclarations.js +2 -2
- package/dist/phase/types.d.ts +104 -0
- package/dist/phase/types.d.ts.map +1 -1
- package/dist/pipeline.d.ts +4 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +4 -0
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +1 -171
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +47 -1281
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +0 -6
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +0 -4
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +0 -1
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +0 -7
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/services/auditJobService.d.ts +15 -0
- package/dist/services/auditJobService.d.ts.map +1 -1
- package/dist/services/auditJobService.js +15 -0
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/projectTaskHelpers.d.ts +7 -0
- package/dist/services/projectTaskHelpers.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js +7 -0
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts +28 -0
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/services/whitelistService.js +28 -0
- package/dist/services/whitelistService.js.map +1 -1
- package/dist/sqlite/driver.d.ts +17 -1
- package/dist/sqlite/driver.d.ts.map +1 -1
- package/dist/sqlite/driver.js +28 -5
- package/dist/sqlite/driver.js.map +1 -1
- package/dist/styles/cssInJsHarvest.d.ts +36 -0
- package/dist/styles/cssInJsHarvest.d.ts.map +1 -0
- package/dist/styles/cssInJsHarvest.js +78 -0
- package/dist/styles/cssInJsHarvest.js.map +1 -0
- package/dist/styles/definedClasses.d.ts +95 -0
- package/dist/styles/definedClasses.d.ts.map +1 -0
- package/dist/styles/definedClasses.js +129 -0
- package/dist/styles/definedClasses.js.map +1 -0
- package/dist/styles/markupStyleHarvest.d.ts +33 -0
- package/dist/styles/markupStyleHarvest.d.ts.map +1 -0
- package/dist/styles/markupStyleHarvest.js +73 -0
- package/dist/styles/markupStyleHarvest.js.map +1 -0
- package/dist/styles/styleExtractor.d.ts +37 -0
- package/dist/styles/styleExtractor.d.ts.map +1 -1
- package/dist/styles/styleExtractor.js +89 -33
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +11 -10
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindExpander.d.ts.map +1 -1
- package/dist/styles/tailwindExpander.js +31 -10
- package/dist/styles/tailwindExpander.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts +4 -0
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +4 -0
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/telemetry.d.ts +67 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +100 -20
- package/dist/telemetry.js.map +1 -1
- package/dist/types/crossLanguage.d.ts +1 -0
- package/dist/types/crossLanguage.d.ts.map +1 -1
- package/dist/types.d.ts +73 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/examples/.auditrc.example.json +7 -22
- package/examples/.auditrc.json +12 -155
- package/examples/.code-auditor.json +8 -51
- package/examples/nextjs.auditrc.json +7 -34
- package/examples/node-api.auditrc.json +6 -46
- package/examples/react.auditrc.json +8 -25
- package/examples/tolerant-solid.auditrc.json +12 -44
- package/package.json +8 -5
- package/plugin/.claude-plugin/manifest.json +10 -9
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/hooks/hooks.json +4 -4
- package/plugin/scripts/hook-audit.sh +25 -10
- package/plugin/scripts/hook-common.sh +12 -7
- package/plugin/scripts/hook-drift-check.sh +10 -1
- package/plugin/scripts/hook-log-path.sh +7 -0
- package/plugin/scripts/hook-self-audit.sh +21 -6
- package/plugin/scripts/hook-warm.sh +17 -11
- package/plugin/skills/code-auditor/SKILL.md +26 -3
- package/server.json +2 -2
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +0 -6
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js +0 -12
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +0 -1
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +0 -7
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js +0 -13
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.d.ts +0 -14
- package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.js +0 -16
- package/dist/__tests__/fixtures/spec-52/item-01-prepare-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.d.ts +0 -15
- package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.js +0 -17
- package/dist/__tests__/fixtures/spec-52/item-02-prepare-bind-accumulate-batch.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts +0 -14
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js +0 -19
- package/dist/__tests__/fixtures/spec-52/item-03-exec-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts +0 -18
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js +0 -19
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts +0 -19
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.js +0 -19
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts +0 -17
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js +0 -19
- package/dist/__tests__/fixtures/spec-52/item-06-chained-prepare-run-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts +0 -25
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js +0 -21
- package/dist/__tests__/fixtures/spec-52/item-07-chained-prepare-bind-first-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.d.ts +0 -15
- package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.js +0 -19
- package/dist/__tests__/fixtures/spec-52/item-08-promise-all-of-statements-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.d.ts +0 -14
- package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.js +0 -14
- package/dist/__tests__/fixtures/spec-52/item-09-promise-all-map-of-statements-in-loop.js.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts +0 -15
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.d.ts.map +0 -1
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js +0 -16
- package/dist/__tests__/fixtures/spec-52/item-10-promise-all-map-of-eager-all-in-loop.js.map +0 -1
- package/dist/languages/go/analyzer-darwin-amd64 +0 -0
- package/dist/languages/go/analyzer-darwin-arm64 +0 -0
- package/dist/languages/go/analyzer-linux-amd64 +0 -0
- package/dist/languages/go/analyzer-linux-arm64 +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +0 -643
- package/dist/languages/go/analyzer-src/dataaccess.go +0 -388
- package/dist/languages/go/analyzer-src/go.mod +0 -3
- package/dist/languages/go/analyzer-src/indexer.go +0 -268
- package/dist/languages/go/analyzer-src/parser.go +0 -461
- package/dist/languages/go/analyzer-src/solid.go +0 -311
- package/dist/languages/go/analyzer-src/testconventions.go +0 -25
- package/dist/languages/go/analyzer-src/types.go +0 -156
- package/dist/languages/go/analyzer-windows-amd64.exe +0 -0
- package/dist/languages/go/go.mod +0 -7
- package/dist/languages/go/main.go +0 -203
- package/dist/native/ast-grep-napi.darwin-arm64.node +0 -0
- package/dist/native/ast-grep-napi.darwin-x64.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-arm64-musl.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-gnu.node +0 -0
- package/dist/native/ast-grep-napi.linux-x64-musl.node +0 -0
- package/dist/native/ast-grep-napi.win32-x64-msvc.node +0 -0
- package/dist/native-bootstrap.d.ts +0 -15
- package/dist/native-bootstrap.d.ts.map +0 -1
- package/dist/native-bootstrap.js +0 -75
- package/dist/native-bootstrap.js.map +0 -1
|
@@ -15,6 +15,13 @@ import { findEnclosingFunctionIdentity, functionIdentityLabel } from './schema/c
|
|
|
15
15
|
import { buildOrgFilterTierSet, tableRequiresOrgFilter, DEFAULT_ORG_PREDICATE_PATTERNS, hasOrganizationFilter, } from '../orgFilterTiers.js';
|
|
16
16
|
import { resolveWhereBinding } from '../../phase/localBinding.js';
|
|
17
17
|
import { parseSql, DEFAULT_SQL_DIALECT, extractTableNames, whereFacts, isWriteStatement, isMassWriteStatement, isUpsertStatement, whereColumnRefs, isDdlStatementKind, } from '../../languages/sql/sqlAst.js';
|
|
18
|
+
// Item B (reissued) — vendor surfaces are resolved per-run from the tree under
|
|
19
|
+
// analysis (its own node_modules), never from a committed snapshot. The method
|
|
20
|
+
// sets below derive from those resolved surfaces via `deriveVendorSets`; a vendor
|
|
21
|
+
// whose declaration cannot be resolved contributes nothing (the honest `unproven`
|
|
22
|
+
// state), recorded with its reason on the `VendorSurfaces.resolutions` records.
|
|
23
|
+
// See specs/vendor-replica-probes.md for the reducible/irreducible mapping.
|
|
24
|
+
import { deriveVendorSets, EMPTY_VENDOR_SURFACES, } from '../vendorSurfaces.js';
|
|
18
25
|
/**
|
|
19
26
|
* SQL keywords recognized as evidence that a *dynamically-constructed* string is
|
|
20
27
|
* an injection surface — the host-language dataflow gate in `checkQuerySecurity`
|
|
@@ -30,7 +37,6 @@ const SQL_KEYWORDS = [
|
|
|
30
37
|
'CREATE', 'DROP', 'ALTER', 'TRUNCATE',
|
|
31
38
|
];
|
|
32
39
|
export const DEFAULT_DATA_ACCESS_CONFIG = {
|
|
33
|
-
checkOrgFilters: true,
|
|
34
40
|
checkSQLInjection: true,
|
|
35
41
|
skipTestFiles: true,
|
|
36
42
|
organizationPatterns: [...DEFAULT_ORG_PREDICATE_PATTERNS],
|
|
@@ -47,17 +53,10 @@ export const DEFAULT_DATA_ACCESS_CONFIG = {
|
|
|
47
53
|
orm: [
|
|
48
54
|
/table\s*[:=]\s*["'`]?([\p{L}\p{N}_]+)["'`]?/giu
|
|
49
55
|
],
|
|
50
|
-
queryBuilder: [/\.from\s*\(\s*["'`]?([\p{L}\p{N}_]+)["'`]?\s*\)/giu]
|
|
51
56
|
},
|
|
52
57
|
performanceThresholds: {
|
|
53
|
-
complexQueryCount: 3,
|
|
54
|
-
unfilteredQueryCount: 3,
|
|
55
58
|
joinedTableCount: 4
|
|
56
59
|
},
|
|
57
|
-
securityPatterns: {
|
|
58
|
-
sqlInjectionRisks: ['${', 'concat', 'string interpolation'],
|
|
59
|
-
parameterizedQueries: ['?', ':param', '$1', 'prepared', 'parameterized']
|
|
60
|
-
},
|
|
61
60
|
// DB wrapper functions that accept (sql, params) — same as .prepare().bind()
|
|
62
61
|
// but expressed as a simple function call. Must match the provenance system's
|
|
63
62
|
// dbWrapperNames so the FP guards see the same capability list the detector does.
|
|
@@ -168,12 +167,15 @@ function hasChainCompanion(methods) {
|
|
|
168
167
|
}
|
|
169
168
|
return false;
|
|
170
169
|
}
|
|
171
|
-
/**
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
/**
|
|
171
|
+
* The reason a Prisma object-form site is `unproven`: the analyzed tree has no
|
|
172
|
+
* generated `.prisma/client`, so the model verb set (the union of the generated
|
|
173
|
+
* `*Delegate` members) cannot be derived and the verb cannot be confirmed. Item B
|
|
174
|
+
* (reissued): the verb set is no longer a committed `PRISMA_VERBS` snapshot — it
|
|
175
|
+
* is resolved per-run from the tree's own generated client, and its absence is
|
|
176
|
+
* this `unproven` reason, never a stale fallback.
|
|
177
|
+
*/
|
|
178
|
+
const PRISMA_NOT_PRESENT_REASON = 'generated Prisma client `.prisma/client` is not present in the analyzed tree — the model verb set cannot be confirmed (run `prisma generate`)';
|
|
177
179
|
/** Collect the property keys of an object literal, including its nested objects. */
|
|
178
180
|
function collectObjectLiteralKeys(node, adapter, sourceCode) {
|
|
179
181
|
const keys = [];
|
|
@@ -197,29 +199,44 @@ function collectObjectLiteralKeys(node, adapter, sourceCode) {
|
|
|
197
199
|
* object-literal argument carries a `where` or `data` key. Option 1 (chained
|
|
198
200
|
* companions) misses this because the `where`/`data` are keys inside an object
|
|
199
201
|
* literal, not chained calls (blitz `prisma.user.update({ where, data })`).
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
*
|
|
203
|
+
* Tri-state (Item B, reissued): the verb is confirmed against the *derived*
|
|
204
|
+
* Prisma verb set (`vendor.prismaVerbs`), not a committed snapshot. When the
|
|
205
|
+
* shape matches (`<recv>.<Model>.<verb>` + `where`/`data` key) but the generated
|
|
206
|
+
* client is absent — so no verb set was derived — the verdict is `prisma-unproven`
|
|
207
|
+
* with {@link PRISMA_NOT_PRESENT_REASON}, never `not-prisma` (which would silently
|
|
208
|
+
* drop the site) and never a stale verb list.
|
|
209
|
+
*/
|
|
210
|
+
function isPrismaObjectForm(node, adapter, sourceCode, prismaVerbs) {
|
|
202
211
|
const callee = getCallExpressionCallee(node, adapter);
|
|
203
212
|
if (!callee || adapter.getNodeType(callee) !== 'member_expression')
|
|
204
|
-
return
|
|
213
|
+
return { kind: 'not-prisma' };
|
|
205
214
|
// The chain must be `<recv>.<Model>.<verb>` — at least two member segments
|
|
206
215
|
// (the Model and the verb) read from their property nodes. A subscript like
|
|
207
216
|
// `obj["a.b"]` is not a Model segment, so `obj["a.b"].update(…)` has only one
|
|
208
217
|
// segment and fails.
|
|
209
218
|
const methods = collectChainMethods(callee, adapter, sourceCode);
|
|
210
219
|
if (methods.length < 2)
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
return false;
|
|
220
|
+
return { kind: 'not-prisma' };
|
|
221
|
+
const verb = methods[methods.length - 1];
|
|
214
222
|
const args = adapter.getChildren(node).find((c) => adapter.getNodeType(c) === 'arguments');
|
|
215
223
|
if (!args)
|
|
216
|
-
return
|
|
224
|
+
return { kind: 'not-prisma' };
|
|
225
|
+
let hasWhereOrData = false;
|
|
217
226
|
for (const arg of adapter.getChildren(args)) {
|
|
218
227
|
const keys = collectObjectLiteralKeys(arg, adapter, sourceCode);
|
|
219
|
-
if (keys.includes('where') || keys.includes('data'))
|
|
220
|
-
|
|
228
|
+
if (keys.includes('where') || keys.includes('data')) {
|
|
229
|
+
hasWhereOrData = true;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
221
232
|
}
|
|
222
|
-
|
|
233
|
+
if (!hasWhereOrData)
|
|
234
|
+
return { kind: 'not-prisma' };
|
|
235
|
+
// The shape matches. Confirm the verb against the derived set; an absent set
|
|
236
|
+
// (no generated client) is `unproven`, a confirmed non-verb is `not-prisma`.
|
|
237
|
+
if (prismaVerbs === null)
|
|
238
|
+
return { kind: 'prisma-unproven', reason: PRISMA_NOT_PRESENT_REASON };
|
|
239
|
+
return prismaVerbs.has(verb) ? { kind: 'prisma' } : { kind: 'not-prisma' };
|
|
223
240
|
}
|
|
224
241
|
/**
|
|
225
242
|
* Spec 68 Thing 2 (#312) — the query-builder shape test.
|
|
@@ -242,23 +259,28 @@ function isPrismaObjectForm(node, adapter, sourceCode) {
|
|
|
242
259
|
* such chain: the real misses are *variable-split* builders (the companion verb
|
|
243
260
|
* lives in a prior statement, e.g. `const q = baseQuery.where(…)`), which
|
|
244
261
|
* catalog resolution does not address. Gating on an under-discovered catalog
|
|
245
|
-
* would just rebuild the receiver-name failure one layer up.
|
|
246
|
-
* extraction-completeness gate (scripts/verify-extraction-completeness.ts) for
|
|
247
|
-
* the pinned residual.
|
|
262
|
+
* would just rebuild the receiver-name failure one layer up.
|
|
248
263
|
*
|
|
249
264
|
* @param node the candidate call node to classify
|
|
250
265
|
* @param adapter the language adapter used to read the callee
|
|
251
266
|
* @param sourceCode the file source text for reading callee text
|
|
267
|
+
* @param prismaVerbs the derived Prisma model verb set (or null when the generated
|
|
268
|
+
* client is absent). A Prisma object form is admitted by shape even when this is
|
|
269
|
+
* null — the *confirmation* of the verb is what degrades to `unproven`, never the
|
|
270
|
+
* admission (see {@link isPrismaObjectForm}).
|
|
252
271
|
* @returns true when the call is query-builder shaped (chain companion or Prisma object form)
|
|
253
272
|
*/
|
|
254
|
-
export function isQueryBuilderShape(node, adapter, sourceCode) {
|
|
273
|
+
export function isQueryBuilderShape(node, adapter, sourceCode, prismaVerbs = null) {
|
|
255
274
|
if (node.type !== 'call_expression')
|
|
256
275
|
return false;
|
|
257
276
|
const callee = getCallExpressionCallee(node, adapter);
|
|
258
277
|
if (!callee)
|
|
259
278
|
return false;
|
|
260
279
|
const methods = collectChainMethods(callee, adapter, sourceCode);
|
|
261
|
-
|
|
280
|
+
if (hasChainCompanion(methods))
|
|
281
|
+
return true;
|
|
282
|
+
const prismaVerdict = isPrismaObjectForm(node, adapter, sourceCode, prismaVerbs);
|
|
283
|
+
return prismaVerdict.kind !== 'not-prisma';
|
|
262
284
|
}
|
|
263
285
|
/**
|
|
264
286
|
* The single admission seam for a candidate node: resolve its handle-ness through
|
|
@@ -470,14 +492,14 @@ function receiverIsThisRooted(callee, adapter) {
|
|
|
470
492
|
* query-builder chain discovered by shape (Spec 68 Thing 2, #312).
|
|
471
493
|
*/
|
|
472
494
|
function isDbCallCandidate(node, scan, tagNames = SQL_TAG_NAMES) {
|
|
473
|
-
const { adapter, sourceCode } = scan;
|
|
495
|
+
const { adapter, sourceCode, vendor } = scan;
|
|
474
496
|
// Spec 70 R1.2: a DB/ORM-method call whose receiver resolves handle-or-unproven.
|
|
475
497
|
// `not-handle` (e.g. lodash `get`) and `null` (not query-shaped) are rejected.
|
|
476
498
|
const verdict = handleVerdictForCall(node, scan);
|
|
477
499
|
if (verdict !== null && verdict.kind !== 'not-handle')
|
|
478
500
|
return true;
|
|
479
501
|
// A query-builder chain is discovered by shape, not receiver name.
|
|
480
|
-
if (isQueryBuilderShape(node, adapter, sourceCode)) {
|
|
502
|
+
if (isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs)) {
|
|
481
503
|
return true;
|
|
482
504
|
}
|
|
483
505
|
// Tagged-template SQL is a data-access call by tag name — no provenance needed.
|
|
@@ -577,7 +599,7 @@ export function hasOrmFilterShape(node, adapter, sourceCode) {
|
|
|
577
599
|
* look like a DB-related query.
|
|
578
600
|
*/
|
|
579
601
|
function buildDatabaseCall(node, ast, scan) {
|
|
580
|
-
const { adapter, sourceCode, config, provenanceContext } = scan;
|
|
602
|
+
const { adapter, sourceCode, config, provenanceContext, vendor } = scan;
|
|
581
603
|
const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
|
|
582
604
|
if (!nodeText || nodeText.trim().length < 10)
|
|
583
605
|
return null;
|
|
@@ -633,8 +655,17 @@ function buildDatabaseCall(node, ast, scan) {
|
|
|
633
655
|
// a type-annotated `db: D1Database` with a dynamic template argument now stays
|
|
634
656
|
// visible instead of being dropped before the rules can see it.
|
|
635
657
|
const handleVerdict = handleVerdictForCall(node, scan);
|
|
636
|
-
|
|
637
|
-
|
|
658
|
+
// Item B (reissued) — a Prisma object-form site whose verb set cannot be
|
|
659
|
+
// confirmed (no generated `.prisma/client`) is `unproven` with that reason. It
|
|
660
|
+
// only fills a *null* handle verdict (no resolution evidence); a definitive
|
|
661
|
+
// `handle`/`not-handle` from declaration resolution always wins.
|
|
662
|
+
const prismaVerdict = isPrismaObjectForm(node, adapter, sourceCode, vendor.prismaVerbs);
|
|
663
|
+
const effectiveHandleVerdict = handleVerdict
|
|
664
|
+
?? (prismaVerdict.kind === 'prisma-unproven'
|
|
665
|
+
? { kind: 'unproven', reason: prismaVerdict.reason }
|
|
666
|
+
: null);
|
|
667
|
+
const handleAdmits = effectiveHandleVerdict !== null && effectiveHandleVerdict.kind !== 'not-handle';
|
|
668
|
+
const isSqlPosition = handleAdmits || isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs);
|
|
638
669
|
const isSqlQuery = sqlOk !== null || isSqlPosition || isTaggedSqlCall;
|
|
639
670
|
if (!isSqlQuery && !isOrmCall)
|
|
640
671
|
return null;
|
|
@@ -642,7 +673,7 @@ function buildDatabaseCall(node, ast, scan) {
|
|
|
642
673
|
const builderVerb = sqlAstNode ? null : builderWriteVerb(node, adapter, sourceCode);
|
|
643
674
|
// Tables: SQL relations from the AST ∪ ORM-shaped references from the node.
|
|
644
675
|
const sqlTables = sqlAstNode ? extractTableNames(sqlAstNode) : [];
|
|
645
|
-
const ormTables = isOrmCall ? extractOrmTables(node, adapter, sourceCode, config) : [];
|
|
676
|
+
const ormTables = isOrmCall ? extractOrmTables(node, adapter, sourceCode, config, vendor) : [];
|
|
646
677
|
const tables = [...new Set([...sqlTables, ...ormTables])];
|
|
647
678
|
// Row-limiting filter: SQL WHERE/HAVING/LIMIT from the AST, or the ORM chain
|
|
648
679
|
// shape when there is no SQL to walk.
|
|
@@ -660,7 +691,7 @@ function buildDatabaseCall(node, ast, scan) {
|
|
|
660
691
|
// a proven `handle` — a parsed SQL argument (R3) or a package in the manifest
|
|
661
692
|
// (R4) — fires. A type name proves nothing under criterion 9, so there is no
|
|
662
693
|
// type-annotation signal to consult.
|
|
663
|
-
const injectionRisk = security.injectionRisk &&
|
|
694
|
+
const injectionRisk = security.injectionRisk && effectiveHandleVerdict?.kind === 'handle';
|
|
664
695
|
const callType = classifyCallType(isSqlQuery, isOrmCall);
|
|
665
696
|
return {
|
|
666
697
|
type: callType,
|
|
@@ -685,7 +716,7 @@ function buildDatabaseCall(node, ast, scan) {
|
|
|
685
716
|
sqlEscaped: security.escaped,
|
|
686
717
|
enclosingFunction: enclosingIdentity(node, adapter, ast.filePath),
|
|
687
718
|
resolvedWhere: resolveWhereBinding(node, sourceCode, adapter) ?? undefined,
|
|
688
|
-
handleVerdict:
|
|
719
|
+
handleVerdict: effectiveHandleVerdict ?? undefined,
|
|
689
720
|
};
|
|
690
721
|
}
|
|
691
722
|
/**
|
|
@@ -1093,15 +1124,15 @@ function dbCallMethodName(node, adapter, sourceCode) {
|
|
|
1093
1124
|
* which only construct a statement object). Used to decide whether a
|
|
1094
1125
|
* prepare/bind call is chained into an eager execution.
|
|
1095
1126
|
*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1127
|
+
* Item B (reissued): the eager set is no longer a module-level constant. It is
|
|
1128
|
+
* derived per-run from the tree's own vendor declarations via
|
|
1129
|
+
* `deriveVendorSets` and threaded on `scan.vendor.eagerMethods`. The eager
|
|
1130
|
+
* classification (which statement methods return rows vs. only configure the
|
|
1131
|
+
* statement) is still the documented judgment; the *names* now come from the
|
|
1132
|
+
* installed vendor's shipped `.d.ts` (better-sqlite3 `Statement`/`Database`,
|
|
1133
|
+
* node:sqlite `StatementSync`/`DatabaseSync`, D1 `D1PreparedStatement`/`D1Database`),
|
|
1134
|
+
* plus the generic `query` driver abstraction (`IndexHandle.query`).
|
|
1103
1135
|
*/
|
|
1104
|
-
const EAGER_DB_METHODS = new Set(['run', 'all', 'first', 'raw', 'exec', 'batch', 'query', 'get']);
|
|
1105
1136
|
/**
|
|
1106
1137
|
* True when `node` (a `.prepare()`/`.bind()` call) is chained into an eager
|
|
1107
1138
|
* method — e.g. `db.prepare(sql).bind(x).run()`. The `.run()` at the end of the
|
|
@@ -1109,7 +1140,7 @@ const EAGER_DB_METHODS = new Set(['run', 'all', 'first', 'raw', 'exec', 'batch',
|
|
|
1109
1140
|
* N+1, not an accumulate-then-batch false positive. Walks up the enclosing
|
|
1110
1141
|
* expression and stops at the statement boundary; a chain never crosses one.
|
|
1111
1142
|
*/
|
|
1112
|
-
function hasEagerMethodInCallChain(node, adapter, sourceCode) {
|
|
1143
|
+
function hasEagerMethodInCallChain(node, adapter, sourceCode, vendor) {
|
|
1113
1144
|
const call = resolveDbCallNode(node, adapter);
|
|
1114
1145
|
let cur = call ? adapter.getParent(call) : adapter.getParent(node);
|
|
1115
1146
|
while (cur) {
|
|
@@ -1117,7 +1148,7 @@ function hasEagerMethodInCallChain(node, adapter, sourceCode) {
|
|
|
1117
1148
|
if (type === 'call_expression') {
|
|
1118
1149
|
const memberExpr = findMemberCallee(cur, adapter);
|
|
1119
1150
|
const name = memberExpr ? memberPropertyName(memberExpr, adapter, sourceCode) : null;
|
|
1120
|
-
if (name &&
|
|
1151
|
+
if (name && vendor.eagerMethods.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode))
|
|
1121
1152
|
return true;
|
|
1122
1153
|
}
|
|
1123
1154
|
if (type === 'expression_statement' ||
|
|
@@ -1141,9 +1172,9 @@ function hasEagerMethodInCallChain(node, adapter, sourceCode) {
|
|
|
1141
1172
|
* `.first()` / `.raw()` / `.exec()` / `.batch()` / `.get()`). Such a call
|
|
1142
1173
|
* performs no I/O, so it is not a query-in-loop on its own (Spec 52 R1).
|
|
1143
1174
|
*/
|
|
1144
|
-
function isStatementConstructionOnly(node, adapter, sourceCode) {
|
|
1175
|
+
function isStatementConstructionOnly(node, adapter, sourceCode, vendor) {
|
|
1145
1176
|
const method = dbCallMethodName(node, adapter, sourceCode);
|
|
1146
|
-
return (method === 'prepare' || method === 'bind') && !hasEagerMethodInCallChain(node, adapter, sourceCode);
|
|
1177
|
+
return (method === 'prepare' || method === 'bind') && !hasEagerMethodInCallChain(node, adapter, sourceCode, vendor);
|
|
1147
1178
|
}
|
|
1148
1179
|
/**
|
|
1149
1180
|
* True when a db-call node is SQL-string construction inside a query compiler:
|
|
@@ -1156,9 +1187,9 @@ function isStatementConstructionOnly(node, adapter, sourceCode) {
|
|
|
1156
1187
|
* (`run`/`all`/`exec`/`query`/…) are excluded, so a genuine
|
|
1157
1188
|
* `results.push(db.query(...))` still fires.
|
|
1158
1189
|
*/
|
|
1159
|
-
function isSqlStringConstruction(node, adapter, sourceCode) {
|
|
1190
|
+
function isSqlStringConstruction(node, adapter, sourceCode, vendor) {
|
|
1160
1191
|
const method = dbCallMethodName(node, adapter, sourceCode);
|
|
1161
|
-
if (!method ||
|
|
1192
|
+
if (!method || vendor.eagerMethods.has(method))
|
|
1162
1193
|
return false;
|
|
1163
1194
|
if (!isOrmMethod(method))
|
|
1164
1195
|
return false;
|
|
@@ -1252,8 +1283,9 @@ function isInExecChain(node, adapter, sourceCode) {
|
|
|
1252
1283
|
return hasSpreadArgument(execCall, adapter);
|
|
1253
1284
|
}
|
|
1254
1285
|
/**
|
|
1255
|
-
* Detect eager
|
|
1256
|
-
* argument — D1's `.all()`/`.first()`/`.run()`,
|
|
1286
|
+
* Detect eager-execution methods called with bind parameters as a second
|
|
1287
|
+
* argument — D1's `.all()`/`.first()`/`.run()`, better-sqlite3's `.get()` /
|
|
1288
|
+
* `.iterate()`, node:sqlite's `.get()` / `.all()`, plus the parameterized
|
|
1257
1289
|
* `.query()`/`.execute()` form shared by node-postgres/mysql2 and the tool's
|
|
1258
1290
|
* own `IndexHandle`.
|
|
1259
1291
|
*
|
|
@@ -1268,18 +1300,40 @@ function isInExecChain(node, adapter, sourceCode) {
|
|
|
1268
1300
|
* `?`-placeholder clauses (`${fp.clause}`, `${inList(types)}`) and `params`
|
|
1269
1301
|
* binds the values out-of-band. A single-arg `.query(\`…${input}\`)` — the raw
|
|
1270
1302
|
* injection shape — stays flagged because it carries no bind argument.
|
|
1303
|
+
*
|
|
1304
|
+
* Item B (reissued): the method set is no longer a hand-kept `D1_CONVENIENCE`
|
|
1305
|
+
* list — it is the cross-driver eager-execution surface (`vendor.eagerExecutionMethods`),
|
|
1306
|
+
* the union of whichever driver surfaces resolved in the analyzed tree. A tree
|
|
1307
|
+
* with only better-sqlite3 gets better-sqlite3's methods and nothing else.
|
|
1308
|
+
*/
|
|
1309
|
+
/**
|
|
1310
|
+
* True when every `${…}` interpolation in the eager-execution call's SQL
|
|
1311
|
+
* argument is provably structural — a placeholder list (`.map(() => '?').join`),
|
|
1312
|
+
* a literal clause fragment, or a safe identifier — and NOT a bound runtime
|
|
1313
|
+
* value. Argument count alone does not establish this: `db.run(\`…\${name}\`,
|
|
1314
|
+
* [name])` carries two arguments yet interpolates a value, so the site stays
|
|
1315
|
+
* flagged. Reuses {@link isSafeDynamicPart}, the same classifier the non-eager
|
|
1316
|
+
* path applies, so "structural vs value-bearing" has one definition.
|
|
1271
1317
|
*/
|
|
1272
|
-
function
|
|
1273
|
-
const
|
|
1318
|
+
function allInterpolationsStructural(node, scan) {
|
|
1319
|
+
const { adapter, sourceCode } = scan;
|
|
1320
|
+
if (!adapter.getDynamicParts)
|
|
1321
|
+
return false;
|
|
1322
|
+
const parts = adapter.getDynamicParts(node, sourceCode);
|
|
1323
|
+
return parts.every((part) => isSafeDynamicPart(part, scan.ast, scan));
|
|
1324
|
+
}
|
|
1325
|
+
function isEagerExecutionCall(node, adapter, sourceCode, eagerExecutionMethods, scan) {
|
|
1274
1326
|
const call = findCallFromEntry(node, adapter);
|
|
1275
1327
|
if (!call)
|
|
1276
1328
|
return false;
|
|
1277
1329
|
const callee = findMemberCallee(call, adapter);
|
|
1278
1330
|
const method = callee ? memberPropertyName(callee, adapter, sourceCode) : null;
|
|
1279
|
-
if (!method || !
|
|
1331
|
+
if (!method || !eagerExecutionMethods || !eagerExecutionMethods.has(method))
|
|
1280
1332
|
return false;
|
|
1281
|
-
// Bind params as a second argument
|
|
1282
|
-
|
|
1333
|
+
// Bind params as a second argument AND every interpolation structural → fully
|
|
1334
|
+
// parameterized. Argument count is not evidence of parameterization: a value
|
|
1335
|
+
// interpolation (`${name}`) stays flagged even with a bind-params argument.
|
|
1336
|
+
return countArgs(call, adapter) >= 2 && allInterpolationsStructural(node, scan);
|
|
1283
1337
|
}
|
|
1284
1338
|
/**
|
|
1285
1339
|
* Detect simple-function-call DB wrappers with bind parameters.
|
|
@@ -1294,7 +1348,7 @@ function isD1ConvenienceCall(node, adapter, sourceCode) {
|
|
|
1294
1348
|
*
|
|
1295
1349
|
* Entry point: a template_string inside the wrapper's arguments.
|
|
1296
1350
|
*/
|
|
1297
|
-
function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames) {
|
|
1351
|
+
function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames, scan) {
|
|
1298
1352
|
if (wrapperNames.length === 0)
|
|
1299
1353
|
return false;
|
|
1300
1354
|
if (adapter.getNodeType(node) !== 'template_string')
|
|
@@ -1317,10 +1371,12 @@ function isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames
|
|
|
1317
1371
|
const calleeName = adapter.getNodeText(callee, sourceCode);
|
|
1318
1372
|
if (!wrapperNames.includes(calleeName))
|
|
1319
1373
|
return false;
|
|
1320
|
-
// Check for a second argument — the bind params
|
|
1321
|
-
//
|
|
1374
|
+
// Check for a second argument — the bind params — AND every interpolation
|
|
1375
|
+
// structural. A single-arg call (d1Query(sql) without params) has no runtime
|
|
1376
|
+
// parameterization, and a two-arg call whose template interpolates a value is
|
|
1377
|
+
// raw assembly, not parameterized (same discriminant as eager-execution).
|
|
1322
1378
|
const realArgs = adapter.getChildren(args).filter(c => !['(', ')', ','].includes(adapter.getNodeType(c)));
|
|
1323
|
-
return realArgs.length >= 2;
|
|
1379
|
+
return realArgs.length >= 2 && allInterpolationsStructural(node, scan);
|
|
1324
1380
|
}
|
|
1325
1381
|
/**
|
|
1326
1382
|
* Check the two-statement prepare→bind pattern: db.prepare() is assigned to
|
|
@@ -1527,10 +1583,6 @@ function ormTableNameFromArgument(node, adapter, sourceCode) {
|
|
|
1527
1583
|
const literal = stringLiteralValue(node, adapter, sourceCode);
|
|
1528
1584
|
return literal && literal.length > 0 ? literal : null;
|
|
1529
1585
|
}
|
|
1530
|
-
/** Kysely builder verbs whose first argument is the table name. */
|
|
1531
|
-
const ORM_TABLE_VERBS = new Set(['selectFrom', 'deleteFrom', 'insertInto', 'updateTable']);
|
|
1532
|
-
/** ORM join verbs whose first argument is the joined table. */
|
|
1533
|
-
const ORM_JOIN_VERBS = new Set(['join', 'leftJoin', 'rightJoin', 'innerJoin']);
|
|
1534
1586
|
/**
|
|
1535
1587
|
* Walk the callee chain of an ORM candidate, reading table references from the
|
|
1536
1588
|
* *structure* (member names + argument nodes) rather than the source text. A
|
|
@@ -1540,10 +1592,12 @@ const ORM_JOIN_VERBS = new Set(['join', 'leftJoin', 'rightJoin', 'innerJoin']);
|
|
|
1540
1592
|
* Three shapes are read:
|
|
1541
1593
|
* - `.from(table)` / `.from('table')` (except JS `Array.from`/`Buffer.from`/
|
|
1542
1594
|
* `String.from`/`*Array.from` construction),
|
|
1543
|
-
* - a Kysely/join verb carrying the table as its first argument
|
|
1595
|
+
* - a Kysely/join verb carrying the table as its first argument (the table and
|
|
1596
|
+
* join verbs are `vendor.ormTableVerbs`/`vendor.ormJoinVerbs` — derived
|
|
1597
|
+
* per-run from the tree's Kysely declaration, item B reissued),
|
|
1544
1598
|
* - Drizzle's `db.<table>.<method>()` table-access idiom.
|
|
1545
1599
|
*/
|
|
1546
|
-
function collectOrmTableReferences(node, adapter, sourceCode, tables) {
|
|
1600
|
+
function collectOrmTableReferences(node, adapter, sourceCode, tables, vendor) {
|
|
1547
1601
|
const t = adapter.getNodeType(node);
|
|
1548
1602
|
if (t === 'call_expression') {
|
|
1549
1603
|
const callee = getCallExpressionCallee(node, adapter);
|
|
@@ -1561,8 +1615,8 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
|
|
|
1561
1615
|
const isFrom = method === 'from'
|
|
1562
1616
|
&& !(receiverName && (receiverName === 'Buffer' || receiverName === 'String'
|
|
1563
1617
|
|| receiverName.endsWith('Array')));
|
|
1564
|
-
const isVerb =
|
|
1565
|
-
const isJoin =
|
|
1618
|
+
const isVerb = vendor.ormTableVerbs.has(method);
|
|
1619
|
+
const isJoin = vendor.ormJoinVerbs.has(method) && receiverName !== 'sql';
|
|
1566
1620
|
if (isFrom || isVerb || isJoin) {
|
|
1567
1621
|
const name = ormTableNameFromArgument(firstArg, adapter, sourceCode);
|
|
1568
1622
|
if (name)
|
|
@@ -1570,7 +1624,7 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
|
|
|
1570
1624
|
}
|
|
1571
1625
|
}
|
|
1572
1626
|
}
|
|
1573
|
-
collectOrmTableReferences(callee, adapter, sourceCode, tables);
|
|
1627
|
+
collectOrmTableReferences(callee, adapter, sourceCode, tables, vendor);
|
|
1574
1628
|
}
|
|
1575
1629
|
}
|
|
1576
1630
|
else if (t === 'member_expression' || t === 'selector_expression') {
|
|
@@ -1588,12 +1642,12 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
|
|
|
1588
1642
|
}
|
|
1589
1643
|
}
|
|
1590
1644
|
if (object)
|
|
1591
|
-
collectOrmTableReferences(object, adapter, sourceCode, tables);
|
|
1645
|
+
collectOrmTableReferences(object, adapter, sourceCode, tables, vendor);
|
|
1592
1646
|
}
|
|
1593
1647
|
else if (t === 'subscript_expression') {
|
|
1594
1648
|
const object = memberObjectNode(node, adapter);
|
|
1595
1649
|
if (object)
|
|
1596
|
-
collectOrmTableReferences(object, adapter, sourceCode, tables);
|
|
1650
|
+
collectOrmTableReferences(object, adapter, sourceCode, tables, vendor);
|
|
1597
1651
|
}
|
|
1598
1652
|
}
|
|
1599
1653
|
/**
|
|
@@ -1609,11 +1663,12 @@ function collectOrmTableReferences(node, adapter, sourceCode, tables) {
|
|
|
1609
1663
|
* @param adapter The language adapter (drives node traversal).
|
|
1610
1664
|
* @param sourceCode The file source text.
|
|
1611
1665
|
* @param config The data-access analyzer config holding table patterns.
|
|
1666
|
+
* @param vendor The per-run derived vendor method sets (item B reissued).
|
|
1612
1667
|
* @returns The set of table names referenced by ORM shapes.
|
|
1613
1668
|
*/
|
|
1614
|
-
export function extractOrmTables(node, adapter, sourceCode, config) {
|
|
1669
|
+
export function extractOrmTables(node, adapter, sourceCode, config, vendor) {
|
|
1615
1670
|
const tables = new Set();
|
|
1616
|
-
collectOrmTableReferences(node, adapter, sourceCode, tables);
|
|
1671
|
+
collectOrmTableReferences(node, adapter, sourceCode, tables, vendor);
|
|
1617
1672
|
// User-configured `orm` patterns are arbitrary text shapes with no node to
|
|
1618
1673
|
// read (e.g. `table: 'users'`), so they still run over the source text.
|
|
1619
1674
|
const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
|
|
@@ -1654,14 +1709,15 @@ function isUnfilteredRead(call, config) {
|
|
|
1654
1709
|
* wrapper function with bind params). All four short-circuit checkQuerySecurity
|
|
1655
1710
|
* to a "safe" result.
|
|
1656
1711
|
*/
|
|
1657
|
-
function isParameterizedByChain(node,
|
|
1712
|
+
function isParameterizedByChain(node, scan, wrapperNames) {
|
|
1713
|
+
const { adapter, sourceCode, vendor } = scan;
|
|
1658
1714
|
if (isInPrepareBindChain(node, adapter, sourceCode))
|
|
1659
1715
|
return true;
|
|
1660
1716
|
if (isInExecChain(node, adapter, sourceCode))
|
|
1661
1717
|
return true;
|
|
1662
|
-
if (
|
|
1718
|
+
if (isEagerExecutionCall(node, adapter, sourceCode, vendor.eagerExecutionMethods, scan))
|
|
1663
1719
|
return true;
|
|
1664
|
-
if (isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames))
|
|
1720
|
+
if (isWrapperFunctionWithBindParams(node, adapter, sourceCode, wrapperNames, scan))
|
|
1665
1721
|
return true;
|
|
1666
1722
|
return false;
|
|
1667
1723
|
}
|
|
@@ -1786,10 +1842,10 @@ function isParameterizedTaggedTemplate(node, adapter, sourceCode, tagNames) {
|
|
|
1786
1842
|
return true;
|
|
1787
1843
|
}
|
|
1788
1844
|
function checkQuerySecurity(node, text, ast, scan) {
|
|
1789
|
-
const { adapter, sourceCode, config } = scan;
|
|
1790
|
-
// Parameterized chains (.prepare().bind(), .exec() spread,
|
|
1791
|
-
// DB wrappers) and explicit parameterization are always safe.
|
|
1792
|
-
if (isParameterizedByChain(node,
|
|
1845
|
+
const { adapter, sourceCode, config, vendor } = scan;
|
|
1846
|
+
// Parameterized chains (.prepare().bind(), .exec() spread, eager-execution
|
|
1847
|
+
// method, DB wrappers) and explicit parameterization are always safe.
|
|
1848
|
+
if (isParameterizedByChain(node, scan, effectiveWrapperNames(scan))) {
|
|
1793
1849
|
return { parameterized: true, injectionRisk: false, escaped: false };
|
|
1794
1850
|
}
|
|
1795
1851
|
// A drizzle `sql`/`db` tagged template parameterizes bare `${id}` interpolations
|
|
@@ -1798,9 +1854,14 @@ function checkQuerySecurity(node, text, ast, scan) {
|
|
|
1798
1854
|
if (isParameterizedTaggedTemplate(node, adapter, sourceCode, config.sqlTagNames ?? SQL_TAG_NAMES)) {
|
|
1799
1855
|
return { parameterized: true, injectionRisk: false, escaped: false };
|
|
1800
1856
|
}
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1857
|
+
// There is deliberately NO whole-text substring test for "parameterized"
|
|
1858
|
+
// markers (`prepared`, `$1`, `:param`, …) ahead of the per-interpolation
|
|
1859
|
+
// classifier below. A presence test on the query *text* can never establish
|
|
1860
|
+
// that a particular `${…}` interpolation is safe — only isSafeDynamicPart can.
|
|
1861
|
+
// Any such short-circuit is a false-negative generator: a column named
|
|
1862
|
+
// `prepared_at`, a `$100` literal, or a `':param'` string would clear a
|
|
1863
|
+
// value-bearing `${id}` before isSafeDynamicPart ever ran. The
|
|
1864
|
+
// `parameterizedQueries` config key was removed with it (no other consumer).
|
|
1804
1865
|
// No dynamic-string capability → can't prove unsafe; err quiet.
|
|
1805
1866
|
if (!adapter.isDynamicStringConstruction || !adapter.getDynamicParts) {
|
|
1806
1867
|
return { parameterized: false, injectionRisk: false, escaped: false };
|
|
@@ -1975,7 +2036,7 @@ function checkLoopQueries(ast, scan) {
|
|
|
1975
2036
|
* query) and the LLM/queue suppression live here — they need the AST.
|
|
1976
2037
|
*/
|
|
1977
2038
|
function collectLoopQueryCandidates(ast, scan) {
|
|
1978
|
-
const { adapter, sourceCode } = scan;
|
|
2039
|
+
const { adapter, sourceCode, vendor } = scan;
|
|
1979
2040
|
const candidates = [];
|
|
1980
2041
|
// Spec 21: provenance-gated detection of database calls.
|
|
1981
2042
|
const dbNodes = adapter.findNodes(ast, {
|
|
@@ -2001,13 +2062,13 @@ function collectLoopQueryCandidates(ast, scan) {
|
|
|
2001
2062
|
continue;
|
|
2002
2063
|
// Spec 52 R1 — skip statement construction (prepare/bind with no eager call);
|
|
2003
2064
|
// an eager call or a prepare chained into one still fires.
|
|
2004
|
-
if (isStatementConstructionOnly(node, adapter, sourceCode))
|
|
2065
|
+
if (isStatementConstructionOnly(node, adapter, sourceCode, vendor))
|
|
2005
2066
|
continue;
|
|
2006
2067
|
// Spec 52 R1 (extension) — skip SQL-string construction inside a query
|
|
2007
2068
|
// compiler: an ORM builder method (e.g. `aggregate`) on an unprovenanced
|
|
2008
2069
|
// `this` whose result is accumulated via `.push(...)` returns a string
|
|
2009
2070
|
// fragment, not a query result (knex `sql.push(...this.aggregate(stmt))`).
|
|
2010
|
-
if (isSqlStringConstruction(node, adapter, sourceCode))
|
|
2071
|
+
if (isSqlStringConstruction(node, adapter, sourceCode, vendor))
|
|
2011
2072
|
continue;
|
|
2012
2073
|
const loopInfo = findEnclosingLoop(node, adapter, sourceCode);
|
|
2013
2074
|
if (!loopInfo)
|
|
@@ -2053,7 +2114,7 @@ function collectLoopQueryCandidates(ast, scan) {
|
|
|
2053
2114
|
const preparesInLoop = loopBodyContainsPrepare(loopInfo.loopNode, adapter, sourceCode);
|
|
2054
2115
|
const hoistedReuse = !preparesInLoop &&
|
|
2055
2116
|
(transactionEnclosesLoop ||
|
|
2056
|
-
isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode));
|
|
2117
|
+
isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode, vendor));
|
|
2057
2118
|
if (hoistedReuse) {
|
|
2058
2119
|
continue;
|
|
2059
2120
|
}
|
|
@@ -2063,7 +2124,7 @@ function collectLoopQueryCandidates(ast, scan) {
|
|
|
2063
2124
|
// a chunked `id IN (…)` write is not an N+1 at any chunk size. Only a scalar
|
|
2064
2125
|
// or property read off a single loop element (`run(row.id)`) is per-row. See
|
|
2065
2126
|
// `bindsBatchArgument`.
|
|
2066
|
-
if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode)) {
|
|
2127
|
+
if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode, vendor)) {
|
|
2067
2128
|
continue;
|
|
2068
2129
|
}
|
|
2069
2130
|
// R4.1 (Spec 46): LLM-pipeline discriminator. A loop whose body invokes an
|
|
@@ -2186,8 +2247,8 @@ function loopBodyContainsPrepare(loopNode, adapter, sourceCode) {
|
|
|
2186
2247
|
* is NOT prepare-bound, so none of them reads as a statement re-run and each
|
|
2187
2248
|
* keeps firing.
|
|
2188
2249
|
*/
|
|
2189
|
-
function isHoistedStatementReRun(node, loopNode, adapter, sourceCode) {
|
|
2190
|
-
const base = memberChainBaseIdentifier(node, adapter, sourceCode);
|
|
2250
|
+
function isHoistedStatementReRun(node, loopNode, adapter, sourceCode, vendor) {
|
|
2251
|
+
const base = memberChainBaseIdentifier(node, adapter, sourceCode, vendor);
|
|
2191
2252
|
if (base && identifierBoundToPrepareOutsideLoop(base, loopNode, adapter, sourceCode)) {
|
|
2192
2253
|
return true;
|
|
2193
2254
|
}
|
|
@@ -2230,8 +2291,8 @@ function isBarePrepareBoundHelperCall(node, loopNode, adapter, sourceCode) {
|
|
|
2230
2291
|
* Returns null when the chain is not a member call (a bare `helper(db, …)`) or
|
|
2231
2292
|
* has no identifier root (an indexed access `x[i]()`).
|
|
2232
2293
|
*/
|
|
2233
|
-
function memberChainBaseIdentifier(node, adapter, sourceCode) {
|
|
2234
|
-
const eagerCall = findEagerExecutionCall(node, adapter, sourceCode);
|
|
2294
|
+
function memberChainBaseIdentifier(node, adapter, sourceCode, vendor) {
|
|
2295
|
+
const eagerCall = findEagerExecutionCall(node, adapter, sourceCode, vendor);
|
|
2235
2296
|
if (!eagerCall)
|
|
2236
2297
|
return null;
|
|
2237
2298
|
const memberExpr = findMemberCallee(eagerCall, adapter);
|
|
@@ -2468,8 +2529,15 @@ function walkSubtree(node, adapter, visitor) {
|
|
|
2468
2529
|
* Array methods that transform one collection into another collection (a batch):
|
|
2469
2530
|
* the elements of a `.slice()`/`.splice()`/`.map()`/`.filter()`/`.concat()`/
|
|
2470
2531
|
* `.flatMap()` result are a *sub*-collection, not a single row.
|
|
2532
|
+
*
|
|
2533
|
+
* Read from the ECMAScript runtime (item B): each name is validated against
|
|
2534
|
+
* `Array.prototype` rather than hand-copied. The *selection* — which array
|
|
2535
|
+
* methods return a collection (`map`/`filter`/`concat`/`flatMap`/`slice`/
|
|
2536
|
+
* `splice`) vs. which consume or reduce one (`forEach`/`reduce`/`find`/`some`/
|
|
2537
|
+
* `every`) — is the documented judgment; the names are the runtime's.
|
|
2471
2538
|
*/
|
|
2472
|
-
const COLLECTION_TRANSFORM_METHODS = new Set(['slice', 'splice', 'map', 'filter', 'concat', 'flatMap']
|
|
2539
|
+
export const COLLECTION_TRANSFORM_METHODS = new Set(['slice', 'splice', 'map', 'filter', 'concat', 'flatMap']
|
|
2540
|
+
.filter((m) => typeof Array.prototype[m] === 'function'));
|
|
2473
2541
|
/**
|
|
2474
2542
|
* Free-function callee names that chunk a collection into batches — `chunk(ids,
|
|
2475
2543
|
* n)`, `chunkArray(ids, n)`, `partition(rows, p)`. Recognized by name (not by a
|
|
@@ -2626,8 +2694,8 @@ function forOfIteration(loopNode, adapter, sourceCode) {
|
|
|
2626
2694
|
* the `.run(…)` chained off `db.prepare(…)`) is where parameters are bound; its
|
|
2627
2695
|
* arguments decide the row-vs-batch question.
|
|
2628
2696
|
*/
|
|
2629
|
-
function bindsBatchArgument(node, loopNode, adapter, sourceCode) {
|
|
2630
|
-
const eagerCall = findEagerExecutionCall(node, adapter, sourceCode);
|
|
2697
|
+
function bindsBatchArgument(node, loopNode, adapter, sourceCode, vendor) {
|
|
2698
|
+
const eagerCall = findEagerExecutionCall(node, adapter, sourceCode, vendor);
|
|
2631
2699
|
if (!eagerCall)
|
|
2632
2700
|
return false;
|
|
2633
2701
|
const argsNode = adapter.getChildren(eagerCall).find((c) => adapter.getNodeType(c) === 'arguments');
|
|
@@ -2672,12 +2740,12 @@ function bindsBatchArgument(node, loopNode, adapter, sourceCode) {
|
|
|
2672
2740
|
* chained off a statement-construction call (`db.prepare(sql).run(…)`). Returns
|
|
2673
2741
|
* null when the call never executes I/O (already filtered upstream).
|
|
2674
2742
|
*/
|
|
2675
|
-
function findEagerExecutionCall(node, adapter, sourceCode) {
|
|
2743
|
+
function findEagerExecutionCall(node, adapter, sourceCode, vendor) {
|
|
2676
2744
|
const call = resolveDbCallNode(node, adapter);
|
|
2677
2745
|
if (!call)
|
|
2678
2746
|
return null;
|
|
2679
2747
|
const direct = dbCallMethodName(call, adapter, sourceCode);
|
|
2680
|
-
if (direct &&
|
|
2748
|
+
if (direct && vendor.eagerMethods.has(direct)) {
|
|
2681
2749
|
const memberExpr = findMemberCallee(call, adapter);
|
|
2682
2750
|
if (!(memberExpr && isPromiseAllMember(memberExpr, adapter, sourceCode)))
|
|
2683
2751
|
return call;
|
|
@@ -2688,7 +2756,7 @@ function findEagerExecutionCall(node, adapter, sourceCode) {
|
|
|
2688
2756
|
if (type === 'call_expression') {
|
|
2689
2757
|
const memberExpr = findMemberCallee(cur, adapter);
|
|
2690
2758
|
const name = memberExpr ? memberPropertyName(memberExpr, adapter, sourceCode) : null;
|
|
2691
|
-
if (name &&
|
|
2759
|
+
if (name && vendor.eagerMethods.has(name) && !isPromiseAllMember(memberExpr, adapter, sourceCode)) {
|
|
2692
2760
|
return cur;
|
|
2693
2761
|
}
|
|
2694
2762
|
}
|
|
@@ -2894,7 +2962,7 @@ function buildGoEnv(ast, adapter, sourceCode, provenanceContext) {
|
|
|
2894
2962
|
sourceCode,
|
|
2895
2963
|
};
|
|
2896
2964
|
}
|
|
2897
|
-
function buildDataAccessScan(ast, adapter, sourceCode, config) {
|
|
2965
|
+
function buildDataAccessScan(ast, adapter, sourceCode, config, vendorSurfaces) {
|
|
2898
2966
|
const finalConfig = { ...DEFAULT_DATA_ACCESS_CONFIG, ...config };
|
|
2899
2967
|
const detectionMode = finalConfig.detection?.mode ?? 'hybrid';
|
|
2900
2968
|
const seedProvenance = config?._receiverProvenance;
|
|
@@ -2916,6 +2984,7 @@ function buildDataAccessScan(ast, adapter, sourceCode, config) {
|
|
|
2916
2984
|
provenanceContext,
|
|
2917
2985
|
handleEnv: buildHandleEnv(ast, adapter, sourceCode, provenanceContext),
|
|
2918
2986
|
goEnv: buildGoEnv(ast, adapter, sourceCode, provenanceContext),
|
|
2987
|
+
vendor: deriveVendorSets(vendorSurfaces ?? EMPTY_VENDOR_SURFACES),
|
|
2919
2988
|
};
|
|
2920
2989
|
}
|
|
2921
2990
|
/**
|
|
@@ -2966,7 +3035,7 @@ export function extractLoopQueries(ast, adapter, sourceCode, config) {
|
|
|
2966
3035
|
* producer sees), so every provenance-dependent signal is captured as an identity
|
|
2967
3036
|
* and re-folded later. `handleEnv`/`goEnv` still build their provenance-free
|
|
2968
3037
|
* bindings/imports, which the structural discovery predicates read. */
|
|
2969
|
-
function buildCandidateScan(ast, adapter, sourceCode, config) {
|
|
3038
|
+
function buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces) {
|
|
2970
3039
|
const finalConfig = { ...DEFAULT_DATA_ACCESS_CONFIG, ...config };
|
|
2971
3040
|
const provenanceContext = {
|
|
2972
3041
|
dbProvenanced: new Map(),
|
|
@@ -2982,6 +3051,7 @@ function buildCandidateScan(ast, adapter, sourceCode, config) {
|
|
|
2982
3051
|
provenanceContext,
|
|
2983
3052
|
handleEnv: buildHandleEnv(ast, adapter, sourceCode, provenanceContext),
|
|
2984
3053
|
goEnv: buildGoEnv(ast, adapter, sourceCode, provenanceContext),
|
|
3054
|
+
vendor: deriveVendorSets(vendorSurfaces ?? EMPTY_VENDOR_SURFACES),
|
|
2985
3055
|
};
|
|
2986
3056
|
}
|
|
2987
3057
|
// ── Structural discovery (empty-provenance supersets) ────────────────────────
|
|
@@ -3053,8 +3123,8 @@ function isTemplateOfDbCall(node, scan) {
|
|
|
3053
3123
|
* variable assignment) are provenance-free; the identifier/member/template arms
|
|
3054
3124
|
* cover every `handleVerdictForCall` shape with the empty seed. */
|
|
3055
3125
|
function isBroadenedDataAccessCandidate(node, scan, tagNames) {
|
|
3056
|
-
const { adapter, sourceCode } = scan;
|
|
3057
|
-
if (isQueryBuilderShape(node, adapter, sourceCode))
|
|
3126
|
+
const { adapter, sourceCode, vendor } = scan;
|
|
3127
|
+
if (isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs))
|
|
3058
3128
|
return true;
|
|
3059
3129
|
if (isTaggedTemplateSqlCall(node, adapter, sourceCode, tagNames))
|
|
3060
3130
|
return true;
|
|
@@ -3203,7 +3273,7 @@ function wrapperCalleeNameForTemplate(node, adapter, sourceCode) {
|
|
|
3203
3273
|
* the corpus `data-access-calls` producer re-folds. The static-security arms run here
|
|
3204
3274
|
* on the empty-provenance scan; only `arm4CalleeName` is corrected corpus-side. */
|
|
3205
3275
|
function buildDataAccessCallCandidate(node, ast, scan) {
|
|
3206
|
-
const { adapter, sourceCode, config } = scan;
|
|
3276
|
+
const { adapter, sourceCode, config, vendor } = scan;
|
|
3207
3277
|
const nodeText = stripComments(adapter.getNodeText(node, sourceCode));
|
|
3208
3278
|
if (!nodeText || nodeText.trim().length < 10)
|
|
3209
3279
|
return null;
|
|
@@ -3213,7 +3283,7 @@ function buildDataAccessCallCandidate(node, ast, scan) {
|
|
|
3213
3283
|
const isOrmCall = isOrmPattern(nodeText);
|
|
3214
3284
|
const tagNames = config.sqlTagNames ?? SQL_TAG_NAMES;
|
|
3215
3285
|
const isTaggedSqlCall = isTaggedTemplateSqlCall(node, adapter, sourceCode, tagNames);
|
|
3216
|
-
const isQueryBuilder = isQueryBuilderShape(node, adapter, sourceCode);
|
|
3286
|
+
const isQueryBuilder = isQueryBuilderShape(node, adapter, sourceCode, vendor.prismaVerbs);
|
|
3217
3287
|
const isVariableAssignmentSql = isVariableAssignment(node, adapter) && sqlArg !== null;
|
|
3218
3288
|
const isTemplate = isTemplateLiteral(node, adapter);
|
|
3219
3289
|
const handle = handleCallSiteIdentity(node, scan);
|
|
@@ -3249,7 +3319,7 @@ function buildDataAccessCallCandidate(node, ast, scan) {
|
|
|
3249
3319
|
hasOrganizationFilter: hasOrganizationFilter(nodeText, config),
|
|
3250
3320
|
enclosingFunction: enclosingIdentity(node, adapter, ast.filePath),
|
|
3251
3321
|
resolvedWhere: resolveWhereBinding(node, sourceCode, adapter),
|
|
3252
|
-
ormTables: isOrmCall ? extractOrmTables(node, adapter, sourceCode, config) : [],
|
|
3322
|
+
ormTables: isOrmCall ? extractOrmTables(node, adapter, sourceCode, config, vendor) : [],
|
|
3253
3323
|
builderVerb: builderWriteVerb(node, adapter, sourceCode),
|
|
3254
3324
|
ormHasFilter: hasOrmFilterShape(node, adapter, sourceCode),
|
|
3255
3325
|
staticParameterized: security.parameterized,
|
|
@@ -3269,10 +3339,12 @@ function buildDataAccessCallCandidate(node, ast, scan) {
|
|
|
3269
3339
|
* @param adapter - The language adapter (drives node discovery + text reads).
|
|
3270
3340
|
* @param sourceCode - The file's source text.
|
|
3271
3341
|
* @param config - Optional data-access config; defaults to the shared config.
|
|
3342
|
+
* @param vendorSurfaces - Optional per-run resolved vendor surfaces (item B
|
|
3343
|
+
* reissued); defaults to the empty set (no vendor resolved).
|
|
3272
3344
|
* @returns The raw, provenance-free `data-access-calls-candidates`.
|
|
3273
3345
|
*/
|
|
3274
|
-
export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config) {
|
|
3275
|
-
const scan = buildCandidateScan(ast, adapter, sourceCode, config);
|
|
3346
|
+
export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config, vendorSurfaces) {
|
|
3347
|
+
const scan = buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces);
|
|
3276
3348
|
const tagNames = scan.config.sqlTagNames ?? SQL_TAG_NAMES;
|
|
3277
3349
|
const nodes = adapter.findNodes(ast, {
|
|
3278
3350
|
custom: (node) => isBroadenedDataAccessCandidate(node, scan, tagNames),
|
|
@@ -3298,10 +3370,12 @@ export function extractDataAccessCallCandidates(ast, adapter, sourceCode, config
|
|
|
3298
3370
|
* @param adapter - The language adapter (drives node discovery + text reads).
|
|
3299
3371
|
* @param sourceCode - The file's source text.
|
|
3300
3372
|
* @param config - Optional data-access config; defaults to the shared config.
|
|
3373
|
+
* @param vendorSurfaces - Optional per-run resolved vendor surfaces (item B
|
|
3374
|
+
* reissued); defaults to the empty set (no vendor resolved).
|
|
3301
3375
|
* @returns The raw, provenance-free `loop-query-candidates`.
|
|
3302
3376
|
*/
|
|
3303
|
-
export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config) {
|
|
3304
|
-
const scan = buildCandidateScan(ast, adapter, sourceCode, config);
|
|
3377
|
+
export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config, vendorSurfaces) {
|
|
3378
|
+
const scan = buildCandidateScan(ast, adapter, sourceCode, config, vendorSurfaces);
|
|
3305
3379
|
const candidates = [];
|
|
3306
3380
|
const dbNodes = adapter.findNodes(ast, {
|
|
3307
3381
|
custom: (node) => isBroadenedLoopCandidate(node, scan),
|
|
@@ -3317,9 +3391,9 @@ export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config)
|
|
|
3317
3391
|
const nodeText = adapter.getNodeText(node, sourceCode);
|
|
3318
3392
|
if (!nodeText || nodeText.trim().length < 10)
|
|
3319
3393
|
continue;
|
|
3320
|
-
if (isStatementConstructionOnly(node, adapter, sourceCode))
|
|
3394
|
+
if (isStatementConstructionOnly(node, adapter, sourceCode, scan.vendor))
|
|
3321
3395
|
continue;
|
|
3322
|
-
if (isSqlStringConstruction(node, adapter, sourceCode))
|
|
3396
|
+
if (isSqlStringConstruction(node, adapter, sourceCode, scan.vendor))
|
|
3323
3397
|
continue;
|
|
3324
3398
|
const loopInfo = findEnclosingLoop(node, adapter, sourceCode);
|
|
3325
3399
|
if (!loopInfo)
|
|
@@ -3331,10 +3405,10 @@ export function extractLoopQueryRawCandidates(ast, adapter, sourceCode, config)
|
|
|
3331
3405
|
(enclosingFnName !== null && transactionWrappedFunctions.has(enclosingFnName));
|
|
3332
3406
|
const preparesInLoop = loopBodyContainsPrepare(loopInfo.loopNode, adapter, sourceCode);
|
|
3333
3407
|
const hoistedReuse = !preparesInLoop &&
|
|
3334
|
-
(transactionEnclosesLoop || isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode));
|
|
3408
|
+
(transactionEnclosesLoop || isHoistedStatementReRun(node, loopInfo.loopNode, adapter, sourceCode, scan.vendor));
|
|
3335
3409
|
if (hoistedReuse)
|
|
3336
3410
|
continue;
|
|
3337
|
-
if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode))
|
|
3411
|
+
if (bindsBatchArgument(node, loopInfo.loopNode, adapter, sourceCode, scan.vendor))
|
|
3338
3412
|
continue;
|
|
3339
3413
|
if (loopBodyContainsLlmCall(loopInfo.loopNode, adapter, sourceCode))
|
|
3340
3414
|
continue;
|