code-auditor-mcp 3.4.18 → 3.6.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 +99 -0
- package/README.md +5 -5
- package/dist/analyzers/applicability.d.ts +19 -5
- package/dist/analyzers/applicability.d.ts.map +1 -1
- package/dist/analyzers/applicability.js +36 -24
- package/dist/analyzers/applicability.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +23 -2
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +52 -9
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +228 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +19 -12
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +78 -111
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +0 -6
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +85 -73
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +5 -5
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +1 -0
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +7 -9
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +88 -124
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +120 -21
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +35 -12
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +66 -108
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +2 -2
- package/dist/analyzers/ruleTiming.js +2 -2
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +60 -24
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +5 -2
- 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 +82 -71
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +2 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +23 -12
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +11 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +60 -22
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +5 -2
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +2 -3
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +258 -87
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js +7 -1
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -1
- package/dist/analyzers/universal/schema/discovery.d.ts +29 -1
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -1
- package/dist/analyzers/universal/schema/discovery.js +76 -21
- package/dist/analyzers/universal/schema/discovery.js.map +1 -1
- package/dist/analyzers/universal/schema/jsonSchema.js +7 -11
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -1
- package/dist/auditRouter.d.ts +41 -0
- package/dist/auditRouter.d.ts.map +1 -0
- package/dist/auditRouter.js +185 -0
- package/dist/auditRouter.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +93 -84
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +306 -95
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +41 -7
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/componentScanner.d.ts.map +1 -1
- package/dist/componentScanner.js +2 -1
- package/dist/componentScanner.js.map +1 -1
- package/dist/config/configLoader.d.ts +9 -0
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +45 -2
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +8 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +19 -3
- package/dist/config/defaults.js.map +1 -1
- package/dist/dataPaths.js +18 -1
- package/dist/dataPaths.js.map +1 -1
- package/dist/enforcement/gate.d.ts +14 -19
- package/dist/enforcement/gate.d.ts.map +1 -1
- package/dist/enforcement/gate.js +27 -45
- package/dist/enforcement/gate.js.map +1 -1
- package/dist/functionScanner.d.ts.map +1 -1
- package/dist/functionScanner.js +1 -17
- package/dist/functionScanner.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +8 -0
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +35 -16
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +9 -10
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +135 -101
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +28 -1
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +158 -47
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +2 -0
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +15 -22
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/go/analyzer +0 -0
- package/dist/languages/go/analyzer-src/analyzer.go +510 -0
- package/dist/languages/go/analyzer-src/go.mod +3 -0
- package/dist/languages/go/analyzer-src/indexer.go +268 -0
- package/dist/languages/go/analyzer-src/parser.go +461 -0
- package/dist/languages/go/analyzer-src/solid.go +395 -0
- package/dist/languages/go/analyzer-src/testconventions.go +25 -0
- package/dist/languages/go/analyzer-src/types.go +148 -0
- package/dist/languages/go/go.mod +7 -0
- package/dist/languages/go/main.go +203 -0
- package/dist/languages/testConventions.d.ts +35 -0
- package/dist/languages/testConventions.d.ts.map +1 -0
- package/dist/languages/testConventions.js +51 -0
- package/dist/languages/testConventions.js.map +1 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +3 -0
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +25 -18
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools/whitelistTools.d.ts +1 -1
- package/dist/mcp-tools-shared.d.ts +0 -3
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js +8 -117
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/mcp.js +3 -1
- package/dist/mcp.js.map +1 -1
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +13 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/nextFile.d.ts +21 -0
- package/dist/nextFile.d.ts.map +1 -0
- package/dist/nextFile.js +51 -0
- package/dist/nextFile.js.map +1 -0
- package/dist/nextFileIncremental.d.ts +141 -0
- package/dist/nextFileIncremental.d.ts.map +1 -0
- package/dist/nextFileIncremental.js +314 -0
- package/dist/nextFileIncremental.js.map +1 -0
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +43 -3
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +17 -0
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +791 -40
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +11 -30
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/styles/styleExtractor.js +2 -2
- package/dist/styles/styleExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.d.ts +7 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -1
- package/dist/styles/styleIndexer.js +51 -67
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/types.d.ts +3 -2
- package/dist/styles/types.d.ts.map +1 -1
- package/dist/types/crossLanguage.d.ts +1 -0
- package/dist/types/crossLanguage.d.ts.map +1 -1
- package/dist/types.d.ts +43 -34
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/fileDiscovery.d.ts +9 -0
- package/dist/utils/fileDiscovery.d.ts.map +1 -1
- package/dist/utils/fileDiscovery.js +18 -0
- package/dist/utils/fileDiscovery.js.map +1 -1
- package/package.json +16 -6
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -9
- package/plugin/hooks/hooks.json +4 -0
- package/plugin/scripts/hook-audit.sh +20 -30
- package/plugin/scripts/hook-common.sh +63 -0
- package/plugin/scripts/hook-self-audit.sh +99 -0
- package/plugin/skills/code-auditor/SKILL-RULE-KINDS.md +55 -9
- package/plugin/skills/code-auditor/SKILL.md +22 -23
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
package analyzer
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/ast"
|
|
5
|
+
"strings"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// SOLIDAnalyzer analyzes Go code for SOLID principle violations
|
|
9
|
+
type SOLIDAnalyzer struct {
|
|
10
|
+
parser *Parser
|
|
11
|
+
functions []Function
|
|
12
|
+
structs []Struct
|
|
13
|
+
interfaces []Interface
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// NewSOLIDAnalyzer creates a new SOLID analyzer
|
|
17
|
+
func NewSOLIDAnalyzer(parser *Parser) *SOLIDAnalyzer {
|
|
18
|
+
return &SOLIDAnalyzer{
|
|
19
|
+
parser: parser,
|
|
20
|
+
functions: parser.ExtractFunctions(),
|
|
21
|
+
structs: parser.ExtractStructs(),
|
|
22
|
+
interfaces: parser.ExtractInterfaces(),
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Analyze performs SOLID principle analysis
|
|
27
|
+
func (s *SOLIDAnalyzer) Analyze() []Violation {
|
|
28
|
+
var violations []Violation
|
|
29
|
+
|
|
30
|
+
// Analyze Single Responsibility Principle
|
|
31
|
+
violations = append(violations, s.analyzeSRP()...)
|
|
32
|
+
|
|
33
|
+
// Analyze Open/Closed Principle
|
|
34
|
+
violations = append(violations, s.analyzeOCP()...)
|
|
35
|
+
|
|
36
|
+
// Analyze Liskov Substitution Principle
|
|
37
|
+
violations = append(violations, s.analyzeLSP()...)
|
|
38
|
+
|
|
39
|
+
// Analyze Interface Segregation Principle
|
|
40
|
+
violations = append(violations, s.analyzeISP()...)
|
|
41
|
+
|
|
42
|
+
// Analyze Dependency Inversion Principle
|
|
43
|
+
violations = append(violations, s.analyzeDIP()...)
|
|
44
|
+
|
|
45
|
+
return violations
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// analyzeSRP analyzes Single Responsibility Principle violations
|
|
49
|
+
func (s *SOLIDAnalyzer) analyzeSRP() []Violation {
|
|
50
|
+
var violations []Violation
|
|
51
|
+
|
|
52
|
+
// Check functions for too many responsibilities
|
|
53
|
+
for _, function := range s.functions {
|
|
54
|
+
responsibilities := s.countFunctionResponsibilities(function)
|
|
55
|
+
if responsibilities > 3 {
|
|
56
|
+
violations = append(violations, Violation{
|
|
57
|
+
File: function.File,
|
|
58
|
+
Line: function.StartLine,
|
|
59
|
+
Severity: "warning",
|
|
60
|
+
Message: "Function has many parameters, returns, or high complexity",
|
|
61
|
+
Details: map[string]interface{}{
|
|
62
|
+
"function": function.Name,
|
|
63
|
+
"responsibilities": responsibilities,
|
|
64
|
+
"principle": "SRP",
|
|
65
|
+
},
|
|
66
|
+
Suggestion: "Consider breaking this function into smaller, more focused functions",
|
|
67
|
+
Analyzer: "solid",
|
|
68
|
+
Category: "single-responsibility",
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check structs for too many responsibilities
|
|
74
|
+
for _, structInfo := range s.structs {
|
|
75
|
+
responsibilities := s.countStructResponsibilities(structInfo)
|
|
76
|
+
if responsibilities > 5 {
|
|
77
|
+
violations = append(violations, Violation{
|
|
78
|
+
File: structInfo.File,
|
|
79
|
+
Line: structInfo.StartLine,
|
|
80
|
+
Severity: "warning",
|
|
81
|
+
Message: "Struct has many fields",
|
|
82
|
+
Details: map[string]interface{}{
|
|
83
|
+
"struct": structInfo.Name,
|
|
84
|
+
"responsibilities": responsibilities,
|
|
85
|
+
"principle": "SRP",
|
|
86
|
+
"fieldCount": len(structInfo.Fields),
|
|
87
|
+
},
|
|
88
|
+
Suggestion: "Consider splitting this struct into smaller, more cohesive structs",
|
|
89
|
+
Analyzer: "solid",
|
|
90
|
+
Category: "single-responsibility",
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return violations
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// analyzeOCP analyzes Open/Closed Principle violations
|
|
99
|
+
func (s *SOLIDAnalyzer) analyzeOCP() []Violation {
|
|
100
|
+
var violations []Violation
|
|
101
|
+
|
|
102
|
+
// Check for large switch/case statements that could benefit from polymorphism
|
|
103
|
+
for filePath, file := range s.parser.files {
|
|
104
|
+
ast.Inspect(file, func(n ast.Node) bool {
|
|
105
|
+
switch node := n.(type) {
|
|
106
|
+
case *ast.SwitchStmt:
|
|
107
|
+
caseCount := s.countSwitchCases(node)
|
|
108
|
+
if caseCount > 5 {
|
|
109
|
+
pos := s.parser.fileSet.Position(node.Pos())
|
|
110
|
+
violations = append(violations, Violation{
|
|
111
|
+
File: filePath,
|
|
112
|
+
Line: pos.Line,
|
|
113
|
+
Severity: "suggestion",
|
|
114
|
+
Message: "Large switch statement detected - consider using polymorphism",
|
|
115
|
+
Details: map[string]interface{}{
|
|
116
|
+
"caseCount": caseCount,
|
|
117
|
+
"principle": "OCP",
|
|
118
|
+
},
|
|
119
|
+
Suggestion: "Consider using interfaces and polymorphism instead of large switch statements",
|
|
120
|
+
Analyzer: "solid",
|
|
121
|
+
Category: "open-closed",
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
case *ast.TypeSwitchStmt:
|
|
125
|
+
caseCount := s.countTypeSwitchCases(node)
|
|
126
|
+
if caseCount > 5 {
|
|
127
|
+
pos := s.parser.fileSet.Position(node.Pos())
|
|
128
|
+
violations = append(violations, Violation{
|
|
129
|
+
File: filePath,
|
|
130
|
+
Line: pos.Line,
|
|
131
|
+
Severity: "suggestion",
|
|
132
|
+
Message: "Large type switch detected - consider using interfaces",
|
|
133
|
+
Details: map[string]interface{}{
|
|
134
|
+
"caseCount": caseCount,
|
|
135
|
+
"principle": "OCP",
|
|
136
|
+
},
|
|
137
|
+
Suggestion: "Consider using interfaces with method dispatch instead of type switches",
|
|
138
|
+
Analyzer: "solid",
|
|
139
|
+
Category: "open-closed",
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return true
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return violations
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// analyzeLSP analyzes Liskov Substitution Principle violations.
|
|
151
|
+
//
|
|
152
|
+
// A method that calls panic() breaks substitutability: a caller holding the
|
|
153
|
+
// interface (or embedding) has no way to recover from a panic the way it can
|
|
154
|
+
// from an error return. The honest, directly-observable signal is "the method
|
|
155
|
+
// body contains a panic() call" — we walk the AST for exactly that. We do not
|
|
156
|
+
// (yet) resolve whether the method overrides a parent, so the message claims
|
|
157
|
+
// only the panic, never the override.
|
|
158
|
+
func (s *SOLIDAnalyzer) analyzeLSP() []Violation {
|
|
159
|
+
var violations []Violation
|
|
160
|
+
|
|
161
|
+
for filePath, file := range s.parser.files {
|
|
162
|
+
ast.Inspect(file, func(n ast.Node) bool {
|
|
163
|
+
funcDecl, ok := n.(*ast.FuncDecl)
|
|
164
|
+
if !ok {
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
// LSP governs methods only — a free function has no supertype to
|
|
168
|
+
// violate. Test functions are test entry points, not production API.
|
|
169
|
+
if funcDecl.Recv == nil || funcDecl.Body == nil || isTestFunction(funcDecl.Name.Name) {
|
|
170
|
+
return true
|
|
171
|
+
}
|
|
172
|
+
if !methodCallsPanic(funcDecl) {
|
|
173
|
+
return true
|
|
174
|
+
}
|
|
175
|
+
pos := s.parser.fileSet.Position(funcDecl.Pos())
|
|
176
|
+
violations = append(violations, Violation{
|
|
177
|
+
File: filePath,
|
|
178
|
+
Line: pos.Line,
|
|
179
|
+
Severity: "warning",
|
|
180
|
+
Message: "Method calls panic()",
|
|
181
|
+
Details: map[string]interface{}{
|
|
182
|
+
"function": funcDecl.Name.Name,
|
|
183
|
+
"principle": "LSP",
|
|
184
|
+
},
|
|
185
|
+
Suggestion: "Return an error instead of panicking so the method stays substitutable",
|
|
186
|
+
Analyzer: "solid",
|
|
187
|
+
Category: "liskov-substitution",
|
|
188
|
+
})
|
|
189
|
+
return true
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return violations
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// analyzeISP analyzes Interface Segregation Principle violations
|
|
197
|
+
func (s *SOLIDAnalyzer) analyzeISP() []Violation {
|
|
198
|
+
var violations []Violation
|
|
199
|
+
|
|
200
|
+
// Check for fat interfaces
|
|
201
|
+
for _, interfaceInfo := range s.interfaces {
|
|
202
|
+
if len(interfaceInfo.Methods) > 5 {
|
|
203
|
+
violations = append(violations, Violation{
|
|
204
|
+
File: interfaceInfo.File,
|
|
205
|
+
Line: interfaceInfo.StartLine,
|
|
206
|
+
Severity: "warning",
|
|
207
|
+
Message: "Interface has many methods",
|
|
208
|
+
Details: map[string]interface{}{
|
|
209
|
+
"interface": interfaceInfo.Name,
|
|
210
|
+
"methodCount": len(interfaceInfo.Methods),
|
|
211
|
+
"principle": "ISP",
|
|
212
|
+
},
|
|
213
|
+
Suggestion: "Consider splitting this interface into smaller, more focused interfaces",
|
|
214
|
+
Analyzer: "solid",
|
|
215
|
+
Category: "interface-segregation",
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return violations
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// analyzeDIP analyzes Dependency Inversion Principle violations
|
|
224
|
+
func (s *SOLIDAnalyzer) analyzeDIP() []Violation {
|
|
225
|
+
var violations []Violation
|
|
226
|
+
|
|
227
|
+
// Check for direct dependencies on concrete types instead of interfaces
|
|
228
|
+
for _, structInfo := range s.structs {
|
|
229
|
+
concreteDeps := s.countConcreteDependencies(structInfo)
|
|
230
|
+
if concreteDeps > 3 {
|
|
231
|
+
violations = append(violations, Violation{
|
|
232
|
+
File: structInfo.File,
|
|
233
|
+
Line: structInfo.StartLine,
|
|
234
|
+
Severity: "suggestion",
|
|
235
|
+
Message: "Struct has many concrete-typed fields",
|
|
236
|
+
Details: map[string]interface{}{
|
|
237
|
+
"struct": structInfo.Name,
|
|
238
|
+
"concreteDependencies": concreteDeps,
|
|
239
|
+
"principle": "DIP",
|
|
240
|
+
},
|
|
241
|
+
Suggestion: "Consider depending on interfaces instead of concrete types",
|
|
242
|
+
Analyzer: "solid",
|
|
243
|
+
Category: "dependency-inversion",
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return violations
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Helper methods for analysis
|
|
252
|
+
|
|
253
|
+
func (s *SOLIDAnalyzer) countFunctionResponsibilities(function Function) int {
|
|
254
|
+
responsibilities := 1
|
|
255
|
+
|
|
256
|
+
// Count different types of operations
|
|
257
|
+
if function.Complexity > 10 {
|
|
258
|
+
responsibilities++
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Check for multiple return types (excluding error)
|
|
262
|
+
returns := strings.Split(function.ReturnType, ",")
|
|
263
|
+
if len(returns) > 2 {
|
|
264
|
+
responsibilities++
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check parameter count
|
|
268
|
+
if len(function.Parameters) > 5 {
|
|
269
|
+
responsibilities++
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return responsibilities
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
func (s *SOLIDAnalyzer) countStructResponsibilities(structInfo Struct) int {
|
|
276
|
+
responsibilities := 1
|
|
277
|
+
|
|
278
|
+
// Base on field count
|
|
279
|
+
fieldCount := len(structInfo.Fields)
|
|
280
|
+
if fieldCount > 10 {
|
|
281
|
+
responsibilities += 2
|
|
282
|
+
} else if fieldCount > 5 {
|
|
283
|
+
responsibilities++
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Check for mixed data types indicating different responsibilities
|
|
287
|
+
hasStrings := false
|
|
288
|
+
hasNumbers := false
|
|
289
|
+
hasCollections := false
|
|
290
|
+
|
|
291
|
+
for _, field := range structInfo.Fields {
|
|
292
|
+
switch {
|
|
293
|
+
case strings.Contains(field.Type, "string"):
|
|
294
|
+
hasStrings = true
|
|
295
|
+
case strings.Contains(field.Type, "int") || strings.Contains(field.Type, "float"):
|
|
296
|
+
hasNumbers = true
|
|
297
|
+
case strings.Contains(field.Type, "[]") || strings.Contains(field.Type, "map"):
|
|
298
|
+
hasCollections = true
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
mixedTypes := 0
|
|
303
|
+
if hasStrings {
|
|
304
|
+
mixedTypes++
|
|
305
|
+
}
|
|
306
|
+
if hasNumbers {
|
|
307
|
+
mixedTypes++
|
|
308
|
+
}
|
|
309
|
+
if hasCollections {
|
|
310
|
+
mixedTypes++
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if mixedTypes > 2 {
|
|
314
|
+
responsibilities++
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return responsibilities
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
func (s *SOLIDAnalyzer) countSwitchCases(switchStmt *ast.SwitchStmt) int {
|
|
321
|
+
caseCount := 0
|
|
322
|
+
if switchStmt.Body != nil {
|
|
323
|
+
for _, stmt := range switchStmt.Body.List {
|
|
324
|
+
if _, ok := stmt.(*ast.CaseClause); ok {
|
|
325
|
+
caseCount++
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return caseCount
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func (s *SOLIDAnalyzer) countTypeSwitchCases(typeSwitchStmt *ast.TypeSwitchStmt) int {
|
|
333
|
+
caseCount := 0
|
|
334
|
+
if typeSwitchStmt.Body != nil {
|
|
335
|
+
for _, stmt := range typeSwitchStmt.Body.List {
|
|
336
|
+
if _, ok := stmt.(*ast.CaseClause); ok {
|
|
337
|
+
caseCount++
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return caseCount
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
func (s *SOLIDAnalyzer) countConcreteDependencies(structInfo Struct) int {
|
|
345
|
+
concreteDeps := 0
|
|
346
|
+
|
|
347
|
+
for _, field := range structInfo.Fields {
|
|
348
|
+
// Check if field type looks like a concrete type (not interface)
|
|
349
|
+
if !strings.Contains(field.Type, "interface") &&
|
|
350
|
+
!strings.HasPrefix(field.Type, "*") && // Pointers might be interfaces
|
|
351
|
+
!s.isBuiltinType(field.Type) {
|
|
352
|
+
concreteDeps++
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return concreteDeps
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
func (s *SOLIDAnalyzer) isBuiltinType(typeName string) bool {
|
|
360
|
+
builtinTypes := []string{
|
|
361
|
+
"bool", "string", "int", "int8", "int16", "int32", "int64",
|
|
362
|
+
"uint", "uint8", "uint16", "uint32", "uint64", "uintptr",
|
|
363
|
+
"byte", "rune", "float32", "float64", "complex64", "complex128",
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for _, builtin := range builtinTypes {
|
|
367
|
+
if typeName == builtin {
|
|
368
|
+
return true
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return false
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// methodCallsPanic reports whether a function body contains a direct panic()
|
|
376
|
+
// call. It matches the exact call shape — a call expression whose function is
|
|
377
|
+
// the identifier `panic` — so a helper that merely has "panic" in its name or
|
|
378
|
+
// a doc comment does not trip it (the near-miss the old name-substring check
|
|
379
|
+
// false-positived on).
|
|
380
|
+
func methodCallsPanic(funcDecl *ast.FuncDecl) bool {
|
|
381
|
+
callsPanic := false
|
|
382
|
+
ast.Inspect(funcDecl.Body, func(n ast.Node) bool {
|
|
383
|
+
call, ok := n.(*ast.CallExpr)
|
|
384
|
+
if !ok {
|
|
385
|
+
return true
|
|
386
|
+
}
|
|
387
|
+
ident, ok := call.Fun.(*ast.Ident)
|
|
388
|
+
if ok && ident.Name == "panic" {
|
|
389
|
+
callsPanic = true
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
return true
|
|
393
|
+
})
|
|
394
|
+
return callsPanic
|
|
395
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package analyzer
|
|
2
|
+
|
|
3
|
+
import "strings"
|
|
4
|
+
|
|
5
|
+
// Go test conventions — mirrors languages/testConventions.ts.
|
|
6
|
+
//
|
|
7
|
+
// `go test` compiles only `*_test.go` files; within them the `testing` package
|
|
8
|
+
// recognizes Test/Benchmark/Example/Fuzz-prefixed functions as entry points.
|
|
9
|
+
// Test code is not production API: it is exempt from analysis and indexing the
|
|
10
|
+
// same way the TypeScript pipeline exempts `.test.`/`.spec.` files. One
|
|
11
|
+
// per-language convention, not a hardcoded TS/JS list.
|
|
12
|
+
|
|
13
|
+
// isTestFile reports whether filePath is a Go test file (`*_test.go`).
|
|
14
|
+
func isTestFile(filePath string) bool {
|
|
15
|
+
return strings.HasSuffix(filePath, "_test.go")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// isTestFunction reports whether name is a Go test function
|
|
19
|
+
// (Test*/Benchmark*/Example*/Fuzz*).
|
|
20
|
+
func isTestFunction(name string) bool {
|
|
21
|
+
return strings.HasPrefix(name, "Test") ||
|
|
22
|
+
strings.HasPrefix(name, "Benchmark") ||
|
|
23
|
+
strings.HasPrefix(name, "Example") ||
|
|
24
|
+
strings.HasPrefix(name, "Fuzz")
|
|
25
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
package analyzer
|
|
2
|
+
|
|
3
|
+
// AnalysisOptions represents options for the analysis
|
|
4
|
+
type AnalysisOptions struct {
|
|
5
|
+
Analyzers []string `json:"analyzers"`
|
|
6
|
+
MinSeverity string `json:"minSeverity"`
|
|
7
|
+
Timeout int `json:"timeout"`
|
|
8
|
+
Language string `json:"language"`
|
|
9
|
+
Verbose bool `json:"verbose"`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// AnalysisResult represents the result of code analysis
|
|
13
|
+
type AnalysisResult struct {
|
|
14
|
+
Violations []Violation `json:"violations"`
|
|
15
|
+
IndexEntries []IndexEntry `json:"indexEntries"`
|
|
16
|
+
Metrics Metrics `json:"metrics"`
|
|
17
|
+
Errors []Error `json:"errors"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Violation represents a code quality violation
|
|
21
|
+
type Violation struct {
|
|
22
|
+
File string `json:"file"`
|
|
23
|
+
Line int `json:"line"`
|
|
24
|
+
Column int `json:"column"`
|
|
25
|
+
Severity string `json:"severity"`
|
|
26
|
+
Message string `json:"message"`
|
|
27
|
+
Details map[string]interface{} `json:"details,omitempty"`
|
|
28
|
+
Snippet string `json:"snippet,omitempty"`
|
|
29
|
+
Suggestion string `json:"suggestion,omitempty"`
|
|
30
|
+
Analyzer string `json:"analyzer"`
|
|
31
|
+
Category string `json:"category"`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// IndexEntry represents an entity in the code index
|
|
35
|
+
type IndexEntry struct {
|
|
36
|
+
ID string `json:"id"`
|
|
37
|
+
Name string `json:"name"`
|
|
38
|
+
Type string `json:"type"`
|
|
39
|
+
Language string `json:"language"`
|
|
40
|
+
File string `json:"file"`
|
|
41
|
+
Signature string `json:"signature"`
|
|
42
|
+
Parameters []Parameter `json:"parameters"`
|
|
43
|
+
Purpose string `json:"purpose"`
|
|
44
|
+
Context string `json:"context"`
|
|
45
|
+
StartLine int `json:"startLine"`
|
|
46
|
+
EndLine int `json:"endLine"`
|
|
47
|
+
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Parameter represents a function parameter
|
|
51
|
+
type Parameter struct {
|
|
52
|
+
Name string `json:"name"`
|
|
53
|
+
Type string `json:"type"`
|
|
54
|
+
Optional bool `json:"optional"`
|
|
55
|
+
Language string `json:"language"`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Metrics represents analysis metrics
|
|
59
|
+
type Metrics struct {
|
|
60
|
+
FilesAnalyzed int64 `json:"filesAnalyzed"`
|
|
61
|
+
ExecutionTime int64 `json:"executionTime"`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Error represents an analysis error
|
|
65
|
+
type Error struct {
|
|
66
|
+
Message string `json:"message"`
|
|
67
|
+
Type string `json:"type"`
|
|
68
|
+
File string `json:"file,omitempty"`
|
|
69
|
+
Line int `json:"line,omitempty"`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// EntityInfo represents information about a Go entity
|
|
73
|
+
type EntityInfo struct {
|
|
74
|
+
Name string
|
|
75
|
+
Type string
|
|
76
|
+
File string
|
|
77
|
+
StartLine int
|
|
78
|
+
EndLine int
|
|
79
|
+
Signature string
|
|
80
|
+
Parameters []Parameter
|
|
81
|
+
ReturnType string
|
|
82
|
+
Purpose string
|
|
83
|
+
Context string
|
|
84
|
+
Receiver string // For methods
|
|
85
|
+
Package string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Function represents a Go function
|
|
89
|
+
type Function struct {
|
|
90
|
+
EntityInfo
|
|
91
|
+
IsMethod bool
|
|
92
|
+
IsExported bool
|
|
93
|
+
Complexity int
|
|
94
|
+
Dependencies []string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Struct represents a Go struct
|
|
98
|
+
type Struct struct {
|
|
99
|
+
EntityInfo
|
|
100
|
+
Fields []Field
|
|
101
|
+
Methods []string
|
|
102
|
+
IsExported bool
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Interface represents a Go interface
|
|
106
|
+
type Interface struct {
|
|
107
|
+
EntityInfo
|
|
108
|
+
Methods []Method
|
|
109
|
+
IsExported bool
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Field represents a struct field
|
|
113
|
+
type Field struct {
|
|
114
|
+
Name string
|
|
115
|
+
Type string
|
|
116
|
+
Tag string
|
|
117
|
+
IsExported bool
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Method represents an interface method
|
|
121
|
+
type Method struct {
|
|
122
|
+
Name string
|
|
123
|
+
Signature string
|
|
124
|
+
Parameters []Parameter
|
|
125
|
+
ReturnType string
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// SOLIDViolation represents SOLID principle violations specific to Go
|
|
129
|
+
type SOLIDViolation struct {
|
|
130
|
+
Principle string // SRP, OCP, LSP, ISP, DIP
|
|
131
|
+
Entity string
|
|
132
|
+
Description string
|
|
133
|
+
Suggestion string
|
|
134
|
+
Severity string
|
|
135
|
+
Examples []string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Package represents a Go package
|
|
139
|
+
type Package struct {
|
|
140
|
+
Name string
|
|
141
|
+
Path string
|
|
142
|
+
Files []string
|
|
143
|
+
Imports []string
|
|
144
|
+
Exports []string
|
|
145
|
+
Functions []Function
|
|
146
|
+
Structs []Struct
|
|
147
|
+
Interfaces []Interface
|
|
148
|
+
}
|