code-auditor-mcp 3.9.11 → 4.0.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 +200 -0
- 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/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/crossDomain/CrossDomainAnalyzer.d.ts +2 -2
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +39 -15
- 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/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 +198 -54
- 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.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 +22 -64
- 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.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 +18 -7
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.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 +38 -26
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +37 -13
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +42 -54
- package/dist/analyzers/universal/schema/codeAnalysis.js.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/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.map +1 -1
- package/dist/auditRunner.js +36 -6
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +23 -17
- 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 +148 -18
- package/dist/codeIndexDB.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.js +3 -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/daemon/core.d.ts.map +1 -1
- package/dist/daemon/core.js +33 -38
- package/dist/daemon/core.js.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/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/graph/importClassification.d.ts +76 -0
- package/dist/graph/importClassification.d.ts.map +1 -0
- package/dist/graph/importClassification.js +221 -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/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 +20 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +58 -0
- package/dist/installer.js.map +1 -1
- package/dist/invariants/invariant-rules.schema.json +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/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/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/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 +0 -21
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +1 -32
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +69 -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.js +2 -2
- package/dist/mcp-tools/projectTasks.js.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.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/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 +3 -2
- package/dist/nextFileIncremental.d.ts.map +1 -1
- package/dist/nextFileIncremental.js +20 -7
- package/dist/nextFileIncremental.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +17 -0
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +279 -178
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/csvReportGenerator.js +6 -6
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +11 -11
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +2 -2
- package/dist/reporting/jsonReportGenerator.js.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/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/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +19 -6
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.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 +59 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -5
- package/dist/types.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/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/package.json +1 -1
- 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 +129 -26
- 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,28 @@ 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
24
|
import { resolveDependency, basenameNoExt } from './graph/importGraph.js';
|
|
25
|
+
import { classifyImportSpecifier, DEFAULT_VIRTUAL_MODULES } from './graph/importClassification.js';
|
|
25
26
|
import { isReactComponent, detectComponentType, getComponentName, } from './utils/reactDetection.js';
|
|
26
27
|
import { findTableReferences, checkNamingConventions, checkQueryPatterns, checkSQLInjection, checkUnresolvedQueries, getNearestTableSuggestions, } from './analyzers/universal/schema/codeAnalysis.js';
|
|
27
28
|
import { passesFileGate, extractTablesFromRegistry, } from './analyzers/universal/schema/discovery.js';
|
|
28
|
-
import { applyMigrationOps } from './analyzers/universal/schema/migrations.js';
|
|
29
|
+
import { applyMigrationOps, stripIdentifier } from './analyzers/universal/schema/migrations.js';
|
|
29
30
|
// ── Rule ID helpers ──────────────────────────────────────────────────────────
|
|
30
31
|
function getRuleIdsFor(name) {
|
|
31
32
|
return Object.entries(RULE_REGISTRY)
|
|
32
33
|
.filter(([, entry]) => entry.analyzer === name)
|
|
33
34
|
.map(([id]) => id);
|
|
34
35
|
}
|
|
36
|
+
/** Join a list of names as prose: "a", "a and b", "a, b and c". */
|
|
37
|
+
function joinEnglish(names) {
|
|
38
|
+
if (names.length === 0)
|
|
39
|
+
return '';
|
|
40
|
+
if (names.length === 1)
|
|
41
|
+
return names[0];
|
|
42
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
43
|
+
}
|
|
35
44
|
// ── Lazy import singleton ────────────────────────────────────────────────────
|
|
36
45
|
function lazySingleton(loader) {
|
|
37
46
|
let promise = null;
|
|
@@ -153,10 +162,6 @@ function computeContentHash(body, signature) {
|
|
|
153
162
|
const normalized = (body ?? '').replace(/\s+/g, ' ').trim() + '|' + (signature ?? '').trim();
|
|
154
163
|
return createHash('sha256').update(normalized).digest('hex');
|
|
155
164
|
}
|
|
156
|
-
/** Raw text from an ASTNode (tree-sitter node stored on `.raw`). */
|
|
157
|
-
function rawText(node) {
|
|
158
|
-
return node.raw?.text ?? '';
|
|
159
|
-
}
|
|
160
165
|
export function createFunctionIndexVisitor() {
|
|
161
166
|
return {
|
|
162
167
|
name: 'function-index',
|
|
@@ -177,8 +182,18 @@ export function createFunctionIndexVisitor() {
|
|
|
177
182
|
table: 'functions',
|
|
178
183
|
data: { _action: 'clear-by-file', file_path: filePath },
|
|
179
184
|
});
|
|
185
|
+
// Spec 60 — import classification is TS/JS-only: the normalization probe
|
|
186
|
+
// list has no `.go` extension, so applying it to Go relative imports would
|
|
187
|
+
// manufacture false `internal-broken` rows. Clear + re-emit only for TS/JS.
|
|
188
|
+
const isTsJs = lang === 'typescript' || lang === 'javascript';
|
|
189
|
+
if (isTsJs) {
|
|
190
|
+
indexFacts.push({
|
|
191
|
+
table: 'import_specifiers',
|
|
192
|
+
data: { _action: 'clear-by-file', file_path: filePath },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
180
195
|
// Build import map once per file for resolving call targets
|
|
181
|
-
const importMap = buildImportMap(root);
|
|
196
|
+
const importMap = buildImportMap(root, sourceCode);
|
|
182
197
|
// Collect function-like nodes in two passes:
|
|
183
198
|
// 1) function_declaration + method_definition nodes
|
|
184
199
|
// 2) arrow functions assigned to variables (variable_declarator children)
|
|
@@ -189,12 +204,11 @@ export function createFunctionIndexVisitor() {
|
|
|
189
204
|
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
190
205
|
if (!nameNode)
|
|
191
206
|
return;
|
|
192
|
-
const name =
|
|
207
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
193
208
|
if (!name)
|
|
194
209
|
return;
|
|
195
210
|
const { line } = getLineAndColumn(node);
|
|
196
|
-
const
|
|
197
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
211
|
+
const endLine = node.location.end.line;
|
|
198
212
|
const body = getFunctionBody(node, sourceCode);
|
|
199
213
|
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
200
214
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -215,7 +229,7 @@ export function createFunctionIndexVisitor() {
|
|
|
215
229
|
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
216
230
|
if (!nameNode)
|
|
217
231
|
return;
|
|
218
|
-
const methodName =
|
|
232
|
+
const methodName = getNodeText(nameNode, sourceCode);
|
|
219
233
|
if (!methodName)
|
|
220
234
|
return;
|
|
221
235
|
// Walk up to find class name
|
|
@@ -225,14 +239,13 @@ export function createFunctionIndexVisitor() {
|
|
|
225
239
|
if (parent.type === 'class_declaration') {
|
|
226
240
|
const cn = parent.children?.find((c) => c.type === 'identifier');
|
|
227
241
|
if (cn)
|
|
228
|
-
className =
|
|
242
|
+
className = getNodeText(cn, sourceCode);
|
|
229
243
|
break;
|
|
230
244
|
}
|
|
231
245
|
parent = parent.parent;
|
|
232
246
|
}
|
|
233
247
|
const { line } = getLineAndColumn(node);
|
|
234
|
-
const
|
|
235
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
248
|
+
const endLine = node.location.end.line;
|
|
236
249
|
const body = getFunctionBody(node, sourceCode);
|
|
237
250
|
const calls = extractFunctionCalls(node, sourceCode, importMap);
|
|
238
251
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -257,14 +270,13 @@ export function createFunctionIndexVisitor() {
|
|
|
257
270
|
const arrowFunc = node.children?.find((c) => c.type === 'arrow_function');
|
|
258
271
|
if (!nameNode || !arrowFunc)
|
|
259
272
|
return;
|
|
260
|
-
const name =
|
|
273
|
+
const name = getNodeText(nameNode, sourceCode);
|
|
261
274
|
if (!name)
|
|
262
275
|
return;
|
|
263
276
|
// Don't duplicate if already covered as function_declaration
|
|
264
277
|
// (shouldn't happen — function_declaration is a different node type)
|
|
265
278
|
const { line } = getLineAndColumn(arrowFunc);
|
|
266
|
-
const
|
|
267
|
-
const endLine = line + (raw?.endPosition?.row ?? raw?.startPosition?.row ?? 0) - (raw?.startPosition?.row ?? line) + 1;
|
|
279
|
+
const endLine = arrowFunc.location.end.line;
|
|
268
280
|
const body = getFunctionBody(arrowFunc, sourceCode);
|
|
269
281
|
const calls = extractFunctionCalls(arrowFunc, sourceCode, importMap);
|
|
270
282
|
const callNames = [...new Set(calls.map((c) => c.callee))];
|
|
@@ -290,12 +302,12 @@ export function createFunctionIndexVisitor() {
|
|
|
290
302
|
filePath.endsWith('.jsx') ||
|
|
291
303
|
(filePath.endsWith('.js') && hasReactImport)) {
|
|
292
304
|
walkAST(root, (node) => {
|
|
293
|
-
if (!isReactComponent(node))
|
|
305
|
+
if (!isReactComponent(node, sourceCode))
|
|
294
306
|
return;
|
|
295
|
-
const ct = detectComponentType(node);
|
|
307
|
+
const ct = detectComponentType(node, sourceCode);
|
|
296
308
|
if (!ct)
|
|
297
309
|
return;
|
|
298
|
-
const cName = getComponentName(node);
|
|
310
|
+
const cName = getComponentName(node, sourceCode);
|
|
299
311
|
if (!cName || cName === 'AnonymousComponent')
|
|
300
312
|
return;
|
|
301
313
|
const existing = fnEntries.find((f) => f.name === cName);
|
|
@@ -308,11 +320,7 @@ export function createFunctionIndexVisitor() {
|
|
|
308
320
|
// New entry — class component, function_expression, or memo/forwardRef
|
|
309
321
|
// wrapper not already captured by passes 1 or 2.
|
|
310
322
|
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;
|
|
323
|
+
const endLine = node.location.end.line;
|
|
316
324
|
const body = getFunctionBody(node, sourceCode);
|
|
317
325
|
fnEntries.push({
|
|
318
326
|
name: cName,
|
|
@@ -335,7 +343,6 @@ export function createFunctionIndexVisitor() {
|
|
|
335
343
|
isExported: fn.isExported,
|
|
336
344
|
complexity: fn.complexity,
|
|
337
345
|
functionCalls: fn.functionCalls,
|
|
338
|
-
body: fn.body,
|
|
339
346
|
};
|
|
340
347
|
const contentHash = computeContentHash(fn.body, '');
|
|
341
348
|
const now = new Date().toISOString();
|
|
@@ -379,29 +386,63 @@ export function createFunctionIndexVisitor() {
|
|
|
379
386
|
isRequire: false,
|
|
380
387
|
line: imp.location.start.line,
|
|
381
388
|
}));
|
|
389
|
+
// Spec 60.1 — classify each static import specifier and emit one row per
|
|
390
|
+
// occurrence to `import_specifiers`. The corpus file set is the stage-1
|
|
391
|
+
// discovery list, threaded here as `_infra.files` (merged into
|
|
392
|
+
// `context.config` by pipeline.ts). Existence is checked against that
|
|
393
|
+
// in-memory set, never `fs.existsSync`. Virtual-module list + tsconfig
|
|
394
|
+
// `paths` patterns are threaded via `_infra` and passed to the classifier.
|
|
395
|
+
if (isTsJs) {
|
|
396
|
+
// Spec 60.1 Correction 1 — corpus set is the unfiltered discovery list
|
|
397
|
+
// (`_infra.corpusFiles`), not the narrowed `_infra.files`. The latter is
|
|
398
|
+
// the audit's analysis list (already pruned by includePaths/excludePaths),
|
|
399
|
+
// which drops `.json`/other non-analyzed extensions and would mis-classify
|
|
400
|
+
// a real `./invariant-rules.schema.json` as `internal-broken`.
|
|
401
|
+
const corpusFiles = new Set((context.config.corpusFiles) ??
|
|
402
|
+
(context.config.files) ??
|
|
403
|
+
[]);
|
|
404
|
+
const virtualModules = context.config.importVirtualModules
|
|
405
|
+
?? DEFAULT_VIRTUAL_MODULES;
|
|
406
|
+
const tsconfigAliases = context.config.tsconfigAliases;
|
|
407
|
+
for (const imp of staticImportInfos) {
|
|
408
|
+
const { classification, resolvedPath } = classifyImportSpecifier(imp.source, filePath, corpusFiles, {
|
|
409
|
+
virtualModules,
|
|
410
|
+
aliasPatterns: tsconfigAliases?.pathPatterns ?? [],
|
|
411
|
+
});
|
|
412
|
+
indexFacts.push({
|
|
413
|
+
table: 'import_specifiers',
|
|
414
|
+
data: {
|
|
415
|
+
file_path: filePath,
|
|
416
|
+
specifier: imp.source,
|
|
417
|
+
classification,
|
|
418
|
+
resolved_path: resolvedPath ?? null,
|
|
419
|
+
line: imp.location.start.line,
|
|
420
|
+
},
|
|
421
|
+
conflictKey: 'file_path, specifier, line',
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
382
425
|
// Dynamic import() and require() — first attempt with pure NodePattern
|
|
383
426
|
// Plan note: import keyword is an anonymous tree-sitter node, so
|
|
384
427
|
// hasChild cannot see it. We use `custom` with raw node access.
|
|
385
428
|
const dynamicCallNodes = langAdapter.findNodes(langAst, {
|
|
386
429
|
type: 'call_expression',
|
|
387
430
|
custom: (node) => {
|
|
388
|
-
const
|
|
389
|
-
const fn = raw?.firstChild;
|
|
431
|
+
const fn = node.children?.[0];
|
|
390
432
|
return (fn?.type === 'import') ||
|
|
391
|
-
(fn?.type === 'identifier' && fn
|
|
433
|
+
(fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
392
434
|
},
|
|
393
435
|
});
|
|
394
436
|
const dynamicImports = [];
|
|
395
437
|
for (const node of dynamicCallNodes) {
|
|
396
|
-
const
|
|
397
|
-
const fn = raw?.firstChild;
|
|
438
|
+
const fn = node.children?.[0];
|
|
398
439
|
const isImport = fn?.type === 'import';
|
|
399
|
-
const isRequire = !isImport && (fn?.type === 'identifier' && fn
|
|
400
|
-
// Walk the
|
|
401
|
-
const argsNode =
|
|
440
|
+
const isRequire = !isImport && (fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require');
|
|
441
|
+
// Walk the AST children to find the string argument
|
|
442
|
+
const argsNode = node.children?.find((c) => c.type === 'arguments');
|
|
402
443
|
const stringNode = argsNode?.children?.find((c) => c.type === 'string');
|
|
403
444
|
if (stringNode) {
|
|
404
|
-
const text = stringNode
|
|
445
|
+
const text = getNodeText(stringNode, sourceCode);
|
|
405
446
|
if (text.length >= 2) {
|
|
406
447
|
dynamicImports.push({
|
|
407
448
|
moduleSpecifier: text.slice(1, -1), // strip quotes
|
|
@@ -531,8 +572,8 @@ export function createStylesCssVisitor() {
|
|
|
531
572
|
facts: {
|
|
532
573
|
[filePath]: {
|
|
533
574
|
declarations: extractDeclarationsFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
534
|
-
tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath),
|
|
535
|
-
classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath),
|
|
575
|
+
tokens: extractTokensFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
576
|
+
classUsage: extractClassUsageFromCSSAst(cssAst, cssAdapter, filePath, sourceCode),
|
|
536
577
|
},
|
|
537
578
|
},
|
|
538
579
|
};
|
|
@@ -940,35 +981,31 @@ export function createCrossDomainReducer() {
|
|
|
940
981
|
// short-circuits with `notRunReason` BEFORE any corpus-wide work — the gate is in
|
|
941
982
|
// the design, not retrofitted after the budget blows. This is the shape that made
|
|
942
983
|
// 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
|
-
}
|
|
984
|
+
// ── Entity helpers (ASTNode-based; parser-specific nodes stay in languages/) ──
|
|
950
985
|
/** A function body node — `statement_block` (TS/JS) or `block` (Go). */
|
|
951
|
-
function
|
|
952
|
-
return
|
|
986
|
+
function clBody(node) {
|
|
987
|
+
return node.children?.find((c) => c.type === 'statement_block' || c.type === 'block');
|
|
953
988
|
}
|
|
954
|
-
function clSignatureText(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const end = body ? body.startIndex : raw.endIndex;
|
|
959
|
-
return sourceCode.slice(raw.startIndex, end).trim();
|
|
989
|
+
function clSignatureText(node, sourceCode) {
|
|
990
|
+
const body = clBody(node);
|
|
991
|
+
const end = body ? body.range[0] : node.range[1];
|
|
992
|
+
return sourceCode.slice(node.range[0], end).trim();
|
|
960
993
|
}
|
|
961
|
-
function clBodyText(
|
|
962
|
-
const body =
|
|
963
|
-
return body ? sourceCode.slice(body.
|
|
994
|
+
function clBodyText(node, sourceCode) {
|
|
995
|
+
const body = clBody(node);
|
|
996
|
+
return body ? sourceCode.slice(body.range[0], body.range[1]) : '';
|
|
964
997
|
}
|
|
965
998
|
/** 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
|
-
|
|
999
|
+
function clNodeName(node, sourceCode) {
|
|
1000
|
+
const field = getFieldNode(node, 'name');
|
|
1001
|
+
if (field) {
|
|
1002
|
+
const text = getNodeText(field, sourceCode);
|
|
1003
|
+
if (text)
|
|
1004
|
+
return text;
|
|
1005
|
+
}
|
|
1006
|
+
const id = node.children?.find((c) => c.type === 'identifier')
|
|
1007
|
+
?? node.children?.find((c) => c.type === 'property_identifier');
|
|
1008
|
+
return id ? getNodeText(id, sourceCode) : undefined;
|
|
972
1009
|
}
|
|
973
1010
|
function clEntityId(filePath, type, name, line) {
|
|
974
1011
|
return `${filePath}::${type}::${name}::${line}`;
|
|
@@ -977,25 +1014,20 @@ function clIsExportedGo(name) {
|
|
|
977
1014
|
return name.length > 0 && name[0] === name[0].toUpperCase();
|
|
978
1015
|
}
|
|
979
1016
|
/** Collect callee names from a function's body (TS `call_expression` / Go `call_expression`). */
|
|
980
|
-
function clCollectCallees(
|
|
1017
|
+
function clCollectCallees(node, sourceCode) {
|
|
981
1018
|
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();
|
|
1019
|
+
const walk = (n) => {
|
|
1020
|
+
if (n.type === 'call_expression') {
|
|
1021
|
+
const fn = getFieldNode(n, 'function') ?? n.children?.[0];
|
|
1022
|
+
const text = fn ? getNodeText(fn, sourceCode).trim() : '';
|
|
991
1023
|
if (text && /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(text)) {
|
|
992
1024
|
names.add(text);
|
|
993
1025
|
}
|
|
994
1026
|
}
|
|
995
|
-
for (const child of
|
|
1027
|
+
for (const child of n.children ?? [])
|
|
996
1028
|
walk(child);
|
|
997
1029
|
};
|
|
998
|
-
walk(
|
|
1030
|
+
walk(node);
|
|
999
1031
|
return [...names];
|
|
1000
1032
|
}
|
|
1001
1033
|
/**
|
|
@@ -1007,26 +1039,20 @@ function clCollectCallees(raw) {
|
|
|
1007
1039
|
* (`arr.map(singularize)`, `[missedClose, …]`) is invisible to it and leaves
|
|
1008
1040
|
* the genuinely-referenced target looking orphaned.
|
|
1009
1041
|
*/
|
|
1010
|
-
function clCollectFileReferences(
|
|
1042
|
+
function clCollectFileReferences(node, sourceCode) {
|
|
1011
1043
|
const names = new Set();
|
|
1012
1044
|
const isIdentPath = (t) => /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(t);
|
|
1013
|
-
const walk = (
|
|
1014
|
-
|
|
1015
|
-
return;
|
|
1016
|
-
const t = node.type;
|
|
1045
|
+
const walk = (n) => {
|
|
1046
|
+
const t = n.type;
|
|
1017
1047
|
if (t === 'call_expression') {
|
|
1018
|
-
const fn =
|
|
1019
|
-
|
|
1020
|
-
: null;
|
|
1021
|
-
const text = (fn ?? node.namedChildren?.[0])?.text?.trim();
|
|
1048
|
+
const fn = getFieldNode(n, 'function') ?? n.children?.[0];
|
|
1049
|
+
const text = fn ? getNodeText(fn, sourceCode).trim() : '';
|
|
1022
1050
|
if (text && isIdentPath(text))
|
|
1023
1051
|
names.add(text);
|
|
1024
1052
|
}
|
|
1025
1053
|
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();
|
|
1054
|
+
const nameNode = getFieldNode(n, 'name') ?? n.children?.[0];
|
|
1055
|
+
const text = nameNode ? getNodeText(nameNode, sourceCode).trim() : '';
|
|
1030
1056
|
if (text && isIdentPath(text))
|
|
1031
1057
|
names.add(text);
|
|
1032
1058
|
}
|
|
@@ -1036,17 +1062,17 @@ function clCollectFileReferences(raw) {
|
|
|
1036
1062
|
// function reference. Declaration names (`name` fields) and member
|
|
1037
1063
|
// accesses (`property_identifier`) are separate node types and are not
|
|
1038
1064
|
// captured here.
|
|
1039
|
-
const parent =
|
|
1065
|
+
const parent = n.parent;
|
|
1040
1066
|
if (parent && (parent.type === 'arguments' || parent.type === 'array')) {
|
|
1041
|
-
const text =
|
|
1067
|
+
const text = getNodeText(n, sourceCode).trim();
|
|
1042
1068
|
if (text && /^[A-Za-z_$][\w$]*$/.test(text))
|
|
1043
1069
|
names.add(text);
|
|
1044
1070
|
}
|
|
1045
1071
|
}
|
|
1046
|
-
for (const child of
|
|
1072
|
+
for (const child of n.children ?? [])
|
|
1047
1073
|
walk(child);
|
|
1048
1074
|
};
|
|
1049
|
-
walk(
|
|
1075
|
+
walk(node);
|
|
1050
1076
|
return [...names];
|
|
1051
1077
|
}
|
|
1052
1078
|
function clMakeFunction(filePath, lang, name, line, signature, purpose, params, callees, exported, complexity, isMethod = false) {
|
|
@@ -1077,18 +1103,21 @@ function clMakeFunction(filePath, lang, name, line, signature, purpose, params,
|
|
|
1077
1103
|
* (`import(someVariable)` — unresolvable, reported rather than silenced), or
|
|
1078
1104
|
* `null` when the call is not a dynamic import/require.
|
|
1079
1105
|
*/
|
|
1080
|
-
function clDynamicImport(
|
|
1081
|
-
if (
|
|
1106
|
+
function clDynamicImport(node, sourceCode) {
|
|
1107
|
+
if (node.type !== 'call_expression')
|
|
1082
1108
|
return null;
|
|
1083
|
-
const fn =
|
|
1109
|
+
const fn = node.children?.[0];
|
|
1084
1110
|
const isImport = fn?.type === 'import';
|
|
1085
|
-
const isRequire = fn?.type === 'identifier' && fn
|
|
1111
|
+
const isRequire = fn?.type === 'identifier' && getNodeText(fn, sourceCode) === 'require';
|
|
1086
1112
|
if (!isImport && !isRequire)
|
|
1087
1113
|
return null;
|
|
1088
|
-
const args =
|
|
1114
|
+
const args = node.children?.find((c) => c.type === 'arguments');
|
|
1089
1115
|
const stringNode = args?.children?.find((c) => c.type === 'string');
|
|
1090
|
-
if (stringNode
|
|
1091
|
-
|
|
1116
|
+
if (stringNode) {
|
|
1117
|
+
const text = getNodeText(stringNode, sourceCode);
|
|
1118
|
+
if (text.length >= 2) {
|
|
1119
|
+
return { specifier: text.slice(1, -1) };
|
|
1120
|
+
}
|
|
1092
1121
|
}
|
|
1093
1122
|
// A template literal with no `${…}` substitution is a compile-time constant,
|
|
1094
1123
|
// not a computed specifier — `import(\`@babel/plugin-syntax-jsx\`)` resolves
|
|
@@ -1098,15 +1127,17 @@ function clDynamicImport(raw) {
|
|
|
1098
1127
|
if (templateNode) {
|
|
1099
1128
|
const hasSubstitution = (templateNode.children ?? []).some((c) => c.type === 'template_substitution');
|
|
1100
1129
|
if (!hasSubstitution) {
|
|
1101
|
-
const t = templateNode
|
|
1130
|
+
const t = getNodeText(templateNode, sourceCode);
|
|
1102
1131
|
if (t.length >= 2 && t.startsWith('`') && t.endsWith('`')) {
|
|
1103
1132
|
return { specifier: t.slice(1, -1) };
|
|
1104
1133
|
}
|
|
1105
1134
|
}
|
|
1106
1135
|
}
|
|
1107
1136
|
// Computed specifier — the target module cannot be resolved statically.
|
|
1108
|
-
const named = args?.
|
|
1109
|
-
const expression = named
|
|
1137
|
+
const named = args?.children?.[0];
|
|
1138
|
+
const expression = named
|
|
1139
|
+
? getNodeText(named, sourceCode)
|
|
1140
|
+
: getNodeText(args, sourceCode).replace(/^\(|\)$/g, '').trim();
|
|
1110
1141
|
return { computed: true, expression };
|
|
1111
1142
|
}
|
|
1112
1143
|
/**
|
|
@@ -1119,23 +1150,26 @@ function clDynamicImport(raw) {
|
|
|
1119
1150
|
* names), since Go has no `export` keyword. Dynamic `import()`/`require()`
|
|
1120
1151
|
* with a static string argument also record an import edge (Spec 58 R2).
|
|
1121
1152
|
*/
|
|
1122
|
-
function clCollectFileInfo(root, lang) {
|
|
1153
|
+
function clCollectFileInfo(root, lang, sourceCode) {
|
|
1123
1154
|
const imports = new Set();
|
|
1124
1155
|
const unresolvedDynamicImports = [];
|
|
1125
1156
|
let hasExports = false;
|
|
1126
1157
|
walkAST(root, (node) => {
|
|
1127
|
-
const raw = node.raw;
|
|
1128
1158
|
if (node.type === 'import_statement') {
|
|
1129
|
-
const source =
|
|
1130
|
-
if (source
|
|
1131
|
-
|
|
1159
|
+
const source = getFieldNode(node, 'source');
|
|
1160
|
+
if (source) {
|
|
1161
|
+
const text = getNodeText(source, sourceCode);
|
|
1162
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1163
|
+
}
|
|
1132
1164
|
}
|
|
1133
1165
|
else if (node.type === 'import_declaration') {
|
|
1134
|
-
for (const spec of
|
|
1166
|
+
for (const spec of node.children ?? []) {
|
|
1135
1167
|
if (spec.type === 'import_spec') {
|
|
1136
|
-
const p =
|
|
1137
|
-
if (p
|
|
1138
|
-
|
|
1168
|
+
const p = getFieldNode(spec, 'path');
|
|
1169
|
+
if (p) {
|
|
1170
|
+
const text = getNodeText(p, sourceCode);
|
|
1171
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1172
|
+
}
|
|
1139
1173
|
}
|
|
1140
1174
|
}
|
|
1141
1175
|
}
|
|
@@ -1144,109 +1178,113 @@ function clCollectFileInfo(root, lang) {
|
|
|
1144
1178
|
// Re-exports (`export { x } from './y'`, `export * from './y'`) are import
|
|
1145
1179
|
// edges for reachability: a barrel re-exporting a module marks it live,
|
|
1146
1180
|
// not dead. The `source` field is present only on the `from` form.
|
|
1147
|
-
const source =
|
|
1148
|
-
if (source
|
|
1149
|
-
|
|
1181
|
+
const source = getFieldNode(node, 'source');
|
|
1182
|
+
if (source) {
|
|
1183
|
+
const text = getNodeText(source, sourceCode);
|
|
1184
|
+
imports.add(text.replace(/^['"]|['"]$/g, ''));
|
|
1185
|
+
}
|
|
1150
1186
|
}
|
|
1151
1187
|
else if (node.type === 'call_expression') {
|
|
1152
|
-
const dyn = clDynamicImport(
|
|
1188
|
+
const dyn = clDynamicImport(node, sourceCode);
|
|
1153
1189
|
if (dyn && 'specifier' in dyn) {
|
|
1154
1190
|
imports.add(dyn.specifier);
|
|
1155
1191
|
}
|
|
1156
1192
|
else if (dyn) {
|
|
1157
|
-
unresolvedDynamicImports.push({ line:
|
|
1193
|
+
unresolvedDynamicImports.push({ line: node.location.start.line, expression: dyn.expression });
|
|
1158
1194
|
}
|
|
1159
1195
|
}
|
|
1160
1196
|
});
|
|
1161
1197
|
return { imports: [...imports], hasExports, unresolvedDynamicImports };
|
|
1162
1198
|
}
|
|
1163
1199
|
// ── TS/JS extraction ─────────────────────────────────────────────────────────
|
|
1164
|
-
function clExtractTSParams(
|
|
1200
|
+
function clExtractTSParams(node, sourceCode) {
|
|
1165
1201
|
const params = [];
|
|
1166
|
-
const formal =
|
|
1167
|
-
for (const p of formal?.
|
|
1202
|
+
const formal = node.children?.find((c) => c.type === 'formal_parameters');
|
|
1203
|
+
for (const p of formal?.children ?? []) {
|
|
1168
1204
|
if (p.type !== 'required_parameter' && p.type !== 'optional_parameter')
|
|
1169
1205
|
continue;
|
|
1170
|
-
const
|
|
1206
|
+
const nameNode = getFieldNode(p, 'name') ?? p.children?.find((c) => c.type === 'identifier');
|
|
1207
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1171
1208
|
if (!name)
|
|
1172
1209
|
continue;
|
|
1173
|
-
const
|
|
1210
|
+
const typeNode = getFieldNode(p, 'type');
|
|
1211
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1174
1212
|
params.push({ name, type, optional: p.type === 'optional_parameter', language: 'typescript' });
|
|
1175
1213
|
}
|
|
1176
1214
|
return params;
|
|
1177
1215
|
}
|
|
1178
|
-
function clExtractTSInterfaceParams(
|
|
1216
|
+
function clExtractTSInterfaceParams(node, sourceCode) {
|
|
1179
1217
|
const params = [];
|
|
1180
|
-
const walk = (
|
|
1181
|
-
if (
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const name = clRawField(node, 'name')?.text ?? clRawNamedChild(node, 'property_identifier')?.text;
|
|
1218
|
+
const walk = (n) => {
|
|
1219
|
+
if (n.type === 'property_signature') {
|
|
1220
|
+
const nameNode = getFieldNode(n, 'name') ?? n.children?.find((c) => c.type === 'property_identifier');
|
|
1221
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1185
1222
|
if (!name)
|
|
1186
1223
|
return;
|
|
1187
|
-
const
|
|
1188
|
-
const
|
|
1224
|
+
const typeNode = getFieldNode(n, 'type');
|
|
1225
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1226
|
+
const optional = n.children?.some((c) => c.type === '?') ?? false;
|
|
1189
1227
|
params.push({ name, type, optional, language: 'typescript' });
|
|
1190
1228
|
}
|
|
1191
|
-
for (const child of
|
|
1229
|
+
for (const child of n.children ?? [])
|
|
1192
1230
|
walk(child);
|
|
1193
1231
|
};
|
|
1194
|
-
walk(
|
|
1232
|
+
walk(node);
|
|
1195
1233
|
return params;
|
|
1196
1234
|
}
|
|
1197
1235
|
function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
1198
|
-
const fileReferences = clCollectFileReferences(root
|
|
1236
|
+
const fileReferences = clCollectFileReferences(root, sourceCode);
|
|
1199
1237
|
walkAST(root, (node) => {
|
|
1200
|
-
const raw = node.raw;
|
|
1201
1238
|
if (node.type === 'function_declaration' || node.type === 'method_definition') {
|
|
1202
|
-
const name = clNodeName(
|
|
1239
|
+
const name = clNodeName(node, sourceCode);
|
|
1203
1240
|
if (!name)
|
|
1204
1241
|
return;
|
|
1205
1242
|
let displayName = name;
|
|
1206
1243
|
if (node.type === 'method_definition') {
|
|
1207
|
-
let p =
|
|
1244
|
+
let p = node.parent ?? null;
|
|
1208
1245
|
while (p) {
|
|
1209
1246
|
if (p.type === 'class_declaration') {
|
|
1210
|
-
const cn = clNodeName(p);
|
|
1247
|
+
const cn = clNodeName(p, sourceCode);
|
|
1211
1248
|
if (cn)
|
|
1212
1249
|
displayName = `${cn}.${name}`;
|
|
1213
1250
|
break;
|
|
1214
1251
|
}
|
|
1215
|
-
p = p.parent;
|
|
1252
|
+
p = p.parent ?? null;
|
|
1216
1253
|
}
|
|
1217
1254
|
}
|
|
1218
|
-
const line =
|
|
1219
|
-
const entity = clMakeFunction(filePath, lang, displayName, line, clSignatureText(
|
|
1255
|
+
const line = node.location.start.line;
|
|
1256
|
+
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
1257
|
entity.metadata.fileReferences = fileReferences;
|
|
1221
1258
|
out.push(entity);
|
|
1222
1259
|
return;
|
|
1223
1260
|
}
|
|
1224
1261
|
if (node.type === 'variable_declarator') {
|
|
1225
|
-
const arrow =
|
|
1262
|
+
const arrow = node.children?.find((c) => c.type === 'arrow_function');
|
|
1226
1263
|
if (!arrow)
|
|
1227
1264
|
return;
|
|
1228
|
-
const
|
|
1265
|
+
const nameNode = node.children?.find((c) => c.type === 'identifier');
|
|
1266
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1229
1267
|
if (!name)
|
|
1230
1268
|
return;
|
|
1231
|
-
const line =
|
|
1232
|
-
const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || arrow
|
|
1269
|
+
const line = node.location.start.line;
|
|
1270
|
+
const entity = clMakeFunction(filePath, lang, name, line, clSignatureText(arrow, sourceCode) || getNodeText(arrow, sourceCode), clBodyText(arrow, sourceCode), [], clCollectCallees(arrow, sourceCode), isExported(node), calculateComplexity(node));
|
|
1233
1271
|
entity.metadata.fileReferences = fileReferences;
|
|
1234
1272
|
out.push(entity);
|
|
1235
1273
|
return;
|
|
1236
1274
|
}
|
|
1237
1275
|
if (node.type === 'interface_declaration') {
|
|
1238
|
-
const name = clNodeName(
|
|
1276
|
+
const name = clNodeName(node, sourceCode);
|
|
1239
1277
|
if (!name)
|
|
1240
1278
|
return;
|
|
1241
|
-
const line =
|
|
1279
|
+
const line = node.location.start.line;
|
|
1242
1280
|
out.push({
|
|
1243
1281
|
id: clEntityId(filePath, 'interface', name, line),
|
|
1244
1282
|
name,
|
|
1245
1283
|
language: lang,
|
|
1246
1284
|
file: filePath,
|
|
1247
1285
|
type: 'interface',
|
|
1248
|
-
signature:
|
|
1249
|
-
parameters: clExtractTSInterfaceParams(
|
|
1286
|
+
signature: getNodeText(node, sourceCode),
|
|
1287
|
+
parameters: clExtractTSInterfaceParams(node, sourceCode),
|
|
1250
1288
|
startLine: line,
|
|
1251
1289
|
visibility: isExported(node) ? 'public' : 'private',
|
|
1252
1290
|
calls: [],
|
|
@@ -1261,70 +1299,75 @@ function clExtractTSEntities(root, filePath, sourceCode, lang, out) {
|
|
|
1261
1299
|
});
|
|
1262
1300
|
}
|
|
1263
1301
|
// ── Go extraction ────────────────────────────────────────────────────────────
|
|
1264
|
-
function clGoReceiverType(receiver) {
|
|
1265
|
-
for (const child of receiver
|
|
1302
|
+
function clGoReceiverType(receiver, sourceCode) {
|
|
1303
|
+
for (const child of receiver.children ?? []) {
|
|
1266
1304
|
if (child.type === 'parameter_declaration') {
|
|
1267
|
-
const typeNode =
|
|
1305
|
+
const typeNode = getFieldNode(child, 'type');
|
|
1268
1306
|
if (typeNode)
|
|
1269
|
-
return typeNode.
|
|
1307
|
+
return getNodeText(typeNode, sourceCode).replace(/^\*/, '');
|
|
1270
1308
|
}
|
|
1271
1309
|
}
|
|
1272
1310
|
return undefined;
|
|
1273
1311
|
}
|
|
1274
|
-
function clExtractGoParams(
|
|
1312
|
+
function clExtractGoParams(node, sourceCode) {
|
|
1275
1313
|
const params = [];
|
|
1276
|
-
const paramList =
|
|
1277
|
-
for (const p of paramList?.
|
|
1314
|
+
const paramList = getFieldNode(node, 'parameters');
|
|
1315
|
+
for (const p of paramList?.children ?? []) {
|
|
1278
1316
|
if (p.type !== 'parameter_declaration')
|
|
1279
1317
|
continue;
|
|
1280
|
-
const
|
|
1281
|
-
const
|
|
1318
|
+
const nameNode = getFieldNode(p, 'name') ?? getFieldNode(p, 'type');
|
|
1319
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1320
|
+
const typeNode = getFieldNode(p, 'type');
|
|
1321
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1282
1322
|
if (!name && !type)
|
|
1283
1323
|
continue;
|
|
1284
1324
|
params.push({ name: name ?? type ?? '<unknown>', type, optional: false, language: 'go' });
|
|
1285
1325
|
}
|
|
1286
1326
|
return params;
|
|
1287
1327
|
}
|
|
1288
|
-
function clExtractGoStructFields(structNode) {
|
|
1328
|
+
function clExtractGoStructFields(structNode, sourceCode) {
|
|
1289
1329
|
const fields = [];
|
|
1290
|
-
for (const f of structNode
|
|
1330
|
+
for (const f of structNode.children ?? []) {
|
|
1291
1331
|
if (f.type !== 'field_declaration')
|
|
1292
1332
|
continue;
|
|
1293
|
-
const
|
|
1333
|
+
const nameNode = getFieldNode(f, 'name');
|
|
1334
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1294
1335
|
if (!name)
|
|
1295
1336
|
continue;
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1337
|
+
const typeNode = getFieldNode(f, 'type');
|
|
1338
|
+
const type = typeNode ? getNodeText(typeNode, sourceCode).trim() : undefined;
|
|
1339
|
+
const tagNode = getFieldNode(f, 'tag');
|
|
1340
|
+
const tag = tagNode ? getNodeText(tagNode, sourceCode) : undefined;
|
|
1298
1341
|
fields.push({ name, type, isExported: clIsExportedGo(name), tag });
|
|
1299
1342
|
}
|
|
1300
1343
|
return fields;
|
|
1301
1344
|
}
|
|
1302
1345
|
function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
1303
|
-
const fileReferences = clCollectFileReferences(root
|
|
1346
|
+
const fileReferences = clCollectFileReferences(root, sourceCode);
|
|
1304
1347
|
walkAST(root, (node) => {
|
|
1305
|
-
const raw = node.raw;
|
|
1306
1348
|
if (node.type === 'function_declaration') {
|
|
1307
|
-
const name = clNodeName(
|
|
1349
|
+
const name = clNodeName(node, sourceCode);
|
|
1308
1350
|
if (!name)
|
|
1309
1351
|
return;
|
|
1310
|
-
const receiver =
|
|
1311
|
-
const receiverType = receiver ? clGoReceiverType(receiver) : undefined;
|
|
1352
|
+
const receiver = getFieldNode(node, 'receiver');
|
|
1353
|
+
const receiverType = receiver ? clGoReceiverType(receiver, sourceCode) : undefined;
|
|
1312
1354
|
const displayName = receiverType ? `${receiverType}.${name}` : name;
|
|
1313
|
-
const line =
|
|
1314
|
-
const entity = clMakeFunction(filePath, 'go', displayName, line, clSignatureText(
|
|
1355
|
+
const line = node.location.start.line;
|
|
1356
|
+
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
1357
|
entity.metadata.fileReferences = fileReferences;
|
|
1316
1358
|
out.push(entity);
|
|
1317
1359
|
return;
|
|
1318
1360
|
}
|
|
1319
1361
|
if (node.type === 'type_declaration') {
|
|
1320
|
-
for (const spec of
|
|
1362
|
+
for (const spec of node.children ?? []) {
|
|
1321
1363
|
if (spec.type !== 'type_spec')
|
|
1322
1364
|
continue;
|
|
1323
|
-
const
|
|
1365
|
+
const nameNode = getFieldNode(spec, 'name');
|
|
1366
|
+
const name = nameNode ? getNodeText(nameNode, sourceCode) : undefined;
|
|
1324
1367
|
if (!name)
|
|
1325
1368
|
continue;
|
|
1326
|
-
const typeNode =
|
|
1327
|
-
const line =
|
|
1369
|
+
const typeNode = getFieldNode(spec, 'type');
|
|
1370
|
+
const line = spec.location.start.line;
|
|
1328
1371
|
if (typeNode?.type === 'struct_type') {
|
|
1329
1372
|
out.push({
|
|
1330
1373
|
id: clEntityId(filePath, 'struct', name, line),
|
|
@@ -1332,7 +1375,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1332
1375
|
language: 'go',
|
|
1333
1376
|
file: filePath,
|
|
1334
1377
|
type: 'struct',
|
|
1335
|
-
signature: spec
|
|
1378
|
+
signature: getNodeText(spec, sourceCode),
|
|
1336
1379
|
parameters: [],
|
|
1337
1380
|
startLine: line,
|
|
1338
1381
|
visibility: clIsExportedGo(name) ? 'public' : 'private',
|
|
@@ -1341,7 +1384,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1341
1384
|
purpose: '',
|
|
1342
1385
|
context: '',
|
|
1343
1386
|
searchTokens: [name.toLowerCase()],
|
|
1344
|
-
metadata: { fields: clExtractGoStructFields(typeNode) },
|
|
1387
|
+
metadata: { fields: clExtractGoStructFields(typeNode, sourceCode) },
|
|
1345
1388
|
});
|
|
1346
1389
|
}
|
|
1347
1390
|
else if (typeNode?.type === 'interface_type') {
|
|
@@ -1351,7 +1394,7 @@ function clExtractGoEntities(root, filePath, sourceCode, out) {
|
|
|
1351
1394
|
language: 'go',
|
|
1352
1395
|
file: filePath,
|
|
1353
1396
|
type: 'interface',
|
|
1354
|
-
signature: spec
|
|
1397
|
+
signature: getNodeText(spec, sourceCode),
|
|
1355
1398
|
parameters: [],
|
|
1356
1399
|
startLine: line,
|
|
1357
1400
|
visibility: clIsExportedGo(name) ? 'public' : 'private',
|
|
@@ -1390,7 +1433,7 @@ export function createCrossLanguageEntityVisitor() {
|
|
|
1390
1433
|
else {
|
|
1391
1434
|
clExtractTSEntities(root, filePath, sourceCode, lang, entities);
|
|
1392
1435
|
}
|
|
1393
|
-
const fileInfo = clCollectFileInfo(root, lang);
|
|
1436
|
+
const fileInfo = clCollectFileInfo(root, lang, sourceCode);
|
|
1394
1437
|
// Go has no `export` keyword — an exported symbol is a capitalized
|
|
1395
1438
|
// top-level name, which the entity extractor already records as public.
|
|
1396
1439
|
const hasExports = lang === 'go'
|
|
@@ -1785,15 +1828,15 @@ export function createDependencyGraphReducer() {
|
|
|
1785
1828
|
}
|
|
1786
1829
|
const HEALTH_SEVERITY = {
|
|
1787
1830
|
'break-cycles': 'severe',
|
|
1788
|
-
'reduce-coupling': '
|
|
1789
|
-
'split-responsibilities': '
|
|
1831
|
+
'reduce-coupling': 'advisory',
|
|
1832
|
+
'split-responsibilities': 'advisory',
|
|
1790
1833
|
'review-orphans': 'severe',
|
|
1791
1834
|
};
|
|
1792
1835
|
for (const s of health.suggestions) {
|
|
1793
1836
|
violations.push({
|
|
1794
1837
|
file: '(multiple)',
|
|
1795
1838
|
line: 0,
|
|
1796
|
-
severity: HEALTH_SEVERITY[s.type] ?? '
|
|
1839
|
+
severity: HEALTH_SEVERITY[s.type] ?? 'advisory',
|
|
1797
1840
|
message: s.description,
|
|
1798
1841
|
rule: s.type,
|
|
1799
1842
|
type: s.type,
|
|
@@ -2029,7 +2072,7 @@ export function createSchemaCodeVisitor() {
|
|
|
2029
2072
|
// Find table references (per-file, uses allTables for short-id false-positive filtering)
|
|
2030
2073
|
const { references: tableRefs, unresolved } = findTableReferences(ast, adapter, sourceCode, { config: schemaConfig, provenanceContext, allTables });
|
|
2031
2074
|
// Record schema usage → emit as indexFacts via the shared instance
|
|
2032
|
-
a.recordTableUsage(ast, adapter, context.filePath, tableRefs);
|
|
2075
|
+
a.recordTableUsage(ast, adapter, context.filePath, tableRefs, sourceCode);
|
|
2033
2076
|
const pending = a.getPendingSchemaRecords();
|
|
2034
2077
|
// Emit clear-by-file + per-usage index facts
|
|
2035
2078
|
if (pending.clearFiles.length > 0) {
|
|
@@ -2044,6 +2087,8 @@ export function createSchemaCodeVisitor() {
|
|
|
2044
2087
|
file_path: usage.filePath,
|
|
2045
2088
|
table_name: usage.tableName,
|
|
2046
2089
|
function_name: usage.functionName,
|
|
2090
|
+
function_start_line: usage.functionStartLine,
|
|
2091
|
+
function_start_column: usage.functionStartColumn,
|
|
2047
2092
|
usage_type: usage.usageType,
|
|
2048
2093
|
line: usage.line,
|
|
2049
2094
|
column: usage.column,
|
|
@@ -2218,6 +2263,12 @@ export function createSchemaReducer() {
|
|
|
2218
2263
|
return na - nb;
|
|
2219
2264
|
return a.filePath.localeCompare(b.filePath);
|
|
2220
2265
|
});
|
|
2266
|
+
// Drop provenance — the last migration that removed a table, and the tables
|
|
2267
|
+
// that same migration introduced. Lets unknown-table detection distinguish
|
|
2268
|
+
// "never existed" from "existed and was dropped": the latter is a stale code
|
|
2269
|
+
// reference, not a typo. `dropProvenance` ends up holding exactly the tables
|
|
2270
|
+
// dropped and not subsequently recreated (CREATE clears the entry).
|
|
2271
|
+
const dropProvenance = new Map();
|
|
2221
2272
|
for (const sqlFile of sqlFiles) {
|
|
2222
2273
|
const before = new Set(knownTables);
|
|
2223
2274
|
applyMigrationOps(sqlFile.ops, knownTables);
|
|
@@ -2229,6 +2280,25 @@ export function createSchemaReducer() {
|
|
|
2229
2280
|
tableProvenances.set(table, sources);
|
|
2230
2281
|
}
|
|
2231
2282
|
}
|
|
2283
|
+
// Genuinely-new tables introduced by this migration (excludes rename/rebuild
|
|
2284
|
+
// churn like `ALTER … RENAME TO x_old` + re-CREATE of the same name).
|
|
2285
|
+
const createdHere = [];
|
|
2286
|
+
for (const op of sqlFile.ops) {
|
|
2287
|
+
if (op.op === 'CREATE') {
|
|
2288
|
+
const t = stripIdentifier(op.table);
|
|
2289
|
+
if (!before.has(t))
|
|
2290
|
+
createdHere.push(t);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
for (const op of sqlFile.ops) {
|
|
2294
|
+
const t = stripIdentifier(op.table);
|
|
2295
|
+
if (op.op === 'DROP') {
|
|
2296
|
+
dropProvenance.set(t, { migrationFile: sqlFile.filePath, createdInSameMigration: createdHere });
|
|
2297
|
+
}
|
|
2298
|
+
else if (op.op === 'CREATE') {
|
|
2299
|
+
dropProvenance.delete(t);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2232
2302
|
}
|
|
2233
2303
|
// 1b. ORM tables from code files — with provenance from the table-source registry
|
|
2234
2304
|
for (const [filePath, fact] of perFile()) {
|
|
@@ -2312,6 +2382,37 @@ export function createSchemaReducer() {
|
|
|
2312
2382
|
const unknownRefs = allTableRefs.filter(ref => !knownTables.has(ref.table));
|
|
2313
2383
|
if (unknownRefs.length / Math.max(knownTables.size, 1) <= 10) {
|
|
2314
2384
|
for (const ref of unknownRefs) {
|
|
2385
|
+
// The table existed and was dropped in a migration — a stale code
|
|
2386
|
+
// reference, not a typo. Name the dropping migration and the tables
|
|
2387
|
+
// that migration introduced (evidence, not proof of a successor).
|
|
2388
|
+
const drop = dropProvenance.get(ref.table);
|
|
2389
|
+
if (drop) {
|
|
2390
|
+
const migrationName = drop.migrationFile.split('/').pop() ?? drop.migrationFile;
|
|
2391
|
+
const created = drop.createdInSameMigration;
|
|
2392
|
+
const msg = created.length > 0
|
|
2393
|
+
? `${ref.table} was dropped in ${migrationName}; that migration creates ${joinEnglish(created)}.`
|
|
2394
|
+
: `${ref.table} was dropped in ${migrationName} and was not recreated.`;
|
|
2395
|
+
violations.push({
|
|
2396
|
+
file: ref.file,
|
|
2397
|
+
line: ref.line,
|
|
2398
|
+
column: ref.column,
|
|
2399
|
+
severity: 'critical',
|
|
2400
|
+
message: msg,
|
|
2401
|
+
rule: 'stale-table-reference',
|
|
2402
|
+
analyzer: 'schema',
|
|
2403
|
+
symbol: ref.table,
|
|
2404
|
+
resolution: {
|
|
2405
|
+
action: 'update-stale-reference',
|
|
2406
|
+
summary: created.length > 0
|
|
2407
|
+
? `The table '${ref.table}' was dropped in ${migrationName}. That migration creates ${joinEnglish(created)} — update this reference to a table that still exists.`
|
|
2408
|
+
: `The table '${ref.table}' was dropped in ${migrationName} and not recreated — update or remove this reference.`,
|
|
2409
|
+
symbols: created.length > 0 ? created : [ref.table],
|
|
2410
|
+
files: [ref.file],
|
|
2411
|
+
lines: [ref.line],
|
|
2412
|
+
},
|
|
2413
|
+
});
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2315
2416
|
const suggestions = getNearestTableSuggestions(ref.table, knownTables, 2);
|
|
2316
2417
|
const msg = suggestions.length > 0
|
|
2317
2418
|
? `Reference to unknown table '${ref.table}' (${ref.type}). Did you mean: ${suggestions.join(', ')}?`
|