code-auditor-mcp 5.0.2 → 5.2.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 +184 -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 +0 -5
- 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/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/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.map +1 -1
- package/dist/auditRunner.js +136 -142
- package/dist/auditRunner.js.map +1 -1
- 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 +195 -26
- 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 +77 -18
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +105 -43
- package/dist/config/defaults.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 +17 -11
- package/dist/dataPaths.d.ts.map +1 -1
- package/dist/dataPaths.js +51 -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/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +18 -3
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.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/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/loopQueryCandidates.js +1 -1
- package/dist/phase/loopQueryCandidates.js.map +1 -1
- package/dist/phase/phaseModel.d.ts +37 -8
- package/dist/phase/phaseModel.d.ts.map +1 -1
- package/dist/phase/phaseModel.js +33 -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/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/dry.d.ts.map +1 -1
- package/dist/phase/rules/dry.js +0 -1
- 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 +48 -67
- 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 +47 -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 +16 -5
- 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/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 +46 -0
- package/dist/phase/types.d.ts.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/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/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 +23 -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 +6 -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 +9 -0
- package/plugin/scripts/hook-common.sh +8 -2
- 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 +9 -0
- package/plugin/scripts/hook-warm.sh +17 -11
- package/plugin/skills/code-auditor/SKILL.md +26 -3
- 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
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
package analyzer
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"go/token"
|
|
6
|
-
"regexp"
|
|
7
|
-
"strconv"
|
|
8
|
-
"strings"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// runDataAccessAnalysis analyzes database-access patterns on `database/sql`,
|
|
12
|
-
// `sqlx`, and GORM (`Raw`) method calls. It is the Go-subprocess counterpart of the TypeScript
|
|
13
|
-
// `UniversalDataAccessAnalyzer` + schema Stage-4 reducer, emitting the same
|
|
14
|
-
// bare rule IDs at the same ledger severities.
|
|
15
|
-
//
|
|
16
|
-
// Tenancy is declared, not guessed. The TypeScript pipeline resolves "which
|
|
17
|
-
// tables require an org/tenant filter" from three tiers — explicit
|
|
18
|
-
// `orgFilterTables` (config), configured `schemas` tables carrying a
|
|
19
|
-
// tenant-scoping column, and DDL migrations (`CREATE TABLE` columns) — via
|
|
20
|
-
// `buildOrgFilterTierSet`. This subprocess is syntax-only and does not read the
|
|
21
|
-
// project config or DDL itself, so the resolved tiers are handed to it in
|
|
22
|
-
// `AnalysisOptions`:
|
|
23
|
-
//
|
|
24
|
-
// - `orgFilterTables` — the tenant-scoped tables (all three tiers, lowercased)
|
|
25
|
-
// that drive `missing-org-filter`.
|
|
26
|
-
// - `knownTables` — the known-table catalog (DDL tables + configured schemas +
|
|
27
|
-
// explicit tables) that drives `unknown-table`.
|
|
28
|
-
// - `orgFilterColumns` — the tenant-scoping column names that count as a
|
|
29
|
-
// tenant predicate, defaulting to org_id/tenant_id/organization_id/
|
|
30
|
-
// workspace_id (the same default as the TS pipeline).
|
|
31
|
-
//
|
|
32
|
-
// An earlier revision hardcoded `tenantTables`/`knownTables` word lists as a
|
|
33
|
-
// substitute for declared tenancy — the Spec 44 English-name fallback the TS
|
|
34
|
-
// side had already deleted on purpose. That made `missing-org-filter` fire on
|
|
35
|
-
// `users`/`projects`/`orders` whether or not the project declared them
|
|
36
|
-
// multi-tenant, and stay silent on a genuinely tenant-scoped table whose name
|
|
37
|
-
// was absent from the list. Declared tenancy removes both errors.
|
|
38
|
-
|
|
39
|
-
// tableExtractRe matches `FROM <table>` / `INTO <table>` / `UPDATE <table>`
|
|
40
|
-
// (optionally backtick/double/single-quoted, optionally schema-prefixed).
|
|
41
|
-
var tableExtractRe = regexp.MustCompile(`(?i)\b(?:from|into|update)\s+[` + "`\"']?([A-Za-z_][A-Za-z0-9_.]*)")
|
|
42
|
-
|
|
43
|
-
// whereRe matches a row-limiting clause (WHERE/HAVING/LIMIT).
|
|
44
|
-
var whereRe = regexp.MustCompile(`(?i)\b(where|having|limit)\b`)
|
|
45
|
-
|
|
46
|
-
// defaultOrgFilterColumns are the tenant-scoping column names assumed when the
|
|
47
|
-
// caller supplies none. Mirrors DEFAULT_ORG_FILTER_COLUMNS on the TS side.
|
|
48
|
-
var defaultOrgFilterColumns = []string{"org_id", "tenant_id", "organization_id", "workspace_id"}
|
|
49
|
-
|
|
50
|
-
func (a *Analyzer) runDataAccessAnalysis() []Violation {
|
|
51
|
-
var violations []Violation
|
|
52
|
-
|
|
53
|
-
tenantTables := a.declaredTenantTables()
|
|
54
|
-
knownTables := a.declaredKnownTables()
|
|
55
|
-
tenantPredRe := a.tenantPredicateRegex()
|
|
56
|
-
|
|
57
|
-
for filePath, file := range a.parser.files {
|
|
58
|
-
ast.Inspect(file, func(n ast.Node) bool {
|
|
59
|
-
call, ok := n.(*ast.CallExpr)
|
|
60
|
-
if !ok {
|
|
61
|
-
return true
|
|
62
|
-
}
|
|
63
|
-
method, sqlArgIndex, isDB := dbMethodName(call)
|
|
64
|
-
if !isDB || sqlArgIndex < 0 || sqlArgIndex >= len(call.Args) {
|
|
65
|
-
return true
|
|
66
|
-
}
|
|
67
|
-
sqlExpr := call.Args[sqlArgIndex]
|
|
68
|
-
|
|
69
|
-
// sql-injection-risk: a dynamically-constructed SQL string is an
|
|
70
|
-
// injection surface; a raw string literal with `$1` placeholders is
|
|
71
|
-
// not. The dynamic case returns early — its table name is not
|
|
72
|
-
// resolvable, so the table-based rules below cannot apply.
|
|
73
|
-
if isDynamicSQL(sqlExpr) {
|
|
74
|
-
if !containsSQLVerb(sqlFormatString(sqlExpr)) {
|
|
75
|
-
return true
|
|
76
|
-
}
|
|
77
|
-
pos := a.parser.fileSet.Position(call.Pos())
|
|
78
|
-
violations = append(violations, Violation{
|
|
79
|
-
File: filePath,
|
|
80
|
-
Line: pos.Line,
|
|
81
|
-
Severity: "critical",
|
|
82
|
-
Message: "SQL query is built with string formatting instead of parameterized placeholders",
|
|
83
|
-
Details: map[string]interface{}{
|
|
84
|
-
"method": method,
|
|
85
|
-
},
|
|
86
|
-
Suggestion: "Use $1/$2 placeholders and pass the values as separate arguments",
|
|
87
|
-
Analyzer: "go",
|
|
88
|
-
Rule: "sql-injection-risk",
|
|
89
|
-
})
|
|
90
|
-
return true
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
sqlText := literalString(sqlExpr)
|
|
94
|
-
if sqlText == "" {
|
|
95
|
-
// A variable-held SQL string: neither provably static nor
|
|
96
|
-
// provably dynamic. Leave it alone rather than guess.
|
|
97
|
-
return true
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
verb := sqlVerb(sqlText)
|
|
101
|
-
table := extractTable(sqlText)
|
|
102
|
-
if table == "" {
|
|
103
|
-
return true
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// unknown-table: a recognized-name near-miss (`user` → `users`).
|
|
107
|
-
if near, known := nearMissTable(knownTables, table); !known && near != "" {
|
|
108
|
-
pos := a.parser.fileSet.Position(call.Pos())
|
|
109
|
-
violations = append(violations, Violation{
|
|
110
|
-
File: filePath,
|
|
111
|
-
Line: pos.Line,
|
|
112
|
-
Severity: "critical",
|
|
113
|
-
Message: "Reference to unknown table \"" + table + "\" (did you mean \"" + near + "\"?)",
|
|
114
|
-
Details: map[string]interface{}{
|
|
115
|
-
"table": table,
|
|
116
|
-
"suggestion": near,
|
|
117
|
-
},
|
|
118
|
-
Suggestion: "Rename the table reference \"" + table + "\" to \"" + near + "\"",
|
|
119
|
-
Analyzer: "go",
|
|
120
|
-
Rule: "unknown-table",
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
hasWhere := whereRe.MatchString(sqlText)
|
|
125
|
-
hasTenantPred := tenantPredRe.MatchString(sqlText)
|
|
126
|
-
|
|
127
|
-
// missing-org-filter: a row-affecting query (SELECT/UPDATE/DELETE) on
|
|
128
|
-
// a tenant table with no tenant predicate. INSERT is excluded — an
|
|
129
|
-
// insert does not filter existing rows; it should carry the tenant
|
|
130
|
-
// column as a value, not a predicate.
|
|
131
|
-
if tenantTables[table] && verb != "INSERT" && !hasTenantPred {
|
|
132
|
-
pos := a.parser.fileSet.Position(call.Pos())
|
|
133
|
-
violations = append(violations, Violation{
|
|
134
|
-
File: filePath,
|
|
135
|
-
Line: pos.Line,
|
|
136
|
-
Severity: "critical",
|
|
137
|
-
Message: "Query on \"" + table + "\" has no organization/tenant predicate",
|
|
138
|
-
Details: map[string]interface{}{
|
|
139
|
-
"table": table,
|
|
140
|
-
"method": method,
|
|
141
|
-
},
|
|
142
|
-
Suggestion: "Add the tenant column (organization_id / org_id) to the WHERE predicate",
|
|
143
|
-
Analyzer: "go",
|
|
144
|
-
Rule: "missing-org-filter",
|
|
145
|
-
})
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// unfiltered-query: a write (INSERT/UPDATE/DELETE) with no
|
|
149
|
-
// WHERE/HAVING/LIMIT. INSERT counts as a write here — the corpus pins
|
|
150
|
-
// it — whereas the TS rule scopes writes to DELETE/UPDATE; an
|
|
151
|
-
// unparameterized mass INSERT is the same "no filter" shape.
|
|
152
|
-
if isWriteVerb(verb) && !hasWhere {
|
|
153
|
-
pos := a.parser.fileSet.Position(call.Pos())
|
|
154
|
-
violations = append(violations, Violation{
|
|
155
|
-
File: filePath,
|
|
156
|
-
Line: pos.Line,
|
|
157
|
-
Severity: "high",
|
|
158
|
-
Message: "Write query on \"" + table + "\" has no WHERE/HAVING/LIMIT",
|
|
159
|
-
Details: map[string]interface{}{
|
|
160
|
-
"table": table,
|
|
161
|
-
"method": method,
|
|
162
|
-
},
|
|
163
|
-
Suggestion: "Add a WHERE/HAVING/LIMIT to scope the write",
|
|
164
|
-
Analyzer: "go",
|
|
165
|
-
Rule: "unfiltered-query",
|
|
166
|
-
})
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return true
|
|
170
|
-
})
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return violations
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// dbMethodName reports whether the call is a `database/sql`, `sqlx`, or GORM
|
|
177
|
-
// method invocation and returns the selector name plus the index of the
|
|
178
|
-
// SQL-string argument within the call. The method set is the `database/sql`
|
|
179
|
-
// query/exec/prepare surface, the `sqlx` wrapper surface, and GORM's `Raw`
|
|
180
|
-
// (its one full-SQL literal method); it deliberately excludes `Close`,
|
|
181
|
-
// `Commit`, `Rollback`, `Err`, and `Next`, which are not SQL-bearing.
|
|
182
|
-
//
|
|
183
|
-
// The SQL argument is not always arg[0], and getting the index wrong is a
|
|
184
|
-
// silent no-op (the analyzer extracts the wrong expression, sees it is not a
|
|
185
|
-
// string literal, and returns early). Two families carry the SQL later:
|
|
186
|
-
//
|
|
187
|
-
// - **Context variants** (`QueryContext`, `ExecContext`, `QueryxContext`,
|
|
188
|
-
// `MustExecContext`, …) put `ctx context.Context` first, so the SQL is
|
|
189
|
-
// arg[1]. This is the canonical parameterized form since Go 1.8 — the
|
|
190
|
-
// overwhelmingly common shape in production code — so the index matters
|
|
191
|
-
// more here than for the bare forms.
|
|
192
|
-
// - **sqlx `Get`/`Select`** put the destination pointer before the SQL
|
|
193
|
-
// (`Get(dest, query, args…)`), so the SQL is arg[1]; with `ctx` it is
|
|
194
|
-
// arg[2] (`GetContext(ctx, dest, query, args…)`).
|
|
195
|
-
func dbMethodName(call *ast.CallExpr) (string, int, bool) {
|
|
196
|
-
sel, ok := call.Fun.(*ast.SelectorExpr)
|
|
197
|
-
if !ok {
|
|
198
|
-
return "", -1, false
|
|
199
|
-
}
|
|
200
|
-
switch sel.Sel.Name {
|
|
201
|
-
// database/sql — SQL string is arg[0].
|
|
202
|
-
case "Query", "QueryRow", "Exec", "Prepare":
|
|
203
|
-
return sel.Sel.Name, 0, true
|
|
204
|
-
// database/sql Context — ctx first, SQL string is arg[1].
|
|
205
|
-
case "QueryContext", "QueryRowContext", "ExecContext", "PrepareContext":
|
|
206
|
-
return sel.Sel.Name, 1, true
|
|
207
|
-
// sqlx — SQL string is arg[0].
|
|
208
|
-
case "Queryx", "QueryRowx", "NamedExec", "NamedQuery", "MustExec":
|
|
209
|
-
return sel.Sel.Name, 0, true
|
|
210
|
-
// sqlx Context — ctx first, SQL string is arg[1].
|
|
211
|
-
case "QueryxContext", "QueryRowxContext", "NamedExecContext", "NamedQueryContext", "MustExecContext":
|
|
212
|
-
return sel.Sel.Name, 1, true
|
|
213
|
-
// sqlx — SQL string is arg[1] (after the destination pointer).
|
|
214
|
-
case "Get", "Select":
|
|
215
|
-
return sel.Sel.Name, 1, true
|
|
216
|
-
// sqlx — SQL string is arg[2] (after ctx + destination pointer).
|
|
217
|
-
case "GetContext", "SelectContext":
|
|
218
|
-
return sel.Sel.Name, 2, true
|
|
219
|
-
// GORM — `Raw(sql, values…)` is GORM's full-SQL literal method; SQL is
|
|
220
|
-
// arg[0]. GORM's `Exec`/`Select` are intentionally NOT listed here: `Exec`
|
|
221
|
-
// is already the database/sql case above, and GORM's `Select` takes a
|
|
222
|
-
// column list (`Select("name, age")`), not a full statement.
|
|
223
|
-
case "Raw":
|
|
224
|
-
return sel.Sel.Name, 0, true
|
|
225
|
-
}
|
|
226
|
-
return "", -1, false
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// isDynamicSQL reports whether the SQL argument is constructed at runtime
|
|
230
|
-
// (string formatting or concatenation) rather than a single string literal.
|
|
231
|
-
func isDynamicSQL(expr ast.Expr) bool {
|
|
232
|
-
switch e := expr.(type) {
|
|
233
|
-
case *ast.BasicLit:
|
|
234
|
-
return false
|
|
235
|
-
case *ast.CallExpr:
|
|
236
|
-
// Any call producing the SQL (fmt.Sprintf, a query builder, …) is
|
|
237
|
-
// dynamic — its text is not a constant the analyzer can inspect.
|
|
238
|
-
return true
|
|
239
|
-
case *ast.BinaryExpr:
|
|
240
|
-
return e.Op == token.ADD
|
|
241
|
-
default:
|
|
242
|
-
// A bare identifier is a variable-held string: treated as static (not
|
|
243
|
-
// flagged), since there is no construction site to name as the risk.
|
|
244
|
-
return false
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// literalString returns the unquoted value of a string literal, or "".
|
|
249
|
-
func literalString(expr ast.Expr) string {
|
|
250
|
-
lit, ok := expr.(*ast.BasicLit)
|
|
251
|
-
if !ok || lit.Kind != token.STRING {
|
|
252
|
-
return ""
|
|
253
|
-
}
|
|
254
|
-
s, err := strconv.Unquote(lit.Value)
|
|
255
|
-
if err != nil {
|
|
256
|
-
return lit.Value
|
|
257
|
-
}
|
|
258
|
-
return s
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// sqlFormatString returns the format string of a Sprintf-style call (its first
|
|
262
|
-
// argument when that argument is a literal), else "".
|
|
263
|
-
func sqlFormatString(expr ast.Expr) string {
|
|
264
|
-
call, ok := expr.(*ast.CallExpr)
|
|
265
|
-
if !ok || len(call.Args) == 0 {
|
|
266
|
-
return ""
|
|
267
|
-
}
|
|
268
|
-
return literalString(call.Args[0])
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// containsSQLVerb reports whether a (possibly dynamic) SQL fragment contains a
|
|
272
|
-
// DML keyword — the signal that a formatted string is actually SQL, not e.g.
|
|
273
|
-
// a `fmt.Sprintf` of a display label passed as a query argument by accident.
|
|
274
|
-
func containsSQLVerb(s string) bool {
|
|
275
|
-
up := strings.ToUpper(s)
|
|
276
|
-
return strings.Contains(up, "SELECT") ||
|
|
277
|
-
strings.Contains(up, "INSERT") ||
|
|
278
|
-
strings.Contains(up, "UPDATE") ||
|
|
279
|
-
strings.Contains(up, "DELETE") ||
|
|
280
|
-
strings.Contains(up, "FROM")
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// sqlVerb returns the leading DML keyword of a SQL statement, or "".
|
|
284
|
-
func sqlVerb(sql string) string {
|
|
285
|
-
up := strings.ToUpper(strings.TrimSpace(sql))
|
|
286
|
-
switch {
|
|
287
|
-
case strings.HasPrefix(up, "SELECT"):
|
|
288
|
-
return "SELECT"
|
|
289
|
-
case strings.HasPrefix(up, "INSERT"):
|
|
290
|
-
return "INSERT"
|
|
291
|
-
case strings.HasPrefix(up, "UPDATE"):
|
|
292
|
-
return "UPDATE"
|
|
293
|
-
case strings.HasPrefix(up, "DELETE"):
|
|
294
|
-
return "DELETE"
|
|
295
|
-
}
|
|
296
|
-
return ""
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// extractTable returns the lowercased table name referenced by FROM/INTO/UPDATE,
|
|
300
|
-
// or "". A schema prefix (`public.users`) is stripped to the bare name.
|
|
301
|
-
func extractTable(sql string) string {
|
|
302
|
-
m := tableExtractRe.FindStringSubmatch(sql)
|
|
303
|
-
if m == nil {
|
|
304
|
-
return ""
|
|
305
|
-
}
|
|
306
|
-
table := m[1]
|
|
307
|
-
if idx := strings.LastIndexByte(table, '.'); idx >= 0 {
|
|
308
|
-
table = table[idx+1:]
|
|
309
|
-
}
|
|
310
|
-
return strings.ToLower(table)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// nearMissTable reports whether a table name is in the declared known-table
|
|
314
|
-
// catalog; when it is not, and it is a singular/plural near-miss of a known
|
|
315
|
-
// name, it returns the corrected name. The `user` vs `users` singular→plural
|
|
316
|
-
// direction is what fires `unknown-table`. The catalog is empty when the caller
|
|
317
|
-
// declared no known tables — then the rule cannot accuse (fail-open, matching
|
|
318
|
-
// the TS schema reducer).
|
|
319
|
-
func nearMissTable(knownTables map[string]bool, table string) (near string, known bool) {
|
|
320
|
-
if knownTables[table] {
|
|
321
|
-
return "", true
|
|
322
|
-
}
|
|
323
|
-
if knownTables[table+"s"] {
|
|
324
|
-
return table + "s", false
|
|
325
|
-
}
|
|
326
|
-
if knownTables[table+"es"] {
|
|
327
|
-
return table + "es", false
|
|
328
|
-
}
|
|
329
|
-
if strings.HasSuffix(table, "s") {
|
|
330
|
-
if base := strings.TrimSuffix(table, "s"); knownTables[base] {
|
|
331
|
-
return base, false
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if strings.HasSuffix(table, "es") {
|
|
335
|
-
if base := strings.TrimSuffix(table, "es"); knownTables[base] {
|
|
336
|
-
return base, false
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
return "", false
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// declaredTenantTables builds the tenant-scoped table set from the resolved
|
|
343
|
-
// tiers the caller passed in `orgFilterTables`. Empty when the caller declared
|
|
344
|
-
// no tenancy — the honest result is "no tenant tables", not a guessed list.
|
|
345
|
-
func (a *Analyzer) declaredTenantTables() map[string]bool {
|
|
346
|
-
set := make(map[string]bool, len(a.options.OrgFilterTables))
|
|
347
|
-
for _, t := range a.options.OrgFilterTables {
|
|
348
|
-
set[strings.ToLower(t)] = true
|
|
349
|
-
}
|
|
350
|
-
return set
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// declaredKnownTables builds the known-table catalog from the caller's
|
|
354
|
-
// `knownTables`. An empty catalog means `unknown-table` cannot accuse.
|
|
355
|
-
func (a *Analyzer) declaredKnownTables() map[string]bool {
|
|
356
|
-
set := make(map[string]bool, len(a.options.KnownTables))
|
|
357
|
-
for _, t := range a.options.KnownTables {
|
|
358
|
-
set[strings.ToLower(t)] = true
|
|
359
|
-
}
|
|
360
|
-
return set
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// tenantPredicateRegex builds the tenant-predicate matcher from the configured
|
|
364
|
-
// tenant columns (or the default set when none were supplied). Each column name
|
|
365
|
-
// is regex-escaped and word-boundary-matched so a tenant column referenced
|
|
366
|
-
// anywhere in the SQL counts as a tenant predicate.
|
|
367
|
-
func (a *Analyzer) tenantPredicateRegex() *regexp.Regexp {
|
|
368
|
-
cols := a.options.OrgFilterColumns
|
|
369
|
-
if len(cols) == 0 {
|
|
370
|
-
cols = defaultOrgFilterColumns
|
|
371
|
-
}
|
|
372
|
-
alts := make([]string, 0, len(cols))
|
|
373
|
-
for _, c := range cols {
|
|
374
|
-
if c == "" {
|
|
375
|
-
continue
|
|
376
|
-
}
|
|
377
|
-
alts = append(alts, regexp.QuoteMeta(strings.ToLower(c)))
|
|
378
|
-
}
|
|
379
|
-
if len(alts) == 0 {
|
|
380
|
-
alts = append(alts, regexp.QuoteMeta(defaultOrgFilterColumns[0]))
|
|
381
|
-
}
|
|
382
|
-
return regexp.MustCompile(`(?i)\b(?:` + strings.Join(alts, "|") + `)\b`)
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// isWriteVerb reports whether the verb mutates rows (INSERT/UPDATE/DELETE).
|
|
386
|
-
func isWriteVerb(verb string) bool {
|
|
387
|
-
return verb == "INSERT" || verb == "UPDATE" || verb == "DELETE"
|
|
388
|
-
}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
package analyzer
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"fmt"
|
|
5
|
-
"path/filepath"
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
// Indexer creates index entries for Go entities
|
|
9
|
-
type Indexer struct {
|
|
10
|
-
parser *Parser
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// NewIndexer creates a new indexer
|
|
14
|
-
func NewIndexer(parser *Parser) *Indexer {
|
|
15
|
-
return &Indexer{
|
|
16
|
-
parser: parser,
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// GenerateIndexEntries creates index entries for all parsed entities
|
|
21
|
-
func (i *Indexer) GenerateIndexEntries() []IndexEntry {
|
|
22
|
-
// Non-nil empty slice so zero entries serialize to [] not null.
|
|
23
|
-
entries := make([]IndexEntry, 0)
|
|
24
|
-
|
|
25
|
-
// Index functions
|
|
26
|
-
functions := i.parser.ExtractFunctions()
|
|
27
|
-
for _, function := range functions {
|
|
28
|
-
entry := i.createFunctionIndexEntry(function)
|
|
29
|
-
entries = append(entries, entry)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Index structs
|
|
33
|
-
structs := i.parser.ExtractStructs()
|
|
34
|
-
for _, structInfo := range structs {
|
|
35
|
-
entry := i.createStructIndexEntry(structInfo)
|
|
36
|
-
entries = append(entries, entry)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Index interfaces
|
|
40
|
-
interfaces := i.parser.ExtractInterfaces()
|
|
41
|
-
for _, interfaceInfo := range interfaces {
|
|
42
|
-
entry := i.createInterfaceIndexEntry(interfaceInfo)
|
|
43
|
-
entries = append(entries, entry)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return entries
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// createFunctionIndexEntry creates an index entry for a function
|
|
50
|
-
func (i *Indexer) createFunctionIndexEntry(function Function) IndexEntry {
|
|
51
|
-
return IndexEntry{
|
|
52
|
-
ID: i.generateID("function", function.File, function.Name, function.StartLine),
|
|
53
|
-
Name: function.Name,
|
|
54
|
-
Type: "function",
|
|
55
|
-
Language: "go",
|
|
56
|
-
File: function.File,
|
|
57
|
-
Signature: function.Signature,
|
|
58
|
-
Parameters: function.Parameters,
|
|
59
|
-
Purpose: function.Purpose,
|
|
60
|
-
Context: i.generateContext(function.EntityInfo),
|
|
61
|
-
StartLine: function.StartLine,
|
|
62
|
-
EndLine: function.EndLine,
|
|
63
|
-
Metadata: map[string]interface{}{
|
|
64
|
-
"isMethod": function.IsMethod,
|
|
65
|
-
"isExported": function.IsExported,
|
|
66
|
-
"receiver": function.Receiver,
|
|
67
|
-
"returnType": function.ReturnType,
|
|
68
|
-
"complexity": function.Complexity,
|
|
69
|
-
"package": function.Package,
|
|
70
|
-
"dependencies": function.Dependencies,
|
|
71
|
-
},
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// createStructIndexEntry creates an index entry for a struct
|
|
76
|
-
func (i *Indexer) createStructIndexEntry(structInfo Struct) IndexEntry {
|
|
77
|
-
return IndexEntry{
|
|
78
|
-
ID: i.generateID("struct", structInfo.File, structInfo.Name, structInfo.StartLine),
|
|
79
|
-
Name: structInfo.Name,
|
|
80
|
-
Type: "struct",
|
|
81
|
-
Language: "go",
|
|
82
|
-
File: structInfo.File,
|
|
83
|
-
Signature: structInfo.Signature,
|
|
84
|
-
Purpose: i.generateStructPurpose(structInfo),
|
|
85
|
-
Context: i.generateContext(structInfo.EntityInfo),
|
|
86
|
-
StartLine: structInfo.StartLine,
|
|
87
|
-
EndLine: structInfo.EndLine,
|
|
88
|
-
Metadata: map[string]interface{}{
|
|
89
|
-
"isExported": structInfo.IsExported,
|
|
90
|
-
"fieldCount": len(structInfo.Fields),
|
|
91
|
-
"fields": i.serializeFields(structInfo.Fields),
|
|
92
|
-
"methods": structInfo.Methods,
|
|
93
|
-
"package": structInfo.Package,
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// createInterfaceIndexEntry creates an index entry for an interface
|
|
99
|
-
func (i *Indexer) createInterfaceIndexEntry(interfaceInfo Interface) IndexEntry {
|
|
100
|
-
return IndexEntry{
|
|
101
|
-
ID: i.generateID("interface", interfaceInfo.File, interfaceInfo.Name, interfaceInfo.StartLine),
|
|
102
|
-
Name: interfaceInfo.Name,
|
|
103
|
-
Type: "interface",
|
|
104
|
-
Language: "go",
|
|
105
|
-
File: interfaceInfo.File,
|
|
106
|
-
Signature: interfaceInfo.Signature,
|
|
107
|
-
Purpose: i.generateInterfacePurpose(interfaceInfo),
|
|
108
|
-
Context: i.generateContext(interfaceInfo.EntityInfo),
|
|
109
|
-
StartLine: interfaceInfo.StartLine,
|
|
110
|
-
EndLine: interfaceInfo.EndLine,
|
|
111
|
-
Metadata: map[string]interface{}{
|
|
112
|
-
"isExported": interfaceInfo.IsExported,
|
|
113
|
-
"methodCount": len(interfaceInfo.Methods),
|
|
114
|
-
"methods": i.serializeMethods(interfaceInfo.Methods),
|
|
115
|
-
"package": interfaceInfo.Package,
|
|
116
|
-
},
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// generateID creates a unique ID for an entity
|
|
121
|
-
func (i *Indexer) generateID(entityType, file, name string, line int) string {
|
|
122
|
-
return fmt.Sprintf("go:%s:%s:%s:%d", entityType, filepath.Base(file), name, line)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// generateContext creates context information for an entity
|
|
126
|
-
func (i *Indexer) generateContext(entity EntityInfo) string {
|
|
127
|
-
return fmt.Sprintf("Go %s in package %s at %s:%d",
|
|
128
|
-
entity.Type, entity.Package, filepath.Base(entity.File), entity.StartLine)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// generateStructPurpose creates a purpose description for a struct
|
|
132
|
-
func (i *Indexer) generateStructPurpose(structInfo Struct) string {
|
|
133
|
-
if structInfo.Purpose != "" {
|
|
134
|
-
return structInfo.Purpose
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
fieldCount := len(structInfo.Fields)
|
|
138
|
-
if fieldCount == 0 {
|
|
139
|
-
return fmt.Sprintf("Empty struct %s", structInfo.Name)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return fmt.Sprintf("Data structure with %d fields representing %s",
|
|
143
|
-
fieldCount, i.inferStructPurpose(structInfo))
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// generateInterfacePurpose creates a purpose description for an interface
|
|
147
|
-
func (i *Indexer) generateInterfacePurpose(interfaceInfo Interface) string {
|
|
148
|
-
if interfaceInfo.Purpose != "" {
|
|
149
|
-
return interfaceInfo.Purpose
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
methodCount := len(interfaceInfo.Methods)
|
|
153
|
-
if methodCount == 0 {
|
|
154
|
-
return fmt.Sprintf("Empty interface %s", interfaceInfo.Name)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return fmt.Sprintf("Interface defining %d methods for %s behavior",
|
|
158
|
-
methodCount, i.inferInterfacePurpose(interfaceInfo))
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// inferStructPurpose tries to infer the purpose of a struct from its name and fields
|
|
162
|
-
func (i *Indexer) inferStructPurpose(structInfo Struct) string {
|
|
163
|
-
name := structInfo.Name
|
|
164
|
-
|
|
165
|
-
// Common patterns in Go struct names
|
|
166
|
-
if containsAny(name, []string{"Config", "Configuration"}) {
|
|
167
|
-
return "configuration data"
|
|
168
|
-
}
|
|
169
|
-
if containsAny(name, []string{"Request", "Req"}) {
|
|
170
|
-
return "request data"
|
|
171
|
-
}
|
|
172
|
-
if containsAny(name, []string{"Response", "Resp"}) {
|
|
173
|
-
return "response data"
|
|
174
|
-
}
|
|
175
|
-
if containsAny(name, []string{"Handler", "Controller"}) {
|
|
176
|
-
return "request handling"
|
|
177
|
-
}
|
|
178
|
-
if containsAny(name, []string{"Service", "Manager"}) {
|
|
179
|
-
return "business logic"
|
|
180
|
-
}
|
|
181
|
-
if containsAny(name, []string{"Repository", "Repo", "Store"}) {
|
|
182
|
-
return "data access"
|
|
183
|
-
}
|
|
184
|
-
if containsAny(name, []string{"Model", "Entity"}) {
|
|
185
|
-
return "data model"
|
|
186
|
-
}
|
|
187
|
-
if containsAny(name, []string{"Client", "Adapter"}) {
|
|
188
|
-
return "external integration"
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return "entity data"
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// inferInterfacePurpose tries to infer the purpose of an interface from its name and methods
|
|
195
|
-
func (i *Indexer) inferInterfacePurpose(interfaceInfo Interface) string {
|
|
196
|
-
name := interfaceInfo.Name
|
|
197
|
-
|
|
198
|
-
// Common patterns in Go interface names
|
|
199
|
-
if containsAny(name, []string{"Reader", "Writer", "ReadWriter"}) {
|
|
200
|
-
return "I/O operations"
|
|
201
|
-
}
|
|
202
|
-
if containsAny(name, []string{"Handler", "Processor"}) {
|
|
203
|
-
return "request processing"
|
|
204
|
-
}
|
|
205
|
-
if containsAny(name, []string{"Repository", "Store"}) {
|
|
206
|
-
return "data storage"
|
|
207
|
-
}
|
|
208
|
-
if containsAny(name, []string{"Service", "Provider"}) {
|
|
209
|
-
return "service operations"
|
|
210
|
-
}
|
|
211
|
-
if containsAny(name, []string{"Client", "Adapter"}) {
|
|
212
|
-
return "external communication"
|
|
213
|
-
}
|
|
214
|
-
if containsAny(name, []string{"Validator", "Checker"}) {
|
|
215
|
-
return "validation logic"
|
|
216
|
-
}
|
|
217
|
-
if containsAny(name, []string{"Builder", "Factory"}) {
|
|
218
|
-
return "object creation"
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return "behavioral contract"
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// serializeFields converts struct fields to a serializable format
|
|
225
|
-
func (i *Indexer) serializeFields(fields []Field) []map[string]interface{} {
|
|
226
|
-
var serialized []map[string]interface{}
|
|
227
|
-
|
|
228
|
-
for _, field := range fields {
|
|
229
|
-
serialized = append(serialized, map[string]interface{}{
|
|
230
|
-
"name": field.Name,
|
|
231
|
-
"type": field.Type,
|
|
232
|
-
"tag": field.Tag,
|
|
233
|
-
"isExported": field.IsExported,
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return serialized
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// serializeMethods converts interface methods to a serializable format
|
|
241
|
-
func (i *Indexer) serializeMethods(methods []Method) []map[string]interface{} {
|
|
242
|
-
var serialized []map[string]interface{}
|
|
243
|
-
|
|
244
|
-
for _, method := range methods {
|
|
245
|
-
serialized = append(serialized, map[string]interface{}{
|
|
246
|
-
"name": method.Name,
|
|
247
|
-
"signature": method.Signature,
|
|
248
|
-
"parameters": method.Parameters,
|
|
249
|
-
"returnType": method.ReturnType,
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return serialized
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// containsAny checks if a string contains any of the given substrings
|
|
257
|
-
func containsAny(str string, substrings []string) bool {
|
|
258
|
-
for _, substring := range substrings {
|
|
259
|
-
if len(str) >= len(substring) {
|
|
260
|
-
for i := 0; i <= len(str)-len(substring); i++ {
|
|
261
|
-
if str[i:i+len(substring)] == substring {
|
|
262
|
-
return true
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
return false
|
|
268
|
-
}
|