code-auditor-mcp 3.10.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +197 -0
- package/dist/__tests__/fixtures/spec-52/item-04-storage-sql-exec-in-loop.d.ts.map +1 -1
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts +4 -4
- package/dist/__tests__/fixtures/spec-52/item-05-upsert-forms.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +2 -2
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +1 -0
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +3 -3
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +62 -17
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +14 -16
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +30 -25
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/provenance.d.ts +10 -1
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +138 -1
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +34 -116
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +5 -3
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +199 -55
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +2 -2
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +10 -17
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +6 -6
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +13 -13
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +266 -101
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -2
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +14 -13
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +11 -11
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +19 -7
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSecretsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -2
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +93 -46
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +58 -34
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +239 -97
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/config.d.ts +6 -6
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/analyzers/universal/schema/types.d.ts +10 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/violations.d.ts +1 -1
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -1
- package/dist/auditRouter.js +7 -7
- package/dist/auditRouter.js.map +1 -1
- package/dist/auditRunner.d.ts +1 -1
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +53 -8
- package/dist/auditRunner.js.map +1 -1
- package/dist/churn/churnExtractor.js.map +1 -1
- package/dist/cli.js +52 -24
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts +1 -1
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +164 -18
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/codeIndexService.d.ts.map +1 -1
- package/dist/codeIndexService.js.map +1 -1
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +89 -28
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configLoader.js +1 -1
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +2 -2
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +10 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +3 -10
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/coverage/lcovParser.js.map +1 -1
- package/dist/daemon/args.js.map +1 -1
- package/dist/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +50 -46
- package/dist/daemon/core.js.map +1 -1
- package/dist/daemon/lspServer.d.ts.map +1 -1
- package/dist/daemon/lspServer.js +1 -1
- package/dist/daemon/lspServer.js.map +1 -1
- package/dist/daemon/main.d.ts.map +1 -1
- package/dist/daemon/main.js +14 -4
- package/dist/daemon/main.js.map +1 -1
- package/dist/daemon/resolve.js.map +1 -1
- package/dist/daemon/socketServer.d.ts.map +1 -1
- package/dist/enforcement/gate.js +2 -2
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +47 -42
- package/dist/functionScanner.js.map +1 -1
- package/dist/generators/AWSQConfigGenerator.d.ts.map +1 -1
- package/dist/generators/AiderConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.d.ts.map +1 -1
- package/dist/generators/BaseConfigGenerator.js.map +1 -1
- package/dist/generators/ClaudeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ClineConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodeiumConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CodexConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.d.ts.map +1 -1
- package/dist/generators/ConfigGeneratorFactory.js.map +1 -1
- package/dist/generators/ContinueConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CopilotConfigGenerator.d.ts.map +1 -1
- package/dist/generators/CursorConfigGenerator.d.ts.map +1 -1
- package/dist/generators/GeminiConfigGenerator.d.ts.map +1 -1
- package/dist/generators/JetBrainsConfigGenerator.d.ts.map +1 -1
- package/dist/generators/VSCodeConfigGenerator.d.ts.map +1 -1
- package/dist/generators/ZCodeConfigGenerator.d.ts.map +1 -1
- package/dist/graph/importClassification.d.ts +114 -0
- package/dist/graph/importClassification.d.ts.map +1 -0
- package/dist/graph/importClassification.js +393 -0
- package/dist/graph/importClassification.js.map +1 -0
- package/dist/graph/importGraph.d.ts +1 -0
- package/dist/graph/importGraph.d.ts.map +1 -1
- package/dist/graph/importGraph.js +1 -1
- package/dist/graph/importGraph.js.map +1 -1
- package/dist/graph/outputFormatter.js.map +1 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +1 -1
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hooks/core.d.ts +1 -1
- package/dist/hooks/core.d.ts.map +1 -1
- package/dist/hooks/core.js +4 -4
- package/dist/hooks/core.js.map +1 -1
- package/dist/hooks/cursor.d.ts.map +1 -1
- package/dist/hooks/cursor.js +2 -2
- package/dist/hooks/cursor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installer.d.ts +13 -7
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +25 -15
- package/dist/installer.js.map +1 -1
- package/dist/invariants/invariant-rules.schema.json +1 -1
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.js +1 -1
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/invariants/types.d.ts +1 -1
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +3 -2
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +1 -1
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +20 -11
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +151 -51
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +10 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +30 -7
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +1 -19
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +33 -116
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +8 -8
- package/dist/languages/go/analyzer-src/solid.go +5 -5
- package/dist/languages/goBinary.d.ts +46 -0
- package/dist/languages/goBinary.d.ts.map +1 -0
- package/dist/languages/goBinary.js +169 -0
- package/dist/languages/goBinary.js.map +1 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +1 -28
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +3 -42
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +2 -1
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/tree-sitter/rawNode.d.ts +31 -0
- package/dist/languages/tree-sitter/rawNode.d.ts.map +1 -0
- package/dist/languages/tree-sitter/rawNode.js +38 -0
- package/dist/languages/tree-sitter/rawNode.js.map +1 -0
- package/dist/languages/types.d.ts +15 -21
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +12 -32
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +103 -203
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +3 -0
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +45 -9
- package/dist/ledger.js.map +1 -1
- package/dist/mcp-tools/projectTasks.d.ts +37 -37
- package/dist/mcp-tools/projectTasks.js +2 -2
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +17 -17
- package/dist/mcp-tools/whitelistTools.d.ts.map +1 -1
- package/dist/mcp-tools/workflowGuide.js +3 -3
- 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 +13 -13
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp-ui-simple.js +7 -7
- package/dist/mcp-ui-simple.js.map +1 -1
- package/dist/mcp.js +14 -14
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.js +7 -7
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/mcpAutoIndex.js +1 -1
- package/dist/mcpAutoIndex.js.map +1 -1
- package/dist/mcpToolErrors.d.ts.map +1 -1
- package/dist/mcpToolErrors.js.map +1 -1
- package/dist/native-bootstrap.js.map +1 -1
- package/dist/nextFile.d.ts +2 -2
- package/dist/nextFile.d.ts.map +1 -1
- package/dist/nextFile.js +19 -5
- package/dist/nextFile.js.map +1 -1
- package/dist/nextFileIncremental.d.ts +10 -2
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +45 -16
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipeline.d.ts +2 -2
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +35 -0
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +17 -0
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +356 -197
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +6 -6
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +11 -11
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +2 -2
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/reportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +1 -1
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/scripts/benchmarkWorkers.js +1 -1
- package/dist/scripts/benchmarkWorkers.js.map +1 -1
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/services/ProjectTaskRepository.d.ts.map +1 -1
- package/dist/services/ProjectTaskRepository.js.map +1 -1
- package/dist/services/auditJobService.js.map +1 -1
- package/dist/services/fileAccounting.d.ts.map +1 -1
- package/dist/services/projectTaskHelpers.js.map +1 -1
- package/dist/services/whitelistService.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.d.ts.map +1 -1
- package/dist/sqlite/betterSqlite3.js.map +1 -1
- package/dist/sqlite/nodeSqlite.d.ts.map +1 -1
- package/dist/sqlite/nodeSqlite.js.map +1 -1
- package/dist/structuralSignature.js.map +1 -1
- package/dist/styles/cssAstExtractor.d.ts +2 -2
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +67 -111
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +19 -6
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindProbe.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js.map +1 -1
- package/dist/tool-registry.d.ts.map +1 -1
- package/dist/types/crossLanguage.d.ts +1 -1
- package/dist/types/crossLanguage.d.ts.map +1 -1
- package/dist/types.d.ts +67 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -5
- package/dist/types.js.map +1 -1
- package/dist/ui/InteractivePrompts.d.ts.map +1 -1
- package/dist/ui/InteractivePrompts.js.map +1 -1
- package/dist/utils/astParser.d.ts +1 -17
- package/dist/utils/astParser.d.ts.map +1 -1
- package/dist/utils/astParser.js +18 -60
- package/dist/utils/astParser.js.map +1 -1
- package/dist/utils/astUtils.d.ts +12 -12
- package/dist/utils/astUtils.d.ts.map +1 -1
- package/dist/utils/astUtils.js +83 -82
- package/dist/utils/astUtils.js.map +1 -1
- package/dist/utils/componentPatterns.js.map +1 -1
- package/dist/utils/componentResponsibility.d.ts +8 -8
- package/dist/utils/componentResponsibility.d.ts.map +1 -1
- package/dist/utils/componentResponsibility.js +33 -38
- package/dist/utils/componentResponsibility.js.map +1 -1
- package/dist/utils/dependencyExtractor.d.ts +3 -3
- package/dist/utils/dependencyExtractor.d.ts.map +1 -1
- package/dist/utils/dependencyExtractor.js +26 -30
- package/dist/utils/dependencyExtractor.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +65 -0
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/dist/utils/jsDocParser.d.ts +6 -6
- package/dist/utils/jsDocParser.d.ts.map +1 -1
- package/dist/utils/jsDocParser.js +18 -21
- package/dist/utils/jsDocParser.js.map +1 -1
- package/dist/utils/reactDetection.d.ts +7 -7
- package/dist/utils/reactDetection.d.ts.map +1 -1
- package/dist/utils/reactDetection.js +42 -49
- package/dist/utils/reactDetection.js.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.d.ts.map +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/version.generated.js.map +1 -1
- package/dist/workers/auditWorker.js.map +1 -1
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +5 -4
- package/plugin/hooks/hooks.json +11 -0
- package/plugin/scripts/hook-common.sh +85 -31
- package/plugin/scripts/hook-warm.sh +44 -0
- package/plugin/skills/code-auditor/SKILL-RULE-KINDS.md +28 -21
- package/plugin/skills/code-auditor/SKILL.md +10 -10
- package/server.json +2 -2
package/dist/pipelineAdapters.js
CHANGED
|
@@ -19,19 +19,27 @@ import { createHash } from 'crypto';
|
|
|
19
19
|
import path from 'path';
|
|
20
20
|
import { RULE_REGISTRY } from './analyzers/ruleRegistry.js';
|
|
21
21
|
import { TYPESCRIPT_EXTENSIONS, JAVASCRIPT_EXTENSIONS, getLanguageFromPath } from './utils/fileDiscovery.js';
|
|
22
|
-
import { walkAST, isExported, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
|
|
22
|
+
import { walkAST, isExported, getNodeText, getFieldNode, getLineAndColumn, calculateComplexity, getFunctionBody, } from './languages/adapterBridge.js';
|
|
23
23
|
import { buildImportMap, extractFunctionCalls, } from './utils/dependencyExtractor.js';
|
|
24
|
-
import {
|
|
24
|
+
import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
|
|
25
25
|
import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
|
|
26
26
|
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
|
|
27
27
|
import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
|
|
28
|
-
import { applyMigrationOps } from './analyzers/universal/schema/migrations.js';
|
|
28
|
+
import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
|
|
29
29
|
// ── Rule ID helpers ──────────────────────────────────────────────────────────
|
|
30
30
|
function getRuleIdsFor(name) {
|
|
31
31
|
return Object.entries(RULE_REGISTRY)
|
|
32
32
|
.filter(([, entry]) => entry.analyzer === name)
|
|
33
33
|
.map(([id]) => id);
|
|
34
34
|
}
|
|
35
|
+
/** Join a list of names as prose: "a", "a and b", "a, b and c". */
|
|
36
|
+
function joinEnglish(names) {
|
|
37
|
+
if (names.length === 0)
|
|
38
|
+
return '';
|
|
39
|
+
if (names.length === 1)
|
|
40
|
+
return names[0];
|
|
41
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
42
|
+
}
|
|
35
43
|
// ── Lazy import singleton ────────────────────────────────────────────────────
|
|
36
44
|
function lazySingleton(loader) {
|
|
37
45
|
let promise = null;
|
|
@@ -153,10 +161,6 @@ function computeContentHash(body, signature) {
|
|
|
153
161
|
const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
|
|
154
162
|
return createHash('sha256').update(normalized).digest('hex');
|
|
155
163
|
}
|
|
156
|
-
/** Raw text from an ASTNode (tree-sitter node stored on `.raw`). */
|
|
157
|
-
function rawText(node) {
|
|
158
|
-
return node.raw?.text ?? '';
|
|
159
|
-
}
|
|
160
164
|
export function createFunctionIndexVisitor() {
|
|
161
165
|
return {
|
|
162
166
|
name: 'function-index',
|
|
@@ -177,8 +181,18 @@ export function createFunctionIndexVisitor() {
|
|
|
177
181
|
table: 'functions',
|
|
178
182
|
data: { _action: 'clear-by-file', file_path: filePath },
|
|
179
183
|
});
|
|
184
|
+
// Spec 60 — import classification is TS/JS-only: the normalization probe
|
|
185
|
+
// list has no `.go` extension, so applying it to Go relative imports would
|
|
186
|
+
// manufacture false `internal-broken` rows. Clear + re-emit only for TS/JS.
|
|
187
|
+
const isTsJs = lang === 'typescript' || lang === 'javascript';
|
|
188
|
+
if (isTsJs) {
|
|
189
|
+
indexFacts.push({
|
|
190
|
+
table: 'import_specifiers',
|
|
191
|
+
data: { _action: 'clear-by-file', file_path: filePath },
|
|
192
|
+
});
|
|
193
|
+
}
|
|
180
194
|
// Build import map once per file for resolving call targets
|
|
181
|
-
const importMap = buildImportMap(root);
|
|
195
|
+
const importMap = buildImportMap(root, sourceCode);
|
|
182
196
|
// Collect function-like nodes in two passes:
|
|
183
197
|
// 1) function_declaration + method_definition nodes
|
|
184
198
|
// 2) arrow functions assigned to variables (variable_declarator children)
|
|
@@ -189,12 +203,11 @@ export function createFunctionIndexVisitor() {
|
|
|
189
203
|
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
190
204
|
if (!nameNode)
|
|
191
205
|
return;
|
|
192
|
-
const name =
|
|
206
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
193
207
|
if (!name)
|
|
194
208
|
return;
|
|
195
209
|
const { line } = getLineAndColumn(node);
|
|
196
|
-
const
|
|
197
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
210
|
+
const endLine = node.location.end.line;
|
|
198
211
|
const body = getFunctionBody(node, sourceCode);
|
|
199
212
|
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
200
213
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -215,7 +228,7 @@ export function createFunctionIndexVisitor() {
|
|
|
215
228
|
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
216
229
|
if (!nameNode)
|
|
217
230
|
return;
|
|
218
|
-
const methodName =
|
|
231
|
+
const methodName = getNodeText(nameNode, sourceCode);
|
|
219
232
|
if (!methodName)
|
|
220
233
|
return;
|
|
221
234
|
// Walk up to find class name
|
|
@@ -225,14 +238,13 @@ export function createFunctionIndexVisitor() {
|
|
|
225
238
|
if (parent.type === 'class_declaration') {
|
|
226
239
|
const cn = parent.children?.find((c) => c.type === 'identifier');
|
|
227
240
|
if (cn)
|
|
228
|
-
className =
|
|
241
|
+
className = getNodeText(cn, sourceCode);
|
|
229
242
|
break;
|
|
230
243
|
}
|
|
231
244
|
parent = parent.parent;
|
|
232
245
|
}
|
|
233
246
|
const { line } = getLineAndColumn(node);
|
|
234
|
-
const
|
|
235
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
247
|
+
const endLine = node.location.end.line;
|
|
236
248
|
const body = getFunctionBody(node, sourceCode);
|
|
237
249
|
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
238
250
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -257,14 +269,13 @@ export function createFunctionIndexVisitor() {
|
|
|
257
269
|
const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
|
|
258
270
|
if (!nameNode || !arrowFunc)
|
|
259
271
|
return;
|
|
260
|
-
const name =
|
|
272
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
261
273
|
if (!name)
|
|
262
274
|
return;
|
|
263
275
|
// Don't duplicate if already covered as function_declaration
|
|
264
276
|
// (shouldn't happen — function_declaration is a different node type)
|
|
265
277
|
const { line } = getLineAndColumn(arrowFunc);
|
|
266
|
-
const
|
|
267
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
278
|
+
const endLine = arrowFunc.location.end.line;
|
|
268
279
|
const body = getFunctionBody(arrowFunc, sourceCode);
|
|
269
280
|
const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
|
|
270
281
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -290,12 +301,12 @@ export function createFunctionIndexVisitor() {
|
|
|
290
301
|
filePath.endsWith('.jsx') ||
|
|
291
302
|
(filePath.endsWith('.js') && hasReactImport)) {
|
|
292
303
|
walkAST(root, (node) => {
|
|
293
|
-
if (!isReactComponent(node))
|
|
304
|
+
if (!isReactComponent(node, sourceCode))
|
|
294
305
|
return;
|
|
295
|
-
const ct = detectComponentType(node);
|
|
306
|
+
const ct = detectComponentType(node, sourceCode);
|
|
296
307
|
if (!ct)
|
|
297
308
|
return;
|
|
298
|
-
const cName = getComponentName(node);
|
|
309
|
+
const cName = getComponentName(node, sourceCode);
|
|
299
310
|
if (!cName || cName === 'AnonymousComponent')
|
|
300
311
|
return;
|
|
301
312
|
const existing = fnEntries.find((f) => f.name === cName);
|
|
@@ -308,11 +319,7 @@ export function createFunctionIndexVisitor() {
|
|
|
308
319
|
// New entry — class component, function_expression, or memo/forwardRef
|
|
309
320
|
// wrapper not already captured by passes 1 or 2.
|
|
310
321
|
const { line } = getLineAndColumn(node);
|
|
311
|
-
const
|
|
312
|
-
const endLine = line +
|
|
313
|
-
(raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) -
|
|
314
|
-
(raw?.startPosition?.row ?? line) +
|
|
315
|
-
1;
|
|
322
|
+
const endLine = node.location.end.line;
|
|
316
323
|
const body = getFunctionBody(node, sourceCode);
|
|
317
324
|
fnEntries.push({
|
|
318
325
|
name: cName,
|
|
@@ -335,7 +342,6 @@ export function createFunctionIndexVisitor() {
|
|
|
335
342
|
isExported: fn.isExported,
|
|
336
343
|
complexity: fn.complexity,
|
|
337
344
|
functionCalls: fn.functionCalls,
|
|
338
|
-
body: fn.body,
|
|
339
345
|
};
|
|
340
346
|
const contentHash = computeContentHash(fn.body, '');
|
|
341
347
|
const now = new Date().toISOString();
|
|
@@ -379,29 +385,66 @@ export function createFunctionIndexVisitor() {
|
|
|
379
385
|
isRequire: false,
|
|
380
386
|
line: imp.location.start.line,
|
|
381
387
|
}));
|
|
388
|
+
// Spec 60.1 — classify each static import specifier and emit one row per
|
|
389
|
+
// occurrence to `import_specifiers`. The corpus file set is the stage-1
|
|
390
|
+
// discovery list, threaded here as `_infra.files` (merged into
|
|
391
|
+
// `context.config` by pipeline.ts). Existence is checked against that
|
|
392
|
+
// in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
|
|
393
|
+
// `paths` patterns are threaded via `_infra` and passed to the classifier.
|
|
394
|
+
if (isTsJs) {
|
|
395
|
+
// Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
|
|
396
|
+
// (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
|
|
397
|
+
// the audit's analysis list (already pruned by includePaths/excludePaths),
|
|
398
|
+
// which drops `.json`/other non-analyzed extensions and would mis-classify
|
|
399
|
+
// a real `./invariant-rules.schema.json` as `internal-broken`.
|
|
400
|
+
const corpusFiles = new Set((context.config.corpusFiles) ??
|
|
401
|
+
(context.config.files) ??
|
|
402
|
+
[]);
|
|
403
|
+
const virtualModules = context.config.importVirtualModules
|
|
404
|
+
?? DEFAULT_VIRTUAL_MODULES;
|
|
405
|
+
const tsconfigAliases = context.config.tsconfigAliases;
|
|
406
|
+
for (const imp of staticImportInfos) {
|
|
407
|
+
const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
|
|
408
|
+
virtualModules,
|
|
409
|
+
aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
|
|
410
|
+
pathMappings: tsconfigAliases?.paths ?? {},
|
|
411
|
+
baseUrl: tsconfigAliases?.baseUrl,
|
|
412
|
+
projectRoot: context.projectRoot,
|
|
413
|
+
});
|
|
414
|
+
indexFacts.push({
|
|
415
|
+
table: 'import_specifiers',
|
|
416
|
+
data: {
|
|
417
|
+
file_path: filePath,
|
|
418
|
+
specifier: imp.source,
|
|
419
|
+
classification,
|
|
420
|
+
resolved_path: resolvedPath ?? null,
|
|
421
|
+
line: imp.location.start.line,
|
|
422
|
+
},
|
|
423
|
+
conflictKey: 'file_path, specifier, line',
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
382
427
|
// Dynamic import() and require() — first attempt with pure NodePattern
|
|
383
428
|
// Plan note: import keyword is an anonymous tree-sitter node, so
|
|
384
429
|
// hasChild cannot see it. We use `custom` with raw node access.
|
|
385
430
|
const dynamicCallNodes = langAdapter.findNodes(langAst, {
|
|
386
431
|
type: 'call_expression',
|
|
387
432
|
custom: (node) => {
|
|
388
|
-
const
|
|
389
|
-
const fn = raw?.firstChild;
|
|
433
|
+
const fn = node.children?.[0];
|
|
390
434
|
return (fn?.type === 'import') ||
|
|
391
|
-
(fn?.type === 'identifier' && fn
|
|
435
|
+
(fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
392
436
|
},
|
|
393
437
|
});
|
|
394
438
|
const dynamicImports = [];
|
|
395
439
|
for (const node of dynamicCallNodes) {
|
|
396
|
-
const
|
|
397
|
-
const fn = raw?.firstChild;
|
|
440
|
+
const fn = node.children?.[0];
|
|
398
441
|
const isImport = fn?.type === 'import';
|
|
399
|
-
const isRequire = !isImport && (fn?.type === 'identifier' && fn
|
|
400
|
-
// Walk the
|
|
401
|
-
const argsNode =
|
|
442
|
+
const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
443
|
+
// Walk the AST children to find the string argument
|
|
444
|
+
const argsNode = node.children?.find((c) => c.type === 'arguments');
|
|
402
445
|
const stringNode = argsNode?.children?.find((c) => c.type === 'string');
|
|
403
446
|
if (stringNode) {
|
|
404
|
-
const text = stringNode
|
|
447
|
+
const text = getNodeText(stringNode, sourceCode);
|
|
405
448
|
if (text.length >= 2) {
|
|
406
449
|
dynamicImports.push({
|
|
407
450
|
moduleSpecifier: text.slice(1, -1), // strip quotes
|
|
@@ -531,8 +574,8 @@ export function createStylesCssVisitor() {
|
|
|
531
574
|
facts: {
|
|
532
575
|
[filePath]: {
|
|
533
576
|
declarations: extractDeclarationsFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
534
|
-
tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath),
|
|
535
|
-
classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath),
|
|
577
|
+
tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
578
|
+
classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
536
579
|
},
|
|
537
580
|
},
|
|
538
581
|
};
|
|
@@ -815,7 +858,12 @@ export function createStylesReducer() {
|
|
|
815
858
|
const factsConsumed = context.indexHandle.count('style_declarations')
|
|
816
859
|
+ context.indexHandle.count('style_tokens')
|
|
817
860
|
+ context.indexHandle.count('style_class_usage');
|
|
818
|
-
return {
|
|
861
|
+
return {
|
|
862
|
+
violations: result.violations ?? [],
|
|
863
|
+
facts: {},
|
|
864
|
+
factsConsumed,
|
|
865
|
+
...((result.diagnostics?.length ?? 0) > 0 && { diagnostics: result.diagnostics }),
|
|
866
|
+
};
|
|
819
867
|
}
|
|
820
868
|
catch {
|
|
821
869
|
return { violations: [], facts: {} };
|
|
@@ -940,35 +988,31 @@ export function createCrossDomainReducer() {
|
|
|
940
988
|
// short-circuits with `notRunReason` BEFORE any corpus-wide work — the gate is in
|
|
941
989
|
// the design, not retrofitted after the budget blows. This is the shape that made
|
|
942
990
|
// the styles reducer 587ms; it must never run over a partial corpus.
|
|
943
|
-
// ──
|
|
944
|
-
function clRawField(raw, field) {
|
|
945
|
-
return typeof raw?.childForFieldName === 'function' ? raw.childForFieldName(field) : null;
|
|
946
|
-
}
|
|
947
|
-
function clRawNamedChild(raw, type) {
|
|
948
|
-
return raw?.namedChildren?.find((c) => c.type === type) ?? null;
|
|
949
|
-
}
|
|
991
|
+
// ── Entity helpers (ASTNode-based; parser-specific nodes stay in languages/) ──
|
|
950
992
|
/** A function body node — `statement_block` (TS/JS) or `block` (Go). */
|
|
951
|
-
function
|
|
952
|
-
return
|
|
993
|
+
function clBody(node) {
|
|
994
|
+
return node.children?.find((c) => c.type === 'statement_block' || c.type === 'block');
|
|
953
995
|
}
|
|
954
|
-
function clSignatureText(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const end = body ? body.startIndex : raw.endIndex;
|
|
959
|
-
return sourceCode.slice(raw.startIndex, end).trim();
|
|
996
|
+
function clSignatureText(node, sourceCode) {
|
|
997
|
+
const body = clBody(node);
|
|
998
|
+
const end = body ? body.range[0] : node.range[1];
|
|
999
|
+
return sourceCode.slice(node.range[0], end).trim();
|
|
960
1000
|
}
|
|
961
|
-
function clBodyText(
|
|
962
|
-
const body =
|
|
963
|
-
return body ? sourceCode.slice(body.
|
|
1001
|
+
function clBodyText(node, sourceCode) {
|
|
1002
|
+
const body = clBody(node);
|
|
1003
|
+
return body ? sourceCode.slice(body.range[0], body.range[1]) : '';
|
|
964
1004
|
}
|
|
965
1005
|
/** Prefer the `name` field (works across TS + Go), fall back to identifier children. */
|
|
966
|
-
function clNodeName(
|
|
967
|
-
const field =
|
|
968
|
-
if (field
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1006
|
+
function clNodeName(node, sourceCode) {
|
|
1007
|
+
const field = getFieldNode(node, 'name');
|
|
1008
|
+
if (field) {
|
|
1009
|
+
const text = getNodeText(field, sourceCode);
|
|
1010
|
+
if (text)
|
|
1011
|
+
return text;
|
|
1012
|
+
}
|
|
1013
|
+
const id = node.children?.find((c) => c.type === 'identifier')
|
|
1014
|
+
?? node.children?.find((c) => c.type === 'property_identifier');
|
|
1015
|
+
return id ? getNodeText(id, sourceCode) : undefined;
|
|
972
1016
|
}
|
|
973
1017
|
function clEntityId(filePath, type, name, line) {
|
|
974
1018
|
return `${filePath}::${type}::${name}::${line}`;
|
|
@@ -977,25 +1021,20 @@ function clIsExportedGo(name) {
|
|
|
977
1021
|
return name.length > 0 && name[0] === name[0].toUpperCase();
|
|
978
1022
|
}
|
|
979
1023
|
/** Collect callee names from a function's body (TS `call_expression` / Go `call_expression`). */
|
|
980
|
-
function clCollectCallees(
|
|
1024
|
+
function clCollectCallees(node, sourceCode) {
|
|
981
1025
|
const names = new Set();
|
|
982
|
-
const walk = (
|
|
983
|
-
if (
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const fn = typeof node.childForFieldName === 'function'
|
|
987
|
-
? node.childForFieldName('function')
|
|
988
|
-
: null;
|
|
989
|
-
const callee = fn ?? node.namedChildren?.[0];
|
|
990
|
-
const text = callee?.text?.trim();
|
|
1026
|
+
const walk = (n) => {
|
|
1027
|
+
if (n.type === 'call_expression') {
|
|
1028
|
+
const fn = getFieldNode(n, 'function') ?? n.children?.[0];
|
|
1029
|
+
const text = fn ? getNodeText(fn, sourceCode).trim() : '';
|
|
991
1030
|
if (text && /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(text)) {
|
|
992
1031
|
names.add(text);
|
|
993
1032
|
}
|
|
994
1033
|
}
|
|
995
|
-
for (const child of
|
|
1034
|
+
for (const child of n.children ?? [])
|
|
996
1035
|
walk(child);
|
|
997
1036
|
};
|
|
998
|
-
walk(
|
|
1037
|
+
walk(node);
|
|
999
1038
|
return [...names];
|
|
1000
1039
|
}
|
|
1001
1040
|
/**
|
|
@@ -1007,26 +1046,20 @@ function clCollectCallees(raw) {
|
|
|
1007
1046
|
* (`arr.map(singularize)`, `[missedClose, …]`) is invisible to it and leaves
|
|
1008
1047
|
* the genuinely-referenced target looking orphaned.
|
|
1009
1048
|
*/
|
|
1010
|
-
function clCollectFileReferences(
|
|
1049
|
+
function clCollectFileReferences(node, sourceCode) {
|
|
1011
1050
|
const names = new Set();
|
|
1012
1051
|
const isIdentPath = (t) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(t);
|
|
1013
|
-
const walk = (
|
|
1014
|
-
|
|
1015
|
-
return;
|
|
1016
|
-
const t = node.type;
|
|
1052
|
+
const walk = (n) => {
|
|
1053
|
+
const t = n.type;
|
|
1017
1054
|
if (t === 'call_expression') {
|
|
1018
|
-
const fn =
|
|
1019
|
-
|
|
1020
|
-
: null;
|
|
1021
|
-
const text = (fn ?? node.namedChildren?.[0])?.text?.trim();
|
|
1055
|
+
const fn = getFieldNode(n, 'function') ?? n.children?.[0];
|
|
1056
|
+
const text = fn ? getNodeText(fn, sourceCode).trim() : '';
|
|
1022
1057
|
if (text && isIdentPath(text))
|
|
1023
1058
|
names.add(text);
|
|
1024
1059
|
}
|
|
1025
1060
|
else if (t === 'jsx_opening_element' || t === 'jsx_self_closing_element') {
|
|
1026
|
-
const nameNode =
|
|
1027
|
-
|
|
1028
|
-
: null;
|
|
1029
|
-
const text = (nameNode ?? node.namedChildren?.[0])?.text?.trim();
|
|
1061
|
+
const nameNode = getFieldNode(n, 'name') ?? n.children?.[0];
|
|
1062
|
+
const text = nameNode ? getNodeText(nameNode, sourceCode).trim() : '';
|
|
1030
1063
|
if (text && isIdentPath(text))
|
|
1031
1064
|
names.add(text);
|
|
1032
1065
|
}
|
|
@@ -1036,17 +1069,17 @@ function clCollectFileReferences(raw) {
|
|
|
1036
1069
|
// function reference. Declaration names (`name` fields) and member
|
|
1037
1070
|
// accesses (`property_identifier`) are separate node types and are not
|
|
1038
1071
|
// captured here.
|
|
1039
|
-
const parent =
|
|
1072
|
+
const parent = n.parent;
|
|
1040
1073
|
if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
|
|
1041
|
-
const text =
|
|
1074
|
+
const text = getNodeText(n, sourceCode).trim();
|
|
1042
1075
|
if (text && /^[A-Za-z_$][\w$]*$/.test(text))
|
|
1043
1076
|
names.add(text);
|
|
1044
1077
|
}
|
|
1045
1078
|
}
|
|
1046
|
-
for (const child of
|
|
1079
|
+
for (const child of n.children ?? [])
|
|
1047
1080
|
walk(child);
|
|
1048
1081
|
};
|
|
1049
|
-
walk(
|
|
1082
|
+
walk(node);
|
|
1050
1083
|
return [...names];
|
|
1051
1084
|
}
|
|
1052
1085
|
function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
|
|
@@ -1077,18 +1110,21 @@ function clMakeFunction(filePath, lang, name, line, signature, purpose, params,
|
|
|
1077
1110
|
* (`import(someVariable)` — unresolvable, reported rather than silenced), or
|
|
1078
1111
|
* `null` when the call is not a dynamic import/require.
|
|
1079
1112
|
*/
|
|
1080
|
-
function clDynamicImport(
|
|
1081
|
-
if (
|
|
1113
|
+
function clDynamicImport(node, sourceCode) {
|
|
1114
|
+
if (node.type !== 'call_expression')
|
|
1082
1115
|
return null;
|
|
1083
|
-
const fn =
|
|
1116
|
+
const fn = node.children?.[0];
|
|
1084
1117
|
const isImport = fn?.type === 'import';
|
|
1085
|
-
const isRequire = fn?.type === 'identifier' && fn
|
|
1118
|
+
const isRequire = fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require';
|
|
1086
1119
|
if (!isImport && !isRequire)
|
|
1087
1120
|
return null;
|
|
1088
|
-
const args =
|
|
1121
|
+
const args = node.children?.find((c) => c.type === 'arguments');
|
|
1089
1122
|
const stringNode = args?.children?.find((c) => c.type === 'string');
|
|
1090
|
-
if (stringNode
|
|
1091
|
-
|
|
1123
|
+
if (stringNode) {
|
|
1124
|
+
const text = getNodeText(stringNode, sourceCode);
|
|
1125
|
+
if (text.length >= 2) {
|
|
1126
|
+
return { specifier: text.slice(1, -1) };
|
|
1127
|
+
}
|
|
1092
1128
|
}
|
|
1093
1129
|
// A template literal with no `${…}` substitution is a compile-time constant,
|
|
1094
1130
|
// not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
|
|
@@ -1098,15 +1134,17 @@ function clDynamicImport(raw) {
|
|
|
1098
1134
|
if (templateNode) {
|
|
1099
1135
|
const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
|
|
1100
1136
|
if (!hasSubstitution) {
|
|
1101
|
-
const t = templateNode
|
|
1137
|
+
const t = getNodeText(templateNode, sourceCode);
|
|
1102
1138
|
if (t.length >= 2 && t.startsWith('`') && t.endsWith('`')) {
|
|
1103
1139
|
return { specifier: t.slice(1, -1) };
|
|
1104
1140
|
}
|
|
1105
1141
|
}
|
|
1106
1142
|
}
|
|
1107
1143
|
// Computed specifier — the target module cannot be resolved statically.
|
|
1108
|
-
const named = args?.
|
|
1109
|
-
const expression = named
|
|
1144
|
+
const named = args?.children?.[0];
|
|
1145
|
+
const expression = named
|
|
1146
|
+
? getNodeText(named, sourceCode)
|
|
1147
|
+
: getNodeText(args, sourceCode).replace(/^\(|\)$/g, '').trim();
|
|
1110
1148
|
return { computed: true, expression };
|
|
1111
1149
|
}
|
|
1112
1150
|
/**
|
|
@@ -1119,23 +1157,26 @@ function clDynamicImport(raw) {
|
|
|
1119
1157
|
* names), since Go has no `export` keyword. Dynamic `import()`/`require()`
|
|
1120
1158
|
* with a static string argument also record an import edge (Spec 58 R2).
|
|
1121
1159
|
*/
|
|
1122
|
-
function clCollectFileInfo(root, lang) {
|
|
1160
|
+
function clCollectFileInfo(root, lang, sourceCode) {
|
|
1123
1161
|
const imports = new Set();
|
|
1124
1162
|
const unresolvedDynamicImports = [];
|
|
1125
1163
|
let hasExports = false;
|
|
1126
1164
|
walkAST(root, (node) => {
|
|
1127
|
-
const raw = node.raw;
|
|
1128
1165
|
if (node.type === 'import_statement') {
|
|
1129
|
-
const source =
|
|
1130
|
-
if (source
|
|
1131
|
-
|
|
1166
|
+
const source = getFieldNode(node, 'source');
|
|
1167
|
+
if (source) {
|
|
1168
|
+
const text = getNodeText(source, sourceCode);
|
|
1169
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1170
|
+
}
|
|
1132
1171
|
}
|
|
1133
1172
|
else if (node.type === 'import_declaration') {
|
|
1134
|
-
for (const spec of
|
|
1173
|
+
for (const spec of node.children ?? []) {
|
|
1135
1174
|
if (spec.type === 'import_spec') {
|
|
1136
|
-
const p =
|
|
1137
|
-
if (p
|
|
1138
|
-
|
|
1175
|
+
const p = getFieldNode(spec, 'path');
|
|
1176
|
+
if (p) {
|
|
1177
|
+
const text = getNodeText(p, sourceCode);
|
|
1178
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1179
|
+
}
|
|
1139
1180
|
}
|
|
1140
1181
|
}
|
|
1141
1182
|
}
|
|
@@ -1144,109 +1185,113 @@ function clCollectFileInfo(root, lang) {
|
|
|
1144
1185
|
// Re-exports (`export { x } from './y'`, `export * from './y'`) are import
|
|
1145
1186
|
// edges for reachability: a barrel re-exporting a module marks it live,
|
|
1146
1187
|
// not dead. The `source` field is present only on the `from` form.
|
|
1147
|
-
const source =
|
|
1148
|
-
if (source
|
|
1149
|
-
|
|
1188
|
+
const source = getFieldNode(node, 'source');
|
|
1189
|
+
if (source) {
|
|
1190
|
+
const text = getNodeText(source, sourceCode);
|
|
1191
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1192
|
+
}
|
|
1150
1193
|
}
|
|
1151
1194
|
else if (node.type === 'call_expression') {
|
|
1152
|
-
const dyn = clDynamicImport(
|
|
1195
|
+
const dyn = clDynamicImport(node, sourceCode);
|
|
1153
1196
|
if (dyn && 'specifier' in dyn) {
|
|
1154
1197
|
imports.add(dyn.specifier);
|
|
1155
1198
|
}
|
|
1156
1199
|
else if (dyn) {
|
|
1157
|
-
unresolvedDynamicImports.push({ line:
|
|
1200
|
+
unresolvedDynamicImports.push({ line: node.location.start.line, expression: dyn.expression });
|
|
1158
1201
|
}
|
|
1159
1202
|
}
|
|
1160
1203
|
});
|
|
1161
1204
|
return { imports: [...imports], hasExports, unresolvedDynamicImports };
|
|
1162
1205
|
}
|
|
1163
1206
|
// ── TS/JS extraction ─────────────────────────────────────────────────────────
|
|
1164
|
-
function clExtractTSParams(
|
|
1207
|
+
function clExtractTSParams(node, sourceCode) {
|
|
1165
1208
|
const params = [];
|
|
1166
|
-
const formal =
|
|
1167
|
-
for (const p of formal?.
|
|
1209
|
+
const formal = node.children?.find((c) => c.type === 'formal_parameters');
|
|
1210
|
+
for (const p of formal?.children ?? []) {
|
|
1168
1211
|
if (p.type !== 'required_parameter' && p.type !== 'optional_parameter')
|
|
1169
1212
|
continue;
|
|
1170
|
-
const
|
|
1213
|
+
const nameNode = getFieldNode(p, 'name') ?? p.children?.find((c) => c.type === 'identifier');
|
|
1214
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1171
1215
|
if (!name)
|
|
1172
1216
|
continue;
|
|
1173
|
-
const
|
|
1217
|
+
const typeNode = getFieldNode(p, 'type');
|
|
1218
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1174
1219
|
params.push({ name, type, optional: p.type === 'optional_parameter', language: 'typescript' });
|
|
1175
1220
|
}
|
|
1176
1221
|
return params;
|
|
1177
1222
|
}
|
|
1178
|
-
function clExtractTSInterfaceParams(
|
|
1223
|
+
function clExtractTSInterfaceParams(node, sourceCode) {
|
|
1179
1224
|
const params = [];
|
|
1180
|
-
const walk = (
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const name = clRawField(node, 'name')?.text ?? clRawNamedChild(node, 'property_identifier')?.text;
|
|
1225
|
+
const walk = (n) => {
|
|
1226
|
+
if (n.type === 'property_signature') {
|
|
1227
|
+
const nameNode = getFieldNode(n, 'name') ?? n.children?.find((c) => c.type === 'property_identifier');
|
|
1228
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1185
1229
|
if (!name)
|
|
1186
1230
|
return;
|
|
1187
|
-
const
|
|
1188
|
-
const
|
|
1231
|
+
const typeNode = getFieldNode(n, 'type');
|
|
1232
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1233
|
+
const optional = n.children?.some((c) => c.type === '?') ?? false;
|
|
1189
1234
|
params.push({ name, type, optional, language: 'typescript' });
|
|
1190
1235
|
}
|
|
1191
|
-
for (const child of
|
|
1236
|
+
for (const child of n.children ?? [])
|
|
1192
1237
|
walk(child);
|
|
1193
1238
|
};
|
|
1194
|
-
walk(
|
|
1239
|
+
walk(node);
|
|
1195
1240
|
return params;
|
|
1196
1241
|
}
|
|
1197
1242
|
function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
1198
|
-
const fileReferences = clCollectFileReferences(root
|
|
1243
|
+
const fileReferences = clCollectFileReferences(root, sourceCode);
|
|
1199
1244
|
walkAST(root, (node) => {
|
|
1200
|
-
const raw = node.raw;
|
|
1201
1245
|
if (node.type === 'function_declaration' || node.type === 'method_definition') {
|
|
1202
|
-
const name = clNodeName(
|
|
1246
|
+
const name = clNodeName(node, sourceCode);
|
|
1203
1247
|
if (!name)
|
|
1204
1248
|
return;
|
|
1205
1249
|
let displayName = name;
|
|
1206
1250
|
if (node.type === 'method_definition') {
|
|
1207
|
-
let p =
|
|
1251
|
+
let p = node.parent ?? null;
|
|
1208
1252
|
while (p) {
|
|
1209
1253
|
if (p.type === 'class_declaration') {
|
|
1210
|
-
const cn = clNodeName(p);
|
|
1254
|
+
const cn = clNodeName(p, sourceCode);
|
|
1211
1255
|
if (cn)
|
|
1212
1256
|
displayName = `${cn}.${name}`;
|
|
1213
1257
|
break;
|
|
1214
1258
|
}
|
|
1215
|
-
p = p.parent;
|
|
1259
|
+
p = p.parent ?? null;
|
|
1216
1260
|
}
|
|
1217
1261
|
}
|
|
1218
|
-
const line =
|
|
1219
|
-
const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(
|
|
1262
|
+
const line = node.location.start.line;
|
|
1263
|
+
const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractTSParams(node, sourceCode), clCollectCallees(node, sourceCode), isExported(node), calculateComplexity(node), node.type === 'method_definition');
|
|
1220
1264
|
entity.metadata.fileReferences = fileReferences;
|
|
1221
1265
|
out.push(entity);
|
|
1222
1266
|
return;
|
|
1223
1267
|
}
|
|
1224
1268
|
if (node.type === 'variable_declarator') {
|
|
1225
|
-
const arrow =
|
|
1269
|
+
const arrow = node.children?.find((c) => c.type === 'arrow_function');
|
|
1226
1270
|
if (!arrow)
|
|
1227
1271
|
return;
|
|
1228
|
-
const
|
|
1272
|
+
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
1273
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1229
1274
|
if (!name)
|
|
1230
1275
|
return;
|
|
1231
|
-
const line =
|
|
1232
|
-
const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || arrow
|
|
1276
|
+
const line = node.location.start.line;
|
|
1277
|
+
const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode), clBodyText(arrow, sourceCode), [], clCollectCallees(arrow, sourceCode), isExported(node), calculateComplexity(node));
|
|
1233
1278
|
entity.metadata.fileReferences = fileReferences;
|
|
1234
1279
|
out.push(entity);
|
|
1235
1280
|
return;
|
|
1236
1281
|
}
|
|
1237
1282
|
if (node.type === 'interface_declaration') {
|
|
1238
|
-
const name = clNodeName(
|
|
1283
|
+
const name = clNodeName(node, sourceCode);
|
|
1239
1284
|
if (!name)
|
|
1240
1285
|
return;
|
|
1241
|
-
const line =
|
|
1286
|
+
const line = node.location.start.line;
|
|
1242
1287
|
out.push({
|
|
1243
1288
|
id: clEntityId(filePath, 'interface', name, line),
|
|
1244
1289
|
name,
|
|
1245
1290
|
language: lang,
|
|
1246
1291
|
file: filePath,
|
|
1247
1292
|
type: 'interface',
|
|
1248
|
-
signature:
|
|
1249
|
-
parameters: clExtractTSInterfaceParams(
|
|
1293
|
+
signature: getNodeText(node, sourceCode),
|
|
1294
|
+
parameters: clExtractTSInterfaceParams(node, sourceCode),
|
|
1250
1295
|
startLine: line,
|
|
1251
1296
|
visibility: isExported(node) ? 'public' : 'private',
|
|
1252
1297
|
calls: [],
|
|
@@ -1261,70 +1306,75 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1261
1306
|
});
|
|
1262
1307
|
}
|
|
1263
1308
|
// ── Go extraction ────────────────────────────────────────────────────────────
|
|
1264
|
-
function clGoReceiverType(receiver) {
|
|
1265
|
-
for (const child of receiver
|
|
1309
|
+
function clGoReceiverType(receiver, sourceCode) {
|
|
1310
|
+
for (const child of receiver.children ?? []) {
|
|
1266
1311
|
if (child.type === 'parameter_declaration') {
|
|
1267
|
-
const typeNode =
|
|
1312
|
+
const typeNode = getFieldNode(child, 'type');
|
|
1268
1313
|
if (typeNode)
|
|
1269
|
-
return typeNode.
|
|
1314
|
+
return getNodeText(typeNode, sourceCode).replace(/^\*/, '');
|
|
1270
1315
|
}
|
|
1271
1316
|
}
|
|
1272
1317
|
return undefined;
|
|
1273
1318
|
}
|
|
1274
|
-
function clExtractGoParams(
|
|
1319
|
+
function clExtractGoParams(node, sourceCode) {
|
|
1275
1320
|
const params = [];
|
|
1276
|
-
const paramList =
|
|
1277
|
-
for (const p of paramList?.
|
|
1321
|
+
const paramList = getFieldNode(node, 'parameters');
|
|
1322
|
+
for (const p of paramList?.children ?? []) {
|
|
1278
1323
|
if (p.type !== 'parameter_declaration')
|
|
1279
1324
|
continue;
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1325
|
+
const nameNode = getFieldNode(p, 'name') ?? getFieldNode(p, 'type');
|
|
1326
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1327
|
+
const typeNode = getFieldNode(p, 'type');
|
|
1328
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1282
1329
|
if (!name && !type)
|
|
1283
1330
|
continue;
|
|
1284
1331
|
params.push({ name: name ?? type ?? '<unknown>', type, optional: false, language: 'go' });
|
|
1285
1332
|
}
|
|
1286
1333
|
return params;
|
|
1287
1334
|
}
|
|
1288
|
-
function clExtractGoStructFields(structNode) {
|
|
1335
|
+
function clExtractGoStructFields(structNode, sourceCode) {
|
|
1289
1336
|
const fields = [];
|
|
1290
|
-
for (const f of structNode
|
|
1337
|
+
for (const f of structNode.children ?? []) {
|
|
1291
1338
|
if (f.type !== 'field_declaration')
|
|
1292
1339
|
continue;
|
|
1293
|
-
const
|
|
1340
|
+
const nameNode = getFieldNode(f, 'name');
|
|
1341
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1294
1342
|
if (!name)
|
|
1295
1343
|
continue;
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1344
|
+
const typeNode = getFieldNode(f, 'type');
|
|
1345
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1346
|
+
const tagNode = getFieldNode(f, 'tag');
|
|
1347
|
+
const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
|
|
1298
1348
|
fields.push({ name, type, isExported: clIsExportedGo(name), tag });
|
|
1299
1349
|
}
|
|
1300
1350
|
return fields;
|
|
1301
1351
|
}
|
|
1302
1352
|
function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
1303
|
-
const fileReferences = clCollectFileReferences(root
|
|
1353
|
+
const fileReferences = clCollectFileReferences(root, sourceCode);
|
|
1304
1354
|
walkAST(root, (node) => {
|
|
1305
|
-
const raw = node.raw;
|
|
1306
1355
|
if (node.type === 'function_declaration') {
|
|
1307
|
-
const name = clNodeName(
|
|
1356
|
+
const name = clNodeName(node, sourceCode);
|
|
1308
1357
|
if (!name)
|
|
1309
1358
|
return;
|
|
1310
|
-
const receiver =
|
|
1311
|
-
const receiverType = receiver ? clGoReceiverType(receiver) : undefined;
|
|
1359
|
+
const receiver = getFieldNode(node, 'receiver');
|
|
1360
|
+
const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
|
|
1312
1361
|
const displayName = receiverType ? `${receiverType}.${name}` : name;
|
|
1313
|
-
const line =
|
|
1314
|
-
const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(
|
|
1362
|
+
const line = node.location.start.line;
|
|
1363
|
+
const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(node, sourceCode) || getNodeText(node, sourceCode), clBodyText(node, sourceCode), clExtractGoParams(node, sourceCode), clCollectCallees(node, sourceCode), clIsExportedGo(name), calculateComplexity(node));
|
|
1315
1364
|
entity.metadata.fileReferences = fileReferences;
|
|
1316
1365
|
out.push(entity);
|
|
1317
1366
|
return;
|
|
1318
1367
|
}
|
|
1319
1368
|
if (node.type === 'type_declaration') {
|
|
1320
|
-
for (const spec of
|
|
1369
|
+
for (const spec of node.children ?? []) {
|
|
1321
1370
|
if (spec.type !== 'type_spec')
|
|
1322
1371
|
continue;
|
|
1323
|
-
const
|
|
1372
|
+
const nameNode = getFieldNode(spec, 'name');
|
|
1373
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1324
1374
|
if (!name)
|
|
1325
1375
|
continue;
|
|
1326
|
-
const typeNode =
|
|
1327
|
-
const line =
|
|
1376
|
+
const typeNode = getFieldNode(spec, 'type');
|
|
1377
|
+
const line = spec.location.start.line;
|
|
1328
1378
|
if (typeNode?.type === 'struct_type') {
|
|
1329
1379
|
out.push({
|
|
1330
1380
|
id: clEntityId(filePath, 'struct', name, line),
|
|
@@ -1332,7 +1382,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1332
1382
|
language: 'go',
|
|
1333
1383
|
file: filePath,
|
|
1334
1384
|
type: 'struct',
|
|
1335
|
-
signature: spec
|
|
1385
|
+
signature: getNodeText(spec, sourceCode),
|
|
1336
1386
|
parameters: [],
|
|
1337
1387
|
startLine: line,
|
|
1338
1388
|
visibility: clIsExportedGo(name) ? 'public' : 'private',
|
|
@@ -1341,7 +1391,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1341
1391
|
purpose: '',
|
|
1342
1392
|
context: '',
|
|
1343
1393
|
searchTokens: [name.toLowerCase()],
|
|
1344
|
-
metadata: { fields: clExtractGoStructFields(typeNode) },
|
|
1394
|
+
metadata: { fields: clExtractGoStructFields(typeNode, sourceCode) },
|
|
1345
1395
|
});
|
|
1346
1396
|
}
|
|
1347
1397
|
else if (typeNode?.type === 'interface_type') {
|
|
@@ -1351,7 +1401,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1351
1401
|
language: 'go',
|
|
1352
1402
|
file: filePath,
|
|
1353
1403
|
type: 'interface',
|
|
1354
|
-
signature: spec
|
|
1404
|
+
signature: getNodeText(spec, sourceCode),
|
|
1355
1405
|
parameters: [],
|
|
1356
1406
|
startLine: line,
|
|
1357
1407
|
visibility: clIsExportedGo(name) ? 'public' : 'private',
|
|
@@ -1390,7 +1440,7 @@ export function createCrossLanguageEntityVisitor() {
|
|
|
1390
1440
|
else {
|
|
1391
1441
|
clExtractTSEntities(root, filePath, sourceCode, lang, entities);
|
|
1392
1442
|
}
|
|
1393
|
-
const fileInfo = clCollectFileInfo(root, lang);
|
|
1443
|
+
const fileInfo = clCollectFileInfo(root, lang, sourceCode);
|
|
1394
1444
|
// Go has no `export` keyword — an exported symbol is a capitalized
|
|
1395
1445
|
// top-level name, which the entity extractor already records as public.
|
|
1396
1446
|
const hasExports = lang === 'go'
|
|
@@ -1464,27 +1514,35 @@ function clIsEntryPointFile(fp) {
|
|
|
1464
1514
|
return true;
|
|
1465
1515
|
return false;
|
|
1466
1516
|
}
|
|
1517
|
+
/** Resolve one import specifier to zero or one internal file via the classifier. */
|
|
1518
|
+
function clResolveImport(dep, sourceFile, options) {
|
|
1519
|
+
const { classification, resolvedPath } = classifyImportSpecifier(dep, sourceFile, options.corpusFiles, {
|
|
1520
|
+
virtualModules: options.virtualModules,
|
|
1521
|
+
aliasPatterns: options.tsconfigAliases?.pathPatterns ?? [],
|
|
1522
|
+
pathMappings: options.tsconfigAliases?.paths ?? {},
|
|
1523
|
+
baseUrl: options.tsconfigAliases?.baseUrl,
|
|
1524
|
+
projectRoot: options.projectRoot,
|
|
1525
|
+
});
|
|
1526
|
+
if (classification === 'internal-resolved' && resolvedPath)
|
|
1527
|
+
return [resolvedPath];
|
|
1528
|
+
return [];
|
|
1529
|
+
}
|
|
1467
1530
|
/**
|
|
1468
1531
|
* Compute file-level reachability from per-file imports.
|
|
1469
1532
|
*
|
|
1470
1533
|
* A file is a framework entry point (live → 1.0), imported by another file
|
|
1471
|
-
* (live → 0.5), or referenced by nothing (dead → 0.0).
|
|
1472
|
-
*
|
|
1473
|
-
*
|
|
1534
|
+
* (live → 0.5), or referenced by nothing (dead → 0.0). Resolution goes through
|
|
1535
|
+
* `classifyImportSpecifier` (relative paths and tsconfig `paths` aliases like
|
|
1536
|
+
* `@/`) instead of the legacy basename/fuzzy matcher, so an alias import creates
|
|
1537
|
+
* a real edge and a bare package import never fabricates a local one.
|
|
1474
1538
|
*/
|
|
1475
|
-
function clComputeReachability(fileFacts) {
|
|
1539
|
+
function clComputeReachability(fileFacts, options) {
|
|
1476
1540
|
const filePaths = new Set(fileFacts.keys());
|
|
1477
|
-
const
|
|
1478
|
-
for (const fp of filePaths) {
|
|
1479
|
-
const bn = basenameNoExt(fp);
|
|
1480
|
-
if (!moduleToFile.has(bn))
|
|
1481
|
-
moduleToFile.set(bn, new Set());
|
|
1482
|
-
moduleToFile.get(bn).add(fp);
|
|
1483
|
-
}
|
|
1541
|
+
const packageEntries = options.packageEntryPoints ?? new Set();
|
|
1484
1542
|
const importersOf = new Map();
|
|
1485
1543
|
for (const [fp, info] of fileFacts) {
|
|
1486
1544
|
for (const dep of info.imports) {
|
|
1487
|
-
const targets =
|
|
1545
|
+
const targets = clResolveImport(dep, fp, options);
|
|
1488
1546
|
for (const t of targets) {
|
|
1489
1547
|
if (t === fp)
|
|
1490
1548
|
continue;
|
|
@@ -1496,7 +1554,7 @@ function clComputeReachability(fileFacts) {
|
|
|
1496
1554
|
}
|
|
1497
1555
|
const reachability = new Map();
|
|
1498
1556
|
for (const fp of filePaths) {
|
|
1499
|
-
const entry = clIsEntryPointFile(fp);
|
|
1557
|
+
const entry = clIsEntryPointFile(fp) || packageEntries.has(fp);
|
|
1500
1558
|
const imported = (importersOf.get(fp)?.size ?? 0) > 0;
|
|
1501
1559
|
reachability.set(fp, entry ? 1 : imported ? 0.5 : 0);
|
|
1502
1560
|
}
|
|
@@ -1717,7 +1775,18 @@ export function createDependencyGraphReducer() {
|
|
|
1717
1775
|
// ── File-level reachability: unreferenced modules + ranking axis ──
|
|
1718
1776
|
const fileFacts = clFileFacts(allFacts);
|
|
1719
1777
|
if (fileFacts.size > 0) {
|
|
1720
|
-
|
|
1778
|
+
// `context.config` is the reducer's merged config (namespace + `_infra`);
|
|
1779
|
+
// a bare reducer context in a unit test omits it, so fall back to the
|
|
1780
|
+
// file facts themselves as the corpus and a no-alias classification.
|
|
1781
|
+
const infraConfig = (context.config ?? {});
|
|
1782
|
+
const packageEntrySet = new Set(infraConfig.packageEntryPoints ?? []);
|
|
1783
|
+
const { reachability, importersOf } = clComputeReachability(fileFacts, {
|
|
1784
|
+
corpusFiles: new Set(infraConfig.corpusFiles ?? [...fileFacts.keys()]),
|
|
1785
|
+
virtualModules: infraConfig.importVirtualModules ?? DEFAULT_VIRTUAL_MODULES,
|
|
1786
|
+
tsconfigAliases: infraConfig.tsconfigAliases,
|
|
1787
|
+
packageEntryPoints: packageEntrySet,
|
|
1788
|
+
projectRoot: context.projectRoot ?? '',
|
|
1789
|
+
});
|
|
1721
1790
|
// Persist reachability to graph_cache so the post-pipeline reorder
|
|
1722
1791
|
// (auditRunner hotspot block) can rank every finding by live/dead.
|
|
1723
1792
|
if (context.indexHandle?.rawDb) {
|
|
@@ -1747,7 +1816,7 @@ export function createDependencyGraphReducer() {
|
|
|
1747
1816
|
continue;
|
|
1748
1817
|
if (clIsTestFile(fp))
|
|
1749
1818
|
continue;
|
|
1750
|
-
if (clIsEntryPointFile(fp))
|
|
1819
|
+
if (clIsEntryPointFile(fp) || packageEntrySet.has(fp))
|
|
1751
1820
|
continue;
|
|
1752
1821
|
if ((importersOf.get(fp)?.size ?? 0) > 0)
|
|
1753
1822
|
continue;
|
|
@@ -1785,15 +1854,15 @@ export function createDependencyGraphReducer() {
|
|
|
1785
1854
|
}
|
|
1786
1855
|
const HEALTH_SEVERITY = {
|
|
1787
1856
|
'break-cycles': 'severe',
|
|
1788
|
-
'reduce-coupling': '
|
|
1789
|
-
'split-responsibilities': '
|
|
1857
|
+
'reduce-coupling': 'advisory',
|
|
1858
|
+
'split-responsibilities': 'advisory',
|
|
1790
1859
|
'review-orphans': 'severe',
|
|
1791
1860
|
};
|
|
1792
1861
|
for (const s of health.suggestions) {
|
|
1793
1862
|
violations.push({
|
|
1794
1863
|
file: '(multiple)',
|
|
1795
1864
|
line: 0,
|
|
1796
|
-
severity: HEALTH_SEVERITY[s.type] ?? '
|
|
1865
|
+
severity: HEALTH_SEVERITY[s.type] ?? 'advisory',
|
|
1797
1866
|
message: s.description,
|
|
1798
1867
|
rule: s.type,
|
|
1799
1868
|
type: s.type,
|
|
@@ -2029,7 +2098,7 @@ export function createSchemaCodeVisitor() {
|
|
|
2029
2098
|
// Find table references (per-file, uses allTables for short-id false-positive filtering)
|
|
2030
2099
|
const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
|
|
2031
2100
|
// Record schema usage → emit as indexFacts via the shared instance
|
|
2032
|
-
a.recordTableUsage(ast, adapter, context.filePath, tableRefs);
|
|
2101
|
+
a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
|
|
2033
2102
|
const pending = a.getPendingSchemaRecords();
|
|
2034
2103
|
// Emit clear-by-file + per-usage index facts
|
|
2035
2104
|
if (pending.clearFiles.length > 0) {
|
|
@@ -2044,10 +2113,13 @@ export function createSchemaCodeVisitor() {
|
|
|
2044
2113
|
file_path: usage.filePath,
|
|
2045
2114
|
table_name: usage.tableName,
|
|
2046
2115
|
function_name: usage.functionName,
|
|
2116
|
+
function_start_line: usage.functionStartLine,
|
|
2117
|
+
function_start_column: usage.functionStartColumn,
|
|
2047
2118
|
usage_type: usage.usageType,
|
|
2048
2119
|
line: usage.line,
|
|
2049
2120
|
column: usage.column,
|
|
2050
2121
|
raw_query: usage.rawQuery,
|
|
2122
|
+
origin: usage.origin ?? null,
|
|
2051
2123
|
},
|
|
2052
2124
|
});
|
|
2053
2125
|
}
|
|
@@ -2066,9 +2138,14 @@ export function createSchemaCodeVisitor() {
|
|
|
2066
2138
|
}
|
|
2067
2139
|
// Check SQL injection
|
|
2068
2140
|
violations.push(...checkSQLInjection(ast, adapter, sourceCode));
|
|
2069
|
-
// Emit facts for the Stage 3 reducer
|
|
2141
|
+
// Emit facts for the Stage 3 reducer. Fluent-builder references
|
|
2142
|
+
// (`origin: 'query-builder'`) are excluded from the unknown-table check:
|
|
2143
|
+
// a scratch/test table name carried by `db('t').select(...)` is not a
|
|
2144
|
+
// schema catalog miss, and feeding it in flips the 10:1 fail-open ratio.
|
|
2070
2145
|
const fileFacts = {
|
|
2071
|
-
tableRefs: tableRefs
|
|
2146
|
+
tableRefs: tableRefs
|
|
2147
|
+
.filter((r) => r.origin !== 'query-builder')
|
|
2148
|
+
.map((r) => ({
|
|
2072
2149
|
table: r.table,
|
|
2073
2150
|
type: r.type,
|
|
2074
2151
|
line: r.location.line,
|
|
@@ -2218,6 +2295,12 @@ export function createSchemaReducer() {
|
|
|
2218
2295
|
return na - nb;
|
|
2219
2296
|
return a.filePath.localeCompare(b.filePath);
|
|
2220
2297
|
});
|
|
2298
|
+
// Drop provenance — the last migration that removed a table, and the tables
|
|
2299
|
+
// that same migration introduced. Lets unknown-table detection distinguish
|
|
2300
|
+
// "never existed" from "existed and was dropped": the latter is a stale code
|
|
2301
|
+
// reference, not a typo. `dropProvenance` ends up holding exactly the tables
|
|
2302
|
+
// dropped and not subsequently recreated (CREATE clears the entry).
|
|
2303
|
+
const dropProvenance = new Map();
|
|
2221
2304
|
for (const sqlFile of sqlFiles) {
|
|
2222
2305
|
const before = new Set(knownTables);
|
|
2223
2306
|
applyMigrationOps(sqlFile.ops, knownTables);
|
|
@@ -2229,6 +2312,46 @@ export function createSchemaReducer() {
|
|
|
2229
2312
|
tableProvenances.set(table, sources);
|
|
2230
2313
|
}
|
|
2231
2314
|
}
|
|
2315
|
+
// Genuinely-new tables introduced by this migration (excludes rename/rebuild
|
|
2316
|
+
// churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
|
|
2317
|
+
const createdHere = [];
|
|
2318
|
+
const createdTables = new Set();
|
|
2319
|
+
const droppedTables = new Set();
|
|
2320
|
+
for (const op of sqlFile.ops) {
|
|
2321
|
+
const t = stripIdentifier(op.table);
|
|
2322
|
+
if (op.op === 'CREATE') {
|
|
2323
|
+
createdTables.add(t);
|
|
2324
|
+
if (!before.has(t))
|
|
2325
|
+
createdHere.push(t);
|
|
2326
|
+
}
|
|
2327
|
+
else if (op.op === 'DROP') {
|
|
2328
|
+
droppedTables.add(t);
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
for (const op of sqlFile.ops) {
|
|
2332
|
+
const t = stripIdentifier(op.table);
|
|
2333
|
+
if (op.op === 'DROP') {
|
|
2334
|
+
dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
|
|
2335
|
+
}
|
|
2336
|
+
else if (op.op === 'CREATE') {
|
|
2337
|
+
dropProvenance.delete(t);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
// A table created and dropped within this same file is a self-contained
|
|
2341
|
+
// fixture — a scratch table a test or script creates, uses, then tears
|
|
2342
|
+
// down. Its DROP is teardown, not a migration, so it must neither surface
|
|
2343
|
+
// as a stale-table-reference nor leave the name unknown: keep it known and
|
|
2344
|
+
// clear the drop provenance this file recorded for it. (A genuine dropped
|
|
2345
|
+
// table stays stale because its DROP lives in a migration that never also
|
|
2346
|
+
// CREATEs it — e.g. recall's `generation_queue`, dropped in 0198.)
|
|
2347
|
+
for (const t of createdTables) {
|
|
2348
|
+
if (droppedTables.has(t)) {
|
|
2349
|
+
knownTables.add(t);
|
|
2350
|
+
if (dropProvenance.get(t)?.migrationFile === sqlFile.filePath) {
|
|
2351
|
+
dropProvenance.delete(t);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2232
2355
|
}
|
|
2233
2356
|
// 1b. ORM tables from code files — with provenance from the table-source registry
|
|
2234
2357
|
for (const [filePath, fact] of perFile()) {
|
|
@@ -2312,6 +2435,42 @@ export function createSchemaReducer() {
|
|
|
2312
2435
|
const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
|
|
2313
2436
|
if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
|
|
2314
2437
|
for (const ref of unknownRefs) {
|
|
2438
|
+
// The table existed and was dropped in a migration — a stale code
|
|
2439
|
+
// reference, not a typo. Name the dropping migration and the tables
|
|
2440
|
+
// that migration introduced (evidence, not proof of a successor).
|
|
2441
|
+
const drop = dropProvenance.get(ref.table);
|
|
2442
|
+
if (drop) {
|
|
2443
|
+
const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
|
|
2444
|
+
const created = drop.createdInSameMigration;
|
|
2445
|
+
const msg = created.length > 0
|
|
2446
|
+
? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
|
|
2447
|
+
: `${ref.table} was dropped in ${migrationName} and was not recreated.`;
|
|
2448
|
+
violations.push({
|
|
2449
|
+
file: ref.file,
|
|
2450
|
+
line: ref.line,
|
|
2451
|
+
column: ref.column,
|
|
2452
|
+
severity: 'critical',
|
|
2453
|
+
message: msg,
|
|
2454
|
+
rule: 'stale-table-reference',
|
|
2455
|
+
analyzer: 'schema',
|
|
2456
|
+
symbol: ref.table,
|
|
2457
|
+
resolution: {
|
|
2458
|
+
action: 'update-stale-reference',
|
|
2459
|
+
// The tables `created` are evidence the migration replaced the
|
|
2460
|
+
// dropped table, not proof of a drop-in successor — schemas can
|
|
2461
|
+
// be split or changed incompatibly, in which case the file must
|
|
2462
|
+
// be removed rather than updated. The summary names them as
|
|
2463
|
+
// context and leaves update-vs-remove to the reviewer.
|
|
2464
|
+
summary: created.length > 0
|
|
2465
|
+
? `The table '${ref.table}' was dropped in ${migrationName}. That migration introduces ${joinEnglish(created)} — review this reference and update or remove it.`
|
|
2466
|
+
: `The table '${ref.table}' was dropped in ${migrationName} and was not recreated — update or remove this reference.`,
|
|
2467
|
+
symbols: created.length > 0 ? created : [ref.table],
|
|
2468
|
+
files: [ref.file],
|
|
2469
|
+
lines: [ref.line],
|
|
2470
|
+
},
|
|
2471
|
+
});
|
|
2472
|
+
continue;
|
|
2473
|
+
}
|
|
2315
2474
|
const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
|
|
2316
2475
|
const msg = suggestions.length > 0
|
|
2317
2476
|
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|