code-auditor-mcp 3.4.12 → 3.4.13
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/CHANGELOG.md +46 -1
- package/dist/analyzers/__verify_self_probe.d.ts +2 -0
- package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
- package/dist/analyzers/__verify_self_probe.js +5 -0
- package/dist/analyzers/__verify_self_probe.js.map +1 -0
- package/dist/analyzers/analyzerUtils.d.ts +34 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +61 -22
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +259 -198
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +57 -50
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.js +10 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +42 -44
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.js +30 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +62 -2
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +443 -349
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +11 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +188 -125
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +110 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +1714 -124
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +54 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -0
- package/dist/analyzers/ruleTiming.js +71 -0
- package/dist/analyzers/ruleTiming.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +17 -202
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1073 -1121
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -214
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1521
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -480
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
- package/dist/analyzers/universal/schema/config.d.ts +31 -0
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/config.js +57 -0
- package/dist/analyzers/universal/schema/config.js.map +1 -0
- package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/discovery.js +616 -0
- package/dist/analyzers/universal/schema/discovery.js.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
- package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/migrations.js +238 -0
- package/dist/analyzers/universal/schema/migrations.js.map +1 -0
- package/dist/analyzers/universal/schema/types.d.ts +131 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/types.js +10 -0
- package/dist/analyzers/universal/schema/types.js.map +1 -0
- package/dist/analyzers/universal/schema/violations.d.ts +44 -0
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/violations.js +54 -0
- package/dist/analyzers/universal/schema/violations.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +89 -13
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +315 -40
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +9 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +12 -3
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +15 -5
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +75 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -0
- package/dist/config/effectiveConfig.js +213 -0
- package/dist/config/effectiveConfig.js.map +1 -0
- package/dist/config/pathProfiles.d.ts +7 -4
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +8 -10
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +45 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -0
- package/dist/config/thresholdRationales.js +67 -0
- package/dist/config/thresholdRationales.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +6 -2
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/enforcement/diffGate.d.ts +55 -0
- package/dist/enforcement/diffGate.d.ts.map +1 -0
- package/dist/enforcement/diffGate.js +136 -0
- package/dist/enforcement/diffGate.js.map +1 -0
- package/dist/enforcement/gate.d.ts +36 -0
- package/dist/enforcement/gate.d.ts.map +1 -0
- package/dist/enforcement/gate.js +72 -0
- package/dist/enforcement/gate.js.map +1 -0
- package/dist/enforcement/suppressions.d.ts +41 -0
- package/dist/enforcement/suppressions.d.ts.map +1 -0
- package/dist/enforcement/suppressions.js +170 -0
- package/dist/enforcement/suppressions.js.map +1 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -4
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +2 -2
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +14 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +93 -55
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts +8 -0
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/LanguageRegistry.js +8 -0
- package/dist/languages/LanguageRegistry.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +82 -60
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +404 -349
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +51 -6
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +127 -88
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +36 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +89 -35
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +66 -23
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +553 -474
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +33 -4
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +101 -66
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +18 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +18 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +39 -2
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +93 -12
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/types.d.ts +67 -2
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -72
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1419 -923
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/pipeline.d.ts +13 -5
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +381 -200
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +3 -8
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +149 -94
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/presets/presets.d.ts +54 -0
- package/dist/presets/presets.d.ts.map +1 -0
- package/dist/presets/presets.js +181 -0
- package/dist/presets/presets.js.map +1 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +0 -2
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts +67 -0
- package/dist/ruleAliases.d.ts.map +1 -0
- package/dist/ruleAliases.js +79 -0
- package/dist/ruleAliases.js.map +1 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +78 -18
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.js +10 -1
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +8 -6
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +61 -15
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +181 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -3
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/hook-audit.sh +25 -7
- package/plugin/skills/code-auditor/SKILL.md +5 -5
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import * as path from 'path';
|
|
16
16
|
import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
|
|
17
|
+
import { withRuleTiming } from '../ruleTiming.js';
|
|
17
18
|
import { makeVisitorStatus } from '../../pipeline.js';
|
|
18
19
|
import { detectCase, detectErrorHandlingShape, detectExportForm, parseFileImports, hasNonLatinChars, } from '../../conventions/conventionMiner.js';
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
@@ -28,6 +29,9 @@ export const DEFAULT_CONVENTIONS_CONFIG = {
|
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
29
30
|
// Analyzer
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
32
|
+
/**
|
|
33
|
+
* Universal conventions analyzer.
|
|
34
|
+
*/
|
|
31
35
|
export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
32
36
|
name = 'conventions';
|
|
33
37
|
description = 'Mines codebase conventions and flags deviations at suggestion severity';
|
|
@@ -35,53 +39,70 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
35
39
|
/**
|
|
36
40
|
* Full override: query conventions from the DB and emit violations per domain.
|
|
37
41
|
* The base-class per-file AST loop is bypassed.
|
|
42
|
+
* @param config
|
|
43
|
+
* @param files
|
|
44
|
+
* @param options
|
|
45
|
+
* @returns
|
|
38
46
|
*/
|
|
39
47
|
async analyze(files, config = {}, options = {}) {
|
|
40
48
|
const startTime = Date.now();
|
|
41
49
|
const violations = [];
|
|
42
50
|
const indexHandle = config.indexHandle;
|
|
43
51
|
if (!indexHandle) {
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
status: makeVisitorStatus(0),
|
|
48
|
-
executionTime: Date.now() - startTime,
|
|
49
|
-
analyzerName: this.name,
|
|
50
|
-
metrics: { filesAnalyzed: 0, totalViolations: 0, executionTime: Date.now() - startTime },
|
|
51
|
-
};
|
|
52
|
+
return this.makeResult([], 0, startTime, [
|
|
53
|
+
{ file: '', error: 'No index handle available — code index not open' },
|
|
54
|
+
]);
|
|
52
55
|
}
|
|
53
56
|
// Query all conventions
|
|
54
57
|
const conventions = indexHandle.query('SELECT * FROM conventions ORDER BY domain, directory');
|
|
55
58
|
if (conventions.length === 0) {
|
|
56
|
-
return
|
|
57
|
-
violations: [],
|
|
58
|
-
errors: [],
|
|
59
|
-
status: makeVisitorStatus(files.length),
|
|
60
|
-
executionTime: Date.now() - startTime,
|
|
61
|
-
analyzerName: this.name,
|
|
62
|
-
metrics: { filesAnalyzed: files.length, totalViolations: 0, executionTime: Date.now() - startTime },
|
|
63
|
-
};
|
|
59
|
+
return this.makeResult([], files.length, startTime);
|
|
64
60
|
}
|
|
65
|
-
// Extract project root, source
|
|
61
|
+
// Extract project root, on-demand source reader, and exports map from config
|
|
66
62
|
const projectRoot = config.projectRoot;
|
|
67
|
-
const
|
|
63
|
+
const readSource = config.readSource;
|
|
68
64
|
// B2: exportsMap comes from function-index visitor facts (AST-extracted)
|
|
69
65
|
const exportsMap = config.exportsMap;
|
|
70
|
-
// Group conventions by domain for efficient detection
|
|
71
|
-
const byDomain = new Map();
|
|
72
|
-
for (const c of conventions) {
|
|
73
|
-
const list = byDomain.get(c.domain) ?? [];
|
|
74
|
-
list.push(c);
|
|
75
|
-
byDomain.set(c.domain, list);
|
|
76
|
-
}
|
|
77
66
|
// Detect violations per domain
|
|
67
|
+
const byDomain = groupConventionsByDomain(conventions);
|
|
68
|
+
const scanCtx = { projectRoot, readSource, exportsMap };
|
|
69
|
+
violations.push(...this.detectPerDomain(byDomain, indexHandle, scanCtx));
|
|
70
|
+
// Count unique files that conventions apply to (from the function index)
|
|
71
|
+
const uniqueFiles = countUniqueFiles(indexHandle, projectRoot);
|
|
72
|
+
return this.makeResult(violations, uniqueFiles, startTime);
|
|
73
|
+
}
|
|
74
|
+
/** Build a standard AnalyzerResult with shared metrics and timing fields. */
|
|
75
|
+
makeResult(violations, filesAnalyzed, startTime, errors = []) {
|
|
76
|
+
return {
|
|
77
|
+
violations,
|
|
78
|
+
errors,
|
|
79
|
+
status: makeVisitorStatus(filesAnalyzed),
|
|
80
|
+
executionTime: Date.now() - startTime,
|
|
81
|
+
analyzerName: this.name,
|
|
82
|
+
metrics: {
|
|
83
|
+
filesAnalyzed,
|
|
84
|
+
totalViolations: violations.length,
|
|
85
|
+
executionTime: Date.now() - startTime,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** No-op — all detection is DB-based. */
|
|
90
|
+
async analyzeAST() {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Dispatch each convention domain to its detector.
|
|
95
|
+
*/
|
|
96
|
+
detectPerDomain(byDomain, indexHandle, scanCtx) {
|
|
97
|
+
const { projectRoot, readSource, exportsMap } = scanCtx;
|
|
98
|
+
const violations = [];
|
|
78
99
|
for (const [domain, domainConventions] of byDomain) {
|
|
79
100
|
switch (domain) {
|
|
80
101
|
case 'usage-pair':
|
|
81
102
|
violations.push(...this.detectUsagePairViolations(indexHandle, domainConventions));
|
|
82
103
|
break;
|
|
83
104
|
case 'import-form':
|
|
84
|
-
violations.push(...this.detectImportFormViolations(indexHandle, domainConventions, projectRoot,
|
|
105
|
+
violations.push(...this.detectImportFormViolations(indexHandle, domainConventions, projectRoot, readSource));
|
|
85
106
|
break;
|
|
86
107
|
case 'error-handling':
|
|
87
108
|
violations.push(...this.detectErrorHandlingViolations(indexHandle, domainConventions));
|
|
@@ -94,28 +115,7 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
94
115
|
break;
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
|
-
|
|
98
|
-
const resolvedProjectRoot = projectRoot ? path.resolve(projectRoot) : undefined;
|
|
99
|
-
const fileRows = indexHandle.query(resolvedProjectRoot
|
|
100
|
-
? 'SELECT COUNT(DISTINCT file_path) as cnt FROM functions WHERE file_path LIKE ?'
|
|
101
|
-
: 'SELECT COUNT(DISTINCT file_path) as cnt FROM functions', resolvedProjectRoot ? [resolvedProjectRoot + '%'] : []);
|
|
102
|
-
const uniqueFiles = fileRows[0]?.cnt ?? 0;
|
|
103
|
-
return {
|
|
104
|
-
violations,
|
|
105
|
-
errors: [],
|
|
106
|
-
status: makeVisitorStatus(uniqueFiles),
|
|
107
|
-
executionTime: Date.now() - startTime,
|
|
108
|
-
analyzerName: this.name,
|
|
109
|
-
metrics: {
|
|
110
|
-
filesAnalyzed: uniqueFiles,
|
|
111
|
-
totalViolations: violations.length,
|
|
112
|
-
executionTime: Date.now() - startTime,
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
/** No-op — all detection is DB-based. */
|
|
117
|
-
async analyzeAST() {
|
|
118
|
-
return [];
|
|
118
|
+
return violations;
|
|
119
119
|
}
|
|
120
120
|
// ── Usage-Pair Detection ──────────────────────────────────────────────
|
|
121
121
|
/**
|
|
@@ -124,60 +124,20 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
124
124
|
*/
|
|
125
125
|
detectUsagePairViolations(indexHandle, conventions) {
|
|
126
126
|
const violations = [];
|
|
127
|
-
// Query all function calls once
|
|
127
|
+
// Query all function calls once and index both directions
|
|
128
128
|
const allCalls = indexHandle.query('SELECT caller_id, callee_name FROM function_calls');
|
|
129
|
-
|
|
130
|
-
const callerCalls = new Map();
|
|
131
|
-
for (const fc of allCalls) {
|
|
132
|
-
if (!callerCalls.has(fc.caller_id))
|
|
133
|
-
callerCalls.set(fc.caller_id, new Set());
|
|
134
|
-
callerCalls.get(fc.caller_id).add(fc.callee_name);
|
|
135
|
-
}
|
|
136
|
-
// antecedentName → Set<callerId>
|
|
137
|
-
const antecedentCallers = new Map();
|
|
138
|
-
for (const fc of allCalls) {
|
|
139
|
-
if (!antecedentCallers.has(fc.callee_name)) {
|
|
140
|
-
antecedentCallers.set(fc.callee_name, new Set());
|
|
141
|
-
}
|
|
142
|
-
antecedentCallers.get(fc.callee_name).add(fc.caller_id);
|
|
143
|
-
}
|
|
129
|
+
const { callerCalls, antecedentCallers } = buildCallMaps(allCalls);
|
|
144
130
|
// Query all functions for file/line info
|
|
145
131
|
const funcRows = indexHandle.query('SELECT id, name, file_path, line_number FROM functions');
|
|
146
|
-
const funcById =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
132
|
+
const funcById = buildFuncById(funcRows);
|
|
133
|
+
const ctx = {
|
|
134
|
+
antecedentCallers,
|
|
135
|
+
callerCalls,
|
|
136
|
+
funcById,
|
|
137
|
+
analyzerName: this.name,
|
|
138
|
+
};
|
|
150
139
|
for (const conv of conventions) {
|
|
151
|
-
|
|
152
|
-
continue;
|
|
153
|
-
const antecedent = conv.antecedent;
|
|
154
|
-
const consequent = conv.consequent;
|
|
155
|
-
const callerIds = antecedentCallers.get(antecedent);
|
|
156
|
-
if (!callerIds || callerIds.size === 0)
|
|
157
|
-
continue;
|
|
158
|
-
for (const cid of callerIds) {
|
|
159
|
-
const callSet = callerCalls.get(cid);
|
|
160
|
-
if (!callSet || !callSet.has(consequent)) {
|
|
161
|
-
const func = funcById.get(cid);
|
|
162
|
-
if (!func)
|
|
163
|
-
continue;
|
|
164
|
-
const exemplarRef = conv.exemplar_file
|
|
165
|
-
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
166
|
-
: '';
|
|
167
|
-
const pct = Math.round(conv.confidence * 100);
|
|
168
|
-
violations.push({
|
|
169
|
-
file: func.file_path,
|
|
170
|
-
line: func.line_number,
|
|
171
|
-
column: 1,
|
|
172
|
-
severity: 'suggestion',
|
|
173
|
-
message: `${pct}% of \`${antecedent}\` callers also call \`${consequent}\` — ` +
|
|
174
|
-
`this function calls \`${antecedent}\` without \`${consequent}\`${exemplarRef}`,
|
|
175
|
-
rule: 'conventions/usage-pair',
|
|
176
|
-
analyzer: this.name,
|
|
177
|
-
functionName: func.name,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
140
|
+
violations.push(...withRuleTiming('conventions/usage-pair', () => detectUsagePairForConvention(conv, ctx)));
|
|
181
141
|
}
|
|
182
142
|
return violations;
|
|
183
143
|
}
|
|
@@ -186,28 +146,10 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
186
146
|
* For each import-form convention (source → dominantForm in directory),
|
|
187
147
|
* find imports of that source that use a minority form.
|
|
188
148
|
*/
|
|
189
|
-
detectImportFormViolations(indexHandle, conventions, projectRoot,
|
|
149
|
+
detectImportFormViolations(indexHandle, conventions, projectRoot, readSource) {
|
|
190
150
|
const violations = [];
|
|
191
|
-
//
|
|
192
|
-
const dirImports =
|
|
193
|
-
for (const conv of conventions) {
|
|
194
|
-
const dir = conv.directory ?? '.';
|
|
195
|
-
const source = conv.antecedent;
|
|
196
|
-
if (!source)
|
|
197
|
-
continue;
|
|
198
|
-
const form = conv.consequent;
|
|
199
|
-
if (!form)
|
|
200
|
-
continue;
|
|
201
|
-
if (!dirImports.has(dir))
|
|
202
|
-
dirImports.set(dir, new Map());
|
|
203
|
-
dirImports.get(dir).set(source, {
|
|
204
|
-
source,
|
|
205
|
-
form,
|
|
206
|
-
confidence: conv.confidence,
|
|
207
|
-
exemplar_file: conv.exemplar_file,
|
|
208
|
-
exemplar_line: conv.exemplar_line,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
151
|
+
// Build directory → Map<source, ImportConv>
|
|
152
|
+
const dirImports = buildDirImports(conventions);
|
|
211
153
|
// Get unique file paths
|
|
212
154
|
const fileRows = indexHandle.query('SELECT DISTINCT file_path FROM functions WHERE file_path IS NOT NULL');
|
|
213
155
|
const seenFiles = new Set();
|
|
@@ -220,36 +162,10 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
220
162
|
if (!importConvs)
|
|
221
163
|
continue;
|
|
222
164
|
const fullPath = projectRoot ? path.join(projectRoot, fp) : fp;
|
|
223
|
-
|
|
165
|
+
const content = readSource?.(fullPath);
|
|
224
166
|
if (content === undefined)
|
|
225
167
|
continue;
|
|
226
|
-
|
|
227
|
-
for (const imp of imports) {
|
|
228
|
-
const conv = importConvs.get(imp.source);
|
|
229
|
-
if (!conv || imp.form === conv.form)
|
|
230
|
-
continue;
|
|
231
|
-
const pct = Math.round(conv.confidence * 100);
|
|
232
|
-
const exemplarRef = conv.exemplar_file
|
|
233
|
-
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
234
|
-
: '';
|
|
235
|
-
violations.push({
|
|
236
|
-
file: fp,
|
|
237
|
-
line: imp.line,
|
|
238
|
-
column: 1,
|
|
239
|
-
severity: 'suggestion',
|
|
240
|
-
message: `${pct}% of imports of \`${imp.source}\` in \`${directory}/\` ` +
|
|
241
|
-
`use ${conv.form} import — this file uses ${imp.form}${exemplarRef}`,
|
|
242
|
-
rule: 'conventions/import-form',
|
|
243
|
-
analyzer: this.name,
|
|
244
|
-
details: {
|
|
245
|
-
source: imp.source,
|
|
246
|
-
directory,
|
|
247
|
-
conventionForm: conv.form,
|
|
248
|
-
actualForm: imp.form,
|
|
249
|
-
localNames: imp.localNames,
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
}
|
|
168
|
+
violations.push(...detectImportFormForFile(fp, content, importConvs, this.name));
|
|
253
169
|
}
|
|
254
170
|
return violations;
|
|
255
171
|
}
|
|
@@ -263,55 +179,14 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
263
179
|
detectErrorHandlingViolations(indexHandle, conventions) {
|
|
264
180
|
const violations = [];
|
|
265
181
|
// directory → dominantShape
|
|
266
|
-
const dirShapes =
|
|
267
|
-
for (const conv of conventions) {
|
|
268
|
-
const dir = conv.directory ?? '.';
|
|
269
|
-
const shape = conv.pattern;
|
|
270
|
-
if (!shape)
|
|
271
|
-
continue;
|
|
272
|
-
dirShapes.set(dir, {
|
|
273
|
-
shape,
|
|
274
|
-
confidence: conv.confidence,
|
|
275
|
-
exemplar_file: conv.exemplar_file,
|
|
276
|
-
exemplar_line: conv.exemplar_line,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
182
|
+
const dirShapes = buildDirShapes(conventions);
|
|
279
183
|
const rows = indexHandle.query(`SELECT id, name, file_path, line_number, metadata_json
|
|
280
184
|
FROM functions
|
|
281
185
|
WHERE metadata_json IS NOT NULL`);
|
|
282
186
|
for (const row of rows) {
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
continue;
|
|
287
|
-
let metadata;
|
|
288
|
-
try {
|
|
289
|
-
metadata = JSON.parse(row.metadata_json);
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
const body = metadata.body;
|
|
295
|
-
const shape = detectErrorHandlingShape(body);
|
|
296
|
-
if (!shape)
|
|
297
|
-
continue; // no error handling → skip
|
|
298
|
-
if (shape === conv.shape)
|
|
299
|
-
continue; // matches convention
|
|
300
|
-
const pct = Math.round(conv.confidence * 100);
|
|
301
|
-
const exemplarRef = conv.exemplar_file
|
|
302
|
-
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
303
|
-
: '';
|
|
304
|
-
violations.push({
|
|
305
|
-
file: row.file_path,
|
|
306
|
-
line: row.line_number,
|
|
307
|
-
column: 1,
|
|
308
|
-
severity: 'suggestion',
|
|
309
|
-
message: `${pct}% of error-handling functions in \`${directory}/\` use ` +
|
|
310
|
-
`\`${conv.shape}\` — this function uses \`${shape}\`${exemplarRef}`,
|
|
311
|
-
rule: 'conventions/error-handling',
|
|
312
|
-
analyzer: this.name,
|
|
313
|
-
functionName: row.name,
|
|
314
|
-
});
|
|
187
|
+
const violation = detectErrorHandlingForRow(row, dirShapes, this.name);
|
|
188
|
+
if (violation)
|
|
189
|
+
violations.push(violation);
|
|
315
190
|
}
|
|
316
191
|
return violations;
|
|
317
192
|
}
|
|
@@ -323,49 +198,14 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
323
198
|
detectExportShapeViolations(indexHandle, conventions, exportsMap) {
|
|
324
199
|
const violations = [];
|
|
325
200
|
// directory → dominantForm
|
|
326
|
-
const dirForms =
|
|
327
|
-
for (const conv of conventions) {
|
|
328
|
-
const dir = conv.directory ?? '.';
|
|
329
|
-
const form = conv.pattern;
|
|
330
|
-
if (!form)
|
|
331
|
-
continue;
|
|
332
|
-
dirForms.set(dir, {
|
|
333
|
-
form,
|
|
334
|
-
confidence: conv.confidence,
|
|
335
|
-
exemplar_file: conv.exemplar_file,
|
|
336
|
-
exemplar_line: conv.exemplar_line,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
201
|
+
const dirForms = buildDirForms(conventions);
|
|
339
202
|
const rows = indexHandle.query(`SELECT id, name, file_path, line_number, is_exported
|
|
340
203
|
FROM functions
|
|
341
204
|
WHERE is_exported = 1`);
|
|
342
205
|
for (const row of rows) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
continue;
|
|
347
|
-
// B2: Use AST-extracted ExportInfo[] from function-index facts
|
|
348
|
-
const fileExports = exportsMap?.get(row.file_path);
|
|
349
|
-
if (!fileExports)
|
|
350
|
-
continue;
|
|
351
|
-
const form = detectExportForm(row.name, fileExports);
|
|
352
|
-
if (!form || form === conv.form)
|
|
353
|
-
continue;
|
|
354
|
-
const pct = Math.round(conv.confidence * 100);
|
|
355
|
-
const exemplarRef = conv.exemplar_file
|
|
356
|
-
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
357
|
-
: '';
|
|
358
|
-
violations.push({
|
|
359
|
-
file: row.file_path,
|
|
360
|
-
line: row.line_number,
|
|
361
|
-
column: 1,
|
|
362
|
-
severity: 'suggestion',
|
|
363
|
-
message: `${pct}% of exports in \`${directory}/\` use ${conv.form} export — ` +
|
|
364
|
-
`\`${row.name}\` uses ${form}${exemplarRef}`,
|
|
365
|
-
rule: 'conventions/export-shape',
|
|
366
|
-
analyzer: this.name,
|
|
367
|
-
functionName: row.name,
|
|
368
|
-
});
|
|
206
|
+
const violation = detectExportShapeForRow(row, dirForms, exportsMap, this.name);
|
|
207
|
+
if (violation)
|
|
208
|
+
violations.push(violation);
|
|
369
209
|
}
|
|
370
210
|
return violations;
|
|
371
211
|
}
|
|
@@ -378,69 +218,308 @@ export class UniversalConventionsAnalyzer extends UniversalAnalyzer {
|
|
|
378
218
|
detectNamingViolations(indexHandle, conventions) {
|
|
379
219
|
const violations = [];
|
|
380
220
|
// directory → kind → convention
|
|
381
|
-
const dirKindCases =
|
|
382
|
-
for (const conv of conventions) {
|
|
383
|
-
const dir = conv.directory ?? '.';
|
|
384
|
-
const casing = conv.pattern;
|
|
385
|
-
if (!casing)
|
|
386
|
-
continue;
|
|
387
|
-
const kind = conv.export_kind ?? 'function';
|
|
388
|
-
if (!dirKindCases.has(dir))
|
|
389
|
-
dirKindCases.set(dir, new Map());
|
|
390
|
-
dirKindCases.get(dir).set(kind, {
|
|
391
|
-
casing,
|
|
392
|
-
confidence: conv.confidence,
|
|
393
|
-
exemplar_file: conv.exemplar_file,
|
|
394
|
-
exemplar_line: conv.exemplar_line,
|
|
395
|
-
kind,
|
|
396
|
-
});
|
|
397
|
-
}
|
|
221
|
+
const dirKindCases = buildDirKindCases(conventions);
|
|
398
222
|
const rows = indexHandle.query(`SELECT id, name, file_path, line_number, is_exported, entity_type, component_type
|
|
399
223
|
FROM functions
|
|
400
224
|
WHERE is_exported = 1`);
|
|
401
225
|
for (const row of rows) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
226
|
+
const violation = detectNamingForRow(row, dirKindCases, this.name);
|
|
227
|
+
if (violation)
|
|
228
|
+
violations.push(violation);
|
|
229
|
+
}
|
|
230
|
+
return violations;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** Build the `(exemplar: file:line)` suffix shared across convention messages. */
|
|
234
|
+
function exemplarRef(conv) {
|
|
235
|
+
return conv.exemplar_file
|
|
236
|
+
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
237
|
+
: '';
|
|
238
|
+
}
|
|
239
|
+
/** Group convention rows by their `domain` field. */
|
|
240
|
+
function groupConventionsByDomain(conventions) {
|
|
241
|
+
const byDomain = new Map();
|
|
242
|
+
for (const conv of conventions) {
|
|
243
|
+
const list = byDomain.get(conv.domain);
|
|
244
|
+
if (list)
|
|
245
|
+
list.push(conv);
|
|
246
|
+
else
|
|
247
|
+
byDomain.set(conv.domain, [conv]);
|
|
248
|
+
}
|
|
249
|
+
return byDomain;
|
|
250
|
+
}
|
|
251
|
+
/** Count distinct file paths in the function index (files conventions apply to). */
|
|
252
|
+
function countUniqueFiles(indexHandle, projectRoot) {
|
|
253
|
+
const rows = indexHandle.query('SELECT DISTINCT file_path FROM functions WHERE file_path IS NOT NULL');
|
|
254
|
+
return rows.length;
|
|
255
|
+
}
|
|
256
|
+
/** Index call rows in both directions: caller→callees and callee→callers. */
|
|
257
|
+
function buildCallMaps(allCalls) {
|
|
258
|
+
const callerCalls = new Map();
|
|
259
|
+
const antecedentCallers = new Map();
|
|
260
|
+
for (const call of allCalls) {
|
|
261
|
+
const callees = callerCalls.get(call.caller_id);
|
|
262
|
+
if (callees)
|
|
263
|
+
callees.add(call.callee_name);
|
|
264
|
+
else
|
|
265
|
+
callerCalls.set(call.caller_id, new Set([call.callee_name]));
|
|
266
|
+
const callers = antecedentCallers.get(call.callee_name);
|
|
267
|
+
if (callers)
|
|
268
|
+
callers.add(call.caller_id);
|
|
269
|
+
else
|
|
270
|
+
antecedentCallers.set(call.callee_name, new Set([call.caller_id]));
|
|
271
|
+
}
|
|
272
|
+
return { callerCalls, antecedentCallers };
|
|
273
|
+
}
|
|
274
|
+
/** Index function rows by id for caller→function lookups. */
|
|
275
|
+
function buildFuncById(funcRows) {
|
|
276
|
+
const map = new Map();
|
|
277
|
+
for (const row of funcRows)
|
|
278
|
+
map.set(row.id, row);
|
|
279
|
+
return map;
|
|
280
|
+
}
|
|
281
|
+
/** Emit usage-pair violations for a single convention row. */
|
|
282
|
+
function detectUsagePairForConvention(conv, ctx) {
|
|
283
|
+
const violations = [];
|
|
284
|
+
if (!conv.antecedent || !conv.consequent)
|
|
285
|
+
return violations;
|
|
286
|
+
const antecedent = conv.antecedent;
|
|
287
|
+
const consequent = conv.consequent;
|
|
288
|
+
const callerIds = ctx.antecedentCallers.get(antecedent);
|
|
289
|
+
if (!callerIds || callerIds.size === 0)
|
|
290
|
+
return violations;
|
|
291
|
+
for (const cid of callerIds) {
|
|
292
|
+
const callSet = ctx.callerCalls.get(cid);
|
|
293
|
+
if (!callSet || !callSet.has(consequent)) {
|
|
294
|
+
const func = ctx.funcById.get(cid);
|
|
295
|
+
if (!func)
|
|
426
296
|
continue;
|
|
427
297
|
const pct = Math.round(conv.confidence * 100);
|
|
428
|
-
const exemplarRef = conv.exemplar_file
|
|
429
|
-
? ` (exemplar: ${conv.exemplar_file}${conv.exemplar_line ? `:${conv.exemplar_line}` : ''})`
|
|
430
|
-
: '';
|
|
431
298
|
violations.push({
|
|
432
|
-
file:
|
|
433
|
-
line:
|
|
299
|
+
file: func.file_path,
|
|
300
|
+
line: func.line_number,
|
|
434
301
|
column: 1,
|
|
435
302
|
severity: 'suggestion',
|
|
436
|
-
message: `${pct}% of
|
|
437
|
-
|
|
438
|
-
rule: 'conventions/
|
|
439
|
-
analyzer:
|
|
440
|
-
functionName:
|
|
303
|
+
message: `${pct}% of \`${antecedent}\` callers also call \`${consequent}\` — ` +
|
|
304
|
+
`this function calls \`${antecedent}\` without \`${consequent}\`${exemplarRef(conv)}`,
|
|
305
|
+
rule: 'conventions/usage-pair',
|
|
306
|
+
analyzer: ctx.analyzerName,
|
|
307
|
+
functionName: func.name,
|
|
308
|
+
resolution: {
|
|
309
|
+
action: 'call-companion',
|
|
310
|
+
summary: `Add a call to the companion function \`${consequent}\` in \`${func.name}\` — ${pct}% of \`${antecedent}\` callers also call it.`,
|
|
311
|
+
symbols: [consequent, antecedent],
|
|
312
|
+
files: [func.file_path],
|
|
313
|
+
lines: [func.line_number],
|
|
314
|
+
},
|
|
441
315
|
});
|
|
442
316
|
}
|
|
443
|
-
return violations;
|
|
444
317
|
}
|
|
318
|
+
return violations;
|
|
319
|
+
}
|
|
320
|
+
/** Build directory → Map<source, ImportConv> from import-form convention rows. */
|
|
321
|
+
function buildDirImports(conventions) {
|
|
322
|
+
const dirImports = new Map();
|
|
323
|
+
for (const conv of conventions) {
|
|
324
|
+
const dir = conv.directory ?? '.';
|
|
325
|
+
const source = conv.antecedent;
|
|
326
|
+
if (!source)
|
|
327
|
+
continue;
|
|
328
|
+
const form = conv.consequent;
|
|
329
|
+
if (!form)
|
|
330
|
+
continue;
|
|
331
|
+
if (!dirImports.has(dir))
|
|
332
|
+
dirImports.set(dir, new Map());
|
|
333
|
+
dirImports.get(dir).set(source, {
|
|
334
|
+
source,
|
|
335
|
+
form,
|
|
336
|
+
confidence: conv.confidence,
|
|
337
|
+
exemplar_file: conv.exemplar_file,
|
|
338
|
+
exemplar_line: conv.exemplar_line,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return dirImports;
|
|
342
|
+
}
|
|
343
|
+
/** Detect import-form violations within one file's parsed imports. */
|
|
344
|
+
function detectImportFormForFile(fp, content, importConvs, analyzerName) {
|
|
345
|
+
const violations = [];
|
|
346
|
+
const directory = path.dirname(fp) || '.';
|
|
347
|
+
const imports = parseFileImports(content);
|
|
348
|
+
for (const imp of imports) {
|
|
349
|
+
const conv = importConvs.get(imp.source);
|
|
350
|
+
if (!conv || imp.form === conv.form)
|
|
351
|
+
continue;
|
|
352
|
+
const pct = Math.round(conv.confidence * 100);
|
|
353
|
+
violations.push({
|
|
354
|
+
file: fp,
|
|
355
|
+
line: imp.line,
|
|
356
|
+
column: 1,
|
|
357
|
+
severity: 'suggestion',
|
|
358
|
+
message: `${pct}% of imports of \`${imp.source}\` in \`${directory}/\` ` +
|
|
359
|
+
`use ${conv.form} import — this file uses ${imp.form}${exemplarRef(conv)}`,
|
|
360
|
+
rule: 'conventions/import-form',
|
|
361
|
+
analyzer: analyzerName,
|
|
362
|
+
details: {
|
|
363
|
+
source: imp.source,
|
|
364
|
+
directory,
|
|
365
|
+
conventionForm: conv.form,
|
|
366
|
+
actualForm: imp.form,
|
|
367
|
+
localNames: imp.localNames,
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
return violations;
|
|
372
|
+
}
|
|
373
|
+
/** Build directory → dominant error-handling shape from convention rows. */
|
|
374
|
+
function buildDirShapes(conventions) {
|
|
375
|
+
const dirShapes = new Map();
|
|
376
|
+
for (const conv of conventions) {
|
|
377
|
+
const dir = conv.directory ?? '.';
|
|
378
|
+
const shape = conv.pattern;
|
|
379
|
+
if (!shape)
|
|
380
|
+
continue;
|
|
381
|
+
dirShapes.set(dir, {
|
|
382
|
+
shape,
|
|
383
|
+
confidence: conv.confidence,
|
|
384
|
+
exemplar_file: conv.exemplar_file,
|
|
385
|
+
exemplar_line: conv.exemplar_line,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return dirShapes;
|
|
389
|
+
}
|
|
390
|
+
/** Detect an error-handling-shape deviation for one function row, if any. */
|
|
391
|
+
function detectErrorHandlingForRow(row, dirShapes, analyzerName) {
|
|
392
|
+
const directory = path.dirname(row.file_path) || '.';
|
|
393
|
+
const conv = dirShapes.get(directory);
|
|
394
|
+
if (!conv)
|
|
395
|
+
return null;
|
|
396
|
+
let metadata;
|
|
397
|
+
try {
|
|
398
|
+
metadata = JSON.parse(row.metadata_json);
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
const body = metadata.body;
|
|
404
|
+
const shape = detectErrorHandlingShape(body);
|
|
405
|
+
if (!shape)
|
|
406
|
+
return null; // no error handling → skip
|
|
407
|
+
if (shape === conv.shape)
|
|
408
|
+
return null; // matches convention
|
|
409
|
+
const pct = Math.round(conv.confidence * 100);
|
|
410
|
+
return {
|
|
411
|
+
file: row.file_path,
|
|
412
|
+
line: row.line_number,
|
|
413
|
+
column: 1,
|
|
414
|
+
severity: 'suggestion',
|
|
415
|
+
message: `${pct}% of error-handling functions in \`${directory}/\` use ` +
|
|
416
|
+
`\`${conv.shape}\` — this function uses \`${shape}\`${exemplarRef(conv)}`,
|
|
417
|
+
rule: 'conventions/error-handling',
|
|
418
|
+
analyzer: analyzerName,
|
|
419
|
+
functionName: row.name,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
/** Build directory → dominant export form from convention rows. */
|
|
423
|
+
function buildDirForms(conventions) {
|
|
424
|
+
const dirForms = new Map();
|
|
425
|
+
for (const conv of conventions) {
|
|
426
|
+
const dir = conv.directory ?? '.';
|
|
427
|
+
const form = conv.pattern;
|
|
428
|
+
if (!form)
|
|
429
|
+
continue;
|
|
430
|
+
dirForms.set(dir, {
|
|
431
|
+
form,
|
|
432
|
+
confidence: conv.confidence,
|
|
433
|
+
exemplar_file: conv.exemplar_file,
|
|
434
|
+
exemplar_line: conv.exemplar_line,
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return dirForms;
|
|
438
|
+
}
|
|
439
|
+
/** Detect an export-shape deviation for one function row, if any. */
|
|
440
|
+
function detectExportShapeForRow(row, dirForms, exportsMap, analyzerName) {
|
|
441
|
+
const directory = path.dirname(row.file_path) || '.';
|
|
442
|
+
const conv = dirForms.get(directory);
|
|
443
|
+
if (!conv)
|
|
444
|
+
return null;
|
|
445
|
+
// B2: Use AST-extracted ExportInfo[] from function-index facts
|
|
446
|
+
const fileExports = exportsMap?.get(row.file_path);
|
|
447
|
+
if (!fileExports)
|
|
448
|
+
return null;
|
|
449
|
+
const form = detectExportForm(row.name, fileExports);
|
|
450
|
+
if (!form || form === conv.form)
|
|
451
|
+
return null;
|
|
452
|
+
const pct = Math.round(conv.confidence * 100);
|
|
453
|
+
return {
|
|
454
|
+
file: row.file_path,
|
|
455
|
+
line: row.line_number,
|
|
456
|
+
column: 1,
|
|
457
|
+
severity: 'suggestion',
|
|
458
|
+
message: `${pct}% of exports in \`${directory}/\` use ${conv.form} export — ` +
|
|
459
|
+
`\`${row.name}\` uses ${form}${exemplarRef(conv)}`,
|
|
460
|
+
rule: 'conventions/export-shape',
|
|
461
|
+
analyzer: analyzerName,
|
|
462
|
+
functionName: row.name,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
/** Build directory → kind → convention from naming convention rows. */
|
|
466
|
+
function buildDirKindCases(conventions) {
|
|
467
|
+
const dirKindCases = new Map();
|
|
468
|
+
for (const conv of conventions) {
|
|
469
|
+
const dir = conv.directory ?? '.';
|
|
470
|
+
const casing = conv.pattern;
|
|
471
|
+
if (!casing)
|
|
472
|
+
continue;
|
|
473
|
+
const kind = conv.export_kind ?? 'function';
|
|
474
|
+
if (!dirKindCases.has(dir))
|
|
475
|
+
dirKindCases.set(dir, new Map());
|
|
476
|
+
dirKindCases.get(dir).set(kind, {
|
|
477
|
+
casing,
|
|
478
|
+
confidence: conv.confidence,
|
|
479
|
+
exemplar_file: conv.exemplar_file,
|
|
480
|
+
exemplar_line: conv.exemplar_line,
|
|
481
|
+
kind,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
return dirKindCases;
|
|
485
|
+
}
|
|
486
|
+
/** Classify an exported function into a naming kind (same logic as mineNaming). */
|
|
487
|
+
function classifyExportKind(row) {
|
|
488
|
+
if (row.entity_type === 'component' || row.component_type !== null) {
|
|
489
|
+
return 'react-component';
|
|
490
|
+
}
|
|
491
|
+
if (/^use[A-Z]/.test(row.name)) {
|
|
492
|
+
return 'hook';
|
|
493
|
+
}
|
|
494
|
+
return 'function';
|
|
495
|
+
}
|
|
496
|
+
/** Detect a naming-casing deviation for one function row, if any. */
|
|
497
|
+
function detectNamingForRow(row, dirKindCases, analyzerName) {
|
|
498
|
+
const directory = path.dirname(row.file_path) || '.';
|
|
499
|
+
const kindConvs = dirKindCases.get(directory);
|
|
500
|
+
if (!kindConvs)
|
|
501
|
+
return null;
|
|
502
|
+
const rowKind = classifyExportKind(row);
|
|
503
|
+
const conv = kindConvs.get(rowKind);
|
|
504
|
+
if (!conv)
|
|
505
|
+
return null;
|
|
506
|
+
// Non-Latin skip (Spec 21 R5.4)
|
|
507
|
+
if (hasNonLatinChars(row.name))
|
|
508
|
+
return null;
|
|
509
|
+
const casing = detectCase(row.name);
|
|
510
|
+
if (!casing || casing === conv.casing)
|
|
511
|
+
return null;
|
|
512
|
+
const pct = Math.round(conv.confidence * 100);
|
|
513
|
+
return {
|
|
514
|
+
file: row.file_path,
|
|
515
|
+
line: row.line_number,
|
|
516
|
+
column: 1,
|
|
517
|
+
severity: 'suggestion',
|
|
518
|
+
message: `${pct}% of ${conv.kind} exports in \`${directory}/\` use ${conv.casing} — ` +
|
|
519
|
+
`\`${row.name}\` uses ${casing}${exemplarRef(conv)}`,
|
|
520
|
+
rule: 'conventions/naming',
|
|
521
|
+
analyzer: analyzerName,
|
|
522
|
+
functionName: row.name,
|
|
523
|
+
};
|
|
445
524
|
}
|
|
446
525
|
//# sourceMappingURL=UniversalConventionsAnalyzer.js.map
|