@nahisaho/musubix-core 1.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/bin/musubix.js +18 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +27 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +320 -0
- package/dist/auth/auth-manager.d.ts.map +1 -0
- package/dist/auth/auth-manager.js +580 -0
- package/dist/auth/auth-manager.js.map +1 -0
- package/dist/cli/base.d.ts +58 -0
- package/dist/cli/base.d.ts.map +1 -0
- package/dist/cli/base.js +93 -0
- package/dist/cli/base.js.map +1 -0
- package/dist/cli/commands/help.d.ts +17 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/help.js +228 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +25 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +38 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +258 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +9 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/codegen/coding-standards.d.ts +250 -0
- package/dist/codegen/coding-standards.d.ts.map +1 -0
- package/dist/codegen/coding-standards.js +976 -0
- package/dist/codegen/coding-standards.js.map +1 -0
- package/dist/codegen/coverage-reporter.d.ts +264 -0
- package/dist/codegen/coverage-reporter.d.ts.map +1 -0
- package/dist/codegen/coverage-reporter.js +697 -0
- package/dist/codegen/coverage-reporter.js.map +1 -0
- package/dist/codegen/dependency-analyzer.d.ts +271 -0
- package/dist/codegen/dependency-analyzer.d.ts.map +1 -0
- package/dist/codegen/dependency-analyzer.js +661 -0
- package/dist/codegen/dependency-analyzer.js.map +1 -0
- package/dist/codegen/generator.d.ts +275 -0
- package/dist/codegen/generator.d.ts.map +1 -0
- package/dist/codegen/generator.js +781 -0
- package/dist/codegen/generator.js.map +1 -0
- package/dist/codegen/index.d.ts +18 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +27 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/integration-test-generator.d.ts +312 -0
- package/dist/codegen/integration-test-generator.d.ts.map +1 -0
- package/dist/codegen/integration-test-generator.js +765 -0
- package/dist/codegen/integration-test-generator.js.map +1 -0
- package/dist/codegen/pattern-conformance.d.ts +309 -0
- package/dist/codegen/pattern-conformance.d.ts.map +1 -0
- package/dist/codegen/pattern-conformance.js +590 -0
- package/dist/codegen/pattern-conformance.js.map +1 -0
- package/dist/codegen/quality-metrics.d.ts +235 -0
- package/dist/codegen/quality-metrics.d.ts.map +1 -0
- package/dist/codegen/quality-metrics.js +439 -0
- package/dist/codegen/quality-metrics.js.map +1 -0
- package/dist/codegen/security-scanner.d.ts +179 -0
- package/dist/codegen/security-scanner.d.ts.map +1 -0
- package/dist/codegen/security-scanner.js +495 -0
- package/dist/codegen/security-scanner.js.map +1 -0
- package/dist/codegen/static-analyzer.d.ts +188 -0
- package/dist/codegen/static-analyzer.d.ts.map +1 -0
- package/dist/codegen/static-analyzer.js +490 -0
- package/dist/codegen/static-analyzer.js.map +1 -0
- package/dist/codegen/unit-test-generator.d.ts +289 -0
- package/dist/codegen/unit-test-generator.d.ts.map +1 -0
- package/dist/codegen/unit-test-generator.js +634 -0
- package/dist/codegen/unit-test-generator.js.map +1 -0
- package/dist/design/adr-generator.d.ts +227 -0
- package/dist/design/adr-generator.d.ts.map +1 -0
- package/dist/design/adr-generator.js +423 -0
- package/dist/design/adr-generator.js.map +1 -0
- package/dist/design/c4-generator.d.ts +267 -0
- package/dist/design/c4-generator.d.ts.map +1 -0
- package/dist/design/c4-generator.js +453 -0
- package/dist/design/c4-generator.js.map +1 -0
- package/dist/design/framework-optimizer.d.ts +190 -0
- package/dist/design/framework-optimizer.d.ts.map +1 -0
- package/dist/design/framework-optimizer.js +589 -0
- package/dist/design/framework-optimizer.js.map +1 -0
- package/dist/design/index.d.ts +12 -0
- package/dist/design/index.d.ts.map +1 -0
- package/dist/design/index.js +13 -0
- package/dist/design/index.js.map +1 -0
- package/dist/design/pattern-detector.d.ts +270 -0
- package/dist/design/pattern-detector.d.ts.map +1 -0
- package/dist/design/pattern-detector.js +621 -0
- package/dist/design/pattern-detector.js.map +1 -0
- package/dist/design/solid-validator.d.ts +188 -0
- package/dist/design/solid-validator.d.ts.map +1 -0
- package/dist/design/solid-validator.js +579 -0
- package/dist/design/solid-validator.js.map +1 -0
- package/dist/error/data-persistence.d.ts +311 -0
- package/dist/error/data-persistence.d.ts.map +1 -0
- package/dist/error/data-persistence.js +586 -0
- package/dist/error/data-persistence.js.map +1 -0
- package/dist/error/graceful-degradation.d.ts +309 -0
- package/dist/error/graceful-degradation.d.ts.map +1 -0
- package/dist/error/graceful-degradation.js +510 -0
- package/dist/error/graceful-degradation.js.map +1 -0
- package/dist/error/index.d.ts +11 -0
- package/dist/error/index.d.ts.map +1 -0
- package/dist/error/index.js +19 -0
- package/dist/error/index.js.map +1 -0
- package/dist/explanation/explanation-generator.d.ts +228 -0
- package/dist/explanation/explanation-generator.d.ts.map +1 -0
- package/dist/explanation/explanation-generator.js +662 -0
- package/dist/explanation/explanation-generator.js.map +1 -0
- package/dist/explanation/index.d.ts +11 -0
- package/dist/explanation/index.d.ts.map +1 -0
- package/dist/explanation/index.js +19 -0
- package/dist/explanation/index.js.map +1 -0
- package/dist/explanation/reasoning-chain.d.ts +314 -0
- package/dist/explanation/reasoning-chain.d.ts.map +1 -0
- package/dist/explanation/reasoning-chain.js +414 -0
- package/dist/explanation/reasoning-chain.js.map +1 -0
- package/dist/explanation/visual-explanation.d.ts +315 -0
- package/dist/explanation/visual-explanation.d.ts.map +1 -0
- package/dist/explanation/visual-explanation.js +667 -0
- package/dist/explanation/visual-explanation.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/requirements/decomposer.d.ts +235 -0
- package/dist/requirements/decomposer.d.ts.map +1 -0
- package/dist/requirements/decomposer.js +587 -0
- package/dist/requirements/decomposer.js.map +1 -0
- package/dist/requirements/related-finder.d.ts +261 -0
- package/dist/requirements/related-finder.d.ts.map +1 -0
- package/dist/requirements/related-finder.js +629 -0
- package/dist/requirements/related-finder.js.map +1 -0
- package/dist/traceability/impact.d.ts +196 -0
- package/dist/traceability/impact.d.ts.map +1 -0
- package/dist/traceability/impact.js +438 -0
- package/dist/traceability/impact.js.map +1 -0
- package/dist/traceability/index.d.ts +9 -0
- package/dist/traceability/index.d.ts.map +1 -0
- package/dist/traceability/index.js +10 -0
- package/dist/traceability/index.js.map +1 -0
- package/dist/traceability/manager.d.ts +266 -0
- package/dist/traceability/manager.d.ts.map +1 -0
- package/dist/traceability/manager.js +412 -0
- package/dist/traceability/manager.js.map +1 -0
- package/dist/types/common.d.ts +294 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +15 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/ears.d.ts +158 -0
- package/dist/types/ears.d.ts.map +1 -0
- package/dist/types/ears.js +33 -0
- package/dist/types/ears.js.map +1 -0
- package/dist/types/errors.d.ts +176 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +55 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/data-protector.d.ts +122 -0
- package/dist/utils/data-protector.d.ts.map +1 -0
- package/dist/utils/data-protector.js +275 -0
- package/dist/utils/data-protector.js.map +1 -0
- package/dist/utils/error-handler.d.ts +101 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +324 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/i18n-manager.d.ts +259 -0
- package/dist/utils/i18n-manager.d.ts.map +1 -0
- package/dist/utils/i18n-manager.js +554 -0
- package/dist/utils/i18n-manager.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +120 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +237 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/performance-profiler.d.ts +251 -0
- package/dist/utils/performance-profiler.d.ts.map +1 -0
- package/dist/utils/performance-profiler.js +458 -0
- package/dist/utils/performance-profiler.js.map +1 -0
- package/dist/utils/scalability-optimizer.d.ts +294 -0
- package/dist/utils/scalability-optimizer.d.ts.map +1 -0
- package/dist/utils/scalability-optimizer.js +606 -0
- package/dist/utils/scalability-optimizer.js.map +1 -0
- package/dist/utils/structured-logger.d.ts +294 -0
- package/dist/utils/structured-logger.d.ts.map +1 -0
- package/dist/utils/structured-logger.js +630 -0
- package/dist/utils/structured-logger.js.map +1 -0
- package/dist/utils/version-compatibility.d.ts +217 -0
- package/dist/utils/version-compatibility.d.ts.map +1 -0
- package/dist/utils/version-compatibility.js +443 -0
- package/dist/utils/version-compatibility.js.map +1 -0
- package/dist/validators/ears-validator.d.ts +182 -0
- package/dist/validators/ears-validator.d.ts.map +1 -0
- package/dist/validators/ears-validator.js +357 -0
- package/dist/validators/ears-validator.js.map +1 -0
- package/dist/validators/index.d.ts +8 -0
- package/dist/validators/index.d.ts.map +1 -0
- package/dist/validators/index.js +9 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +100 -0
|
@@ -0,0 +1,976 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Coding Standards Checker
|
|
3
|
+
*
|
|
4
|
+
* Checks and enforces coding standards
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
* @module codegen/coding-standards
|
|
8
|
+
*
|
|
9
|
+
* @see REQ-COD-005 - Coding Standards Enforcement
|
|
10
|
+
* @see Article III - Bidirectional Traceability
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Default configuration
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_STANDARDS_CONFIG = {
|
|
16
|
+
language: 'typescript',
|
|
17
|
+
enabledCategories: ['naming', 'formatting', 'structure', 'documentation', 'complexity'],
|
|
18
|
+
minSeverity: 'warning',
|
|
19
|
+
failOnWarning: false,
|
|
20
|
+
ignorePatterns: ['node_modules/**', 'dist/**', '*.min.*'],
|
|
21
|
+
customRules: [],
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Built-in rules
|
|
25
|
+
*/
|
|
26
|
+
export const BUILTIN_RULES = [
|
|
27
|
+
// Naming rules
|
|
28
|
+
{
|
|
29
|
+
id: 'naming/camel-case',
|
|
30
|
+
name: 'camelCase Variables',
|
|
31
|
+
description: 'Variables should use camelCase',
|
|
32
|
+
category: 'naming',
|
|
33
|
+
severity: 'warning',
|
|
34
|
+
enabled: true,
|
|
35
|
+
autoFixable: false,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'naming/pascal-case-class',
|
|
39
|
+
name: 'PascalCase Classes',
|
|
40
|
+
description: 'Classes should use PascalCase',
|
|
41
|
+
category: 'naming',
|
|
42
|
+
severity: 'error',
|
|
43
|
+
enabled: true,
|
|
44
|
+
autoFixable: false,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'naming/upper-case-constants',
|
|
48
|
+
name: 'UPPER_CASE Constants',
|
|
49
|
+
description: 'Constants should use UPPER_CASE',
|
|
50
|
+
category: 'naming',
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
enabled: true,
|
|
53
|
+
autoFixable: false,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'naming/prefix-interface',
|
|
57
|
+
name: 'Interface Prefix',
|
|
58
|
+
description: 'Interfaces should start with I prefix (optional)',
|
|
59
|
+
category: 'naming',
|
|
60
|
+
severity: 'info',
|
|
61
|
+
enabled: false,
|
|
62
|
+
autoFixable: false,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'naming/meaningful-names',
|
|
66
|
+
name: 'Meaningful Names',
|
|
67
|
+
description: 'Names should be descriptive and meaningful',
|
|
68
|
+
category: 'naming',
|
|
69
|
+
severity: 'warning',
|
|
70
|
+
enabled: true,
|
|
71
|
+
autoFixable: false,
|
|
72
|
+
},
|
|
73
|
+
// Formatting rules
|
|
74
|
+
{
|
|
75
|
+
id: 'formatting/max-line-length',
|
|
76
|
+
name: 'Max Line Length',
|
|
77
|
+
description: 'Lines should not exceed max length',
|
|
78
|
+
category: 'formatting',
|
|
79
|
+
severity: 'warning',
|
|
80
|
+
enabled: true,
|
|
81
|
+
options: { maxLength: 100 },
|
|
82
|
+
autoFixable: false,
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'formatting/consistent-indentation',
|
|
86
|
+
name: 'Consistent Indentation',
|
|
87
|
+
description: 'Use consistent indentation',
|
|
88
|
+
category: 'formatting',
|
|
89
|
+
severity: 'error',
|
|
90
|
+
enabled: true,
|
|
91
|
+
options: { style: 'space', size: 2 },
|
|
92
|
+
autoFixable: true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'formatting/trailing-comma',
|
|
96
|
+
name: 'Trailing Comma',
|
|
97
|
+
description: 'Use trailing commas in multiline',
|
|
98
|
+
category: 'formatting',
|
|
99
|
+
severity: 'info',
|
|
100
|
+
enabled: true,
|
|
101
|
+
autoFixable: true,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'formatting/semicolons',
|
|
105
|
+
name: 'Semicolons',
|
|
106
|
+
description: 'Use semicolons consistently',
|
|
107
|
+
category: 'formatting',
|
|
108
|
+
severity: 'warning',
|
|
109
|
+
enabled: true,
|
|
110
|
+
autoFixable: true,
|
|
111
|
+
},
|
|
112
|
+
// Structure rules
|
|
113
|
+
{
|
|
114
|
+
id: 'structure/max-file-lines',
|
|
115
|
+
name: 'Max File Lines',
|
|
116
|
+
description: 'Files should not exceed max lines',
|
|
117
|
+
category: 'structure',
|
|
118
|
+
severity: 'warning',
|
|
119
|
+
enabled: true,
|
|
120
|
+
options: { maxLines: 500 },
|
|
121
|
+
autoFixable: false,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'structure/max-function-lines',
|
|
125
|
+
name: 'Max Function Lines',
|
|
126
|
+
description: 'Functions should not exceed max lines',
|
|
127
|
+
category: 'structure',
|
|
128
|
+
severity: 'warning',
|
|
129
|
+
enabled: true,
|
|
130
|
+
options: { maxLines: 50 },
|
|
131
|
+
autoFixable: false,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'structure/max-parameters',
|
|
135
|
+
name: 'Max Parameters',
|
|
136
|
+
description: 'Functions should not have too many parameters',
|
|
137
|
+
category: 'structure',
|
|
138
|
+
severity: 'warning',
|
|
139
|
+
enabled: true,
|
|
140
|
+
options: { maxParams: 5 },
|
|
141
|
+
autoFixable: false,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'structure/single-responsibility',
|
|
145
|
+
name: 'Single Responsibility',
|
|
146
|
+
description: 'Functions/classes should have single responsibility',
|
|
147
|
+
category: 'structure',
|
|
148
|
+
severity: 'info',
|
|
149
|
+
enabled: true,
|
|
150
|
+
autoFixable: false,
|
|
151
|
+
},
|
|
152
|
+
// Documentation rules
|
|
153
|
+
{
|
|
154
|
+
id: 'documentation/jsdoc-required',
|
|
155
|
+
name: 'JSDoc Required',
|
|
156
|
+
description: 'Public APIs should have JSDoc comments',
|
|
157
|
+
category: 'documentation',
|
|
158
|
+
severity: 'warning',
|
|
159
|
+
enabled: true,
|
|
160
|
+
autoFixable: false,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'documentation/jsdoc-params',
|
|
164
|
+
name: 'JSDoc Parameters',
|
|
165
|
+
description: 'JSDoc should document all parameters',
|
|
166
|
+
category: 'documentation',
|
|
167
|
+
severity: 'warning',
|
|
168
|
+
enabled: true,
|
|
169
|
+
autoFixable: false,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: 'documentation/jsdoc-returns',
|
|
173
|
+
name: 'JSDoc Returns',
|
|
174
|
+
description: 'JSDoc should document return value',
|
|
175
|
+
category: 'documentation',
|
|
176
|
+
severity: 'info',
|
|
177
|
+
enabled: true,
|
|
178
|
+
autoFixable: false,
|
|
179
|
+
},
|
|
180
|
+
// Complexity rules
|
|
181
|
+
{
|
|
182
|
+
id: 'complexity/cyclomatic',
|
|
183
|
+
name: 'Cyclomatic Complexity',
|
|
184
|
+
description: 'Cyclomatic complexity should not exceed threshold',
|
|
185
|
+
category: 'complexity',
|
|
186
|
+
severity: 'warning',
|
|
187
|
+
enabled: true,
|
|
188
|
+
options: { maxComplexity: 10 },
|
|
189
|
+
autoFixable: false,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
id: 'complexity/max-nesting',
|
|
193
|
+
name: 'Max Nesting',
|
|
194
|
+
description: 'Nesting level should not exceed threshold',
|
|
195
|
+
category: 'complexity',
|
|
196
|
+
severity: 'warning',
|
|
197
|
+
enabled: true,
|
|
198
|
+
options: { maxNesting: 4 },
|
|
199
|
+
autoFixable: false,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'complexity/cognitive',
|
|
203
|
+
name: 'Cognitive Complexity',
|
|
204
|
+
description: 'Cognitive complexity should not exceed threshold',
|
|
205
|
+
category: 'complexity',
|
|
206
|
+
severity: 'warning',
|
|
207
|
+
enabled: true,
|
|
208
|
+
options: { maxCognitive: 15 },
|
|
209
|
+
autoFixable: false,
|
|
210
|
+
},
|
|
211
|
+
// Security rules
|
|
212
|
+
{
|
|
213
|
+
id: 'security/no-eval',
|
|
214
|
+
name: 'No Eval',
|
|
215
|
+
description: 'Avoid using eval()',
|
|
216
|
+
category: 'security',
|
|
217
|
+
severity: 'error',
|
|
218
|
+
enabled: true,
|
|
219
|
+
autoFixable: false,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'security/no-hardcoded-secrets',
|
|
223
|
+
name: 'No Hardcoded Secrets',
|
|
224
|
+
description: 'Avoid hardcoded secrets',
|
|
225
|
+
category: 'security',
|
|
226
|
+
severity: 'error',
|
|
227
|
+
enabled: true,
|
|
228
|
+
autoFixable: false,
|
|
229
|
+
},
|
|
230
|
+
];
|
|
231
|
+
/**
|
|
232
|
+
* Naming patterns
|
|
233
|
+
*/
|
|
234
|
+
const NAMING_PATTERNS = {
|
|
235
|
+
camelCase: /^[a-z][a-zA-Z0-9]*$/,
|
|
236
|
+
PascalCase: /^[A-Z][a-zA-Z0-9]*$/,
|
|
237
|
+
UPPER_CASE: /^[A-Z][A-Z0-9_]*$/,
|
|
238
|
+
kebabCase: /^[a-z][a-z0-9-]*$/,
|
|
239
|
+
snakeCase: /^[a-z][a-z0-9_]*$/,
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Short name patterns (likely not meaningful)
|
|
243
|
+
*/
|
|
244
|
+
const SHORT_NAME_EXCEPTIONS = new Set(['i', 'j', 'k', 'n', 'x', 'y', 'z', 'id', 'ok', 'fn', 'cb']);
|
|
245
|
+
/**
|
|
246
|
+
* Coding Standards Checker
|
|
247
|
+
*/
|
|
248
|
+
export class CodingStandardsChecker {
|
|
249
|
+
config;
|
|
250
|
+
rules = new Map();
|
|
251
|
+
constructor(config) {
|
|
252
|
+
this.config = { ...DEFAULT_STANDARDS_CONFIG, ...config };
|
|
253
|
+
this.initializeRules();
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Initialize rules
|
|
257
|
+
*/
|
|
258
|
+
initializeRules() {
|
|
259
|
+
// Add built-in rules
|
|
260
|
+
for (const rule of BUILTIN_RULES) {
|
|
261
|
+
if (this.config.enabledCategories.includes(rule.category)) {
|
|
262
|
+
this.rules.set(rule.id, { ...rule });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// Add custom rules
|
|
266
|
+
for (const rule of this.config.customRules) {
|
|
267
|
+
this.rules.set(rule.id, rule);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Check file content
|
|
272
|
+
*/
|
|
273
|
+
check(content, filePath) {
|
|
274
|
+
const startTime = Date.now();
|
|
275
|
+
const violations = [];
|
|
276
|
+
const lines = content.split('\n');
|
|
277
|
+
// Run each rule
|
|
278
|
+
for (const rule of this.rules.values()) {
|
|
279
|
+
if (!rule.enabled)
|
|
280
|
+
continue;
|
|
281
|
+
if (!this.shouldReport(rule.severity))
|
|
282
|
+
continue;
|
|
283
|
+
const ruleViolations = this.runRule(rule, content, lines, filePath);
|
|
284
|
+
violations.push(...ruleViolations);
|
|
285
|
+
}
|
|
286
|
+
// Sort violations by line number
|
|
287
|
+
violations.sort((a, b) => a.line - b.line);
|
|
288
|
+
// Count by severity
|
|
289
|
+
const errorCount = violations.filter((v) => v.severity === 'error').length;
|
|
290
|
+
const warningCount = violations.filter((v) => v.severity === 'warning').length;
|
|
291
|
+
const infoCount = violations.filter((v) => v.severity === 'info' || v.severity === 'hint').length;
|
|
292
|
+
// Determine pass/fail
|
|
293
|
+
const passed = errorCount === 0 && (!this.config.failOnWarning || warningCount === 0);
|
|
294
|
+
return {
|
|
295
|
+
file: filePath,
|
|
296
|
+
violations,
|
|
297
|
+
errorCount,
|
|
298
|
+
warningCount,
|
|
299
|
+
infoCount,
|
|
300
|
+
passed,
|
|
301
|
+
duration: Date.now() - startTime,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Check multiple files
|
|
306
|
+
*/
|
|
307
|
+
checkFiles(files) {
|
|
308
|
+
const startTime = Date.now();
|
|
309
|
+
const results = [];
|
|
310
|
+
for (const file of files) {
|
|
311
|
+
// Check ignore patterns
|
|
312
|
+
if (this.shouldIgnore(file.path))
|
|
313
|
+
continue;
|
|
314
|
+
const result = this.check(file.content, file.path);
|
|
315
|
+
results.push(result);
|
|
316
|
+
}
|
|
317
|
+
// Aggregate results
|
|
318
|
+
const bySeverity = {
|
|
319
|
+
error: 0,
|
|
320
|
+
warning: 0,
|
|
321
|
+
info: 0,
|
|
322
|
+
hint: 0,
|
|
323
|
+
};
|
|
324
|
+
const byCategory = {
|
|
325
|
+
naming: 0,
|
|
326
|
+
formatting: 0,
|
|
327
|
+
structure: 0,
|
|
328
|
+
documentation: 0,
|
|
329
|
+
complexity: 0,
|
|
330
|
+
security: 0,
|
|
331
|
+
performance: 0,
|
|
332
|
+
testing: 0,
|
|
333
|
+
style: 0,
|
|
334
|
+
};
|
|
335
|
+
let totalViolations = 0;
|
|
336
|
+
for (const result of results) {
|
|
337
|
+
for (const violation of result.violations) {
|
|
338
|
+
totalViolations++;
|
|
339
|
+
bySeverity[violation.severity]++;
|
|
340
|
+
const rule = this.rules.get(violation.ruleId);
|
|
341
|
+
if (rule) {
|
|
342
|
+
byCategory[rule.category]++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const filesPassed = results.filter((r) => r.passed).length;
|
|
347
|
+
const filesFailed = results.length - filesPassed;
|
|
348
|
+
return {
|
|
349
|
+
timestamp: new Date(),
|
|
350
|
+
filesChecked: results.length,
|
|
351
|
+
filesPassed,
|
|
352
|
+
filesFailed,
|
|
353
|
+
totalViolations,
|
|
354
|
+
bySeverity,
|
|
355
|
+
byCategory,
|
|
356
|
+
results,
|
|
357
|
+
duration: Date.now() - startTime,
|
|
358
|
+
passRate: results.length > 0 ? filesPassed / results.length : 1,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Run a specific rule
|
|
363
|
+
*/
|
|
364
|
+
runRule(rule, content, lines, filePath) {
|
|
365
|
+
const violations = [];
|
|
366
|
+
switch (rule.id) {
|
|
367
|
+
case 'naming/camel-case':
|
|
368
|
+
violations.push(...this.checkCamelCase(rule, lines, filePath));
|
|
369
|
+
break;
|
|
370
|
+
case 'naming/pascal-case-class':
|
|
371
|
+
violations.push(...this.checkPascalCaseClass(rule, lines, filePath));
|
|
372
|
+
break;
|
|
373
|
+
case 'naming/upper-case-constants':
|
|
374
|
+
violations.push(...this.checkUpperCaseConstants(rule, lines, filePath));
|
|
375
|
+
break;
|
|
376
|
+
case 'naming/meaningful-names':
|
|
377
|
+
violations.push(...this.checkMeaningfulNames(rule, lines, filePath));
|
|
378
|
+
break;
|
|
379
|
+
case 'formatting/max-line-length':
|
|
380
|
+
violations.push(...this.checkMaxLineLength(rule, lines, filePath));
|
|
381
|
+
break;
|
|
382
|
+
case 'formatting/consistent-indentation':
|
|
383
|
+
violations.push(...this.checkIndentation(rule, lines, filePath));
|
|
384
|
+
break;
|
|
385
|
+
case 'structure/max-file-lines':
|
|
386
|
+
violations.push(...this.checkMaxFileLines(rule, lines, filePath));
|
|
387
|
+
break;
|
|
388
|
+
case 'structure/max-function-lines':
|
|
389
|
+
violations.push(...this.checkMaxFunctionLines(rule, lines, filePath));
|
|
390
|
+
break;
|
|
391
|
+
case 'structure/max-parameters':
|
|
392
|
+
violations.push(...this.checkMaxParameters(rule, lines, filePath));
|
|
393
|
+
break;
|
|
394
|
+
case 'documentation/jsdoc-required':
|
|
395
|
+
violations.push(...this.checkJSDocRequired(rule, lines, filePath));
|
|
396
|
+
break;
|
|
397
|
+
case 'complexity/cyclomatic':
|
|
398
|
+
violations.push(...this.checkCyclomaticComplexity(rule, content, filePath));
|
|
399
|
+
break;
|
|
400
|
+
case 'complexity/max-nesting':
|
|
401
|
+
violations.push(...this.checkMaxNesting(rule, lines, filePath));
|
|
402
|
+
break;
|
|
403
|
+
case 'security/no-eval':
|
|
404
|
+
violations.push(...this.checkNoEval(rule, lines, filePath));
|
|
405
|
+
break;
|
|
406
|
+
case 'security/no-hardcoded-secrets':
|
|
407
|
+
violations.push(...this.checkNoHardcodedSecrets(rule, lines, filePath));
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
return violations;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Check camelCase naming
|
|
414
|
+
*/
|
|
415
|
+
checkCamelCase(rule, lines, filePath) {
|
|
416
|
+
const violations = [];
|
|
417
|
+
const varPattern = /(?:let|const|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=:]/g;
|
|
418
|
+
for (let i = 0; i < lines.length; i++) {
|
|
419
|
+
const line = lines[i];
|
|
420
|
+
let match;
|
|
421
|
+
while ((match = varPattern.exec(line)) !== null) {
|
|
422
|
+
const name = match[1];
|
|
423
|
+
// Skip UPPER_CASE (constants) and PascalCase (classes/types)
|
|
424
|
+
if (NAMING_PATTERNS.UPPER_CASE.test(name) || NAMING_PATTERNS.PascalCase.test(name)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (!NAMING_PATTERNS.camelCase.test(name) && !name.startsWith('_')) {
|
|
428
|
+
violations.push({
|
|
429
|
+
ruleId: rule.id,
|
|
430
|
+
ruleName: rule.name,
|
|
431
|
+
severity: rule.severity,
|
|
432
|
+
message: `Variable '${name}' should use camelCase`,
|
|
433
|
+
file: filePath,
|
|
434
|
+
line: i + 1,
|
|
435
|
+
column: match.index + 1,
|
|
436
|
+
autoFixable: false,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return violations;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Check PascalCase for classes
|
|
445
|
+
*/
|
|
446
|
+
checkPascalCaseClass(rule, lines, filePath) {
|
|
447
|
+
const violations = [];
|
|
448
|
+
const classPattern = /class\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
449
|
+
for (let i = 0; i < lines.length; i++) {
|
|
450
|
+
const line = lines[i];
|
|
451
|
+
let match;
|
|
452
|
+
while ((match = classPattern.exec(line)) !== null) {
|
|
453
|
+
const name = match[1];
|
|
454
|
+
if (!NAMING_PATTERNS.PascalCase.test(name)) {
|
|
455
|
+
violations.push({
|
|
456
|
+
ruleId: rule.id,
|
|
457
|
+
ruleName: rule.name,
|
|
458
|
+
severity: rule.severity,
|
|
459
|
+
message: `Class '${name}' should use PascalCase`,
|
|
460
|
+
file: filePath,
|
|
461
|
+
line: i + 1,
|
|
462
|
+
column: match.index + 1,
|
|
463
|
+
autoFixable: false,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return violations;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Check UPPER_CASE for constants
|
|
472
|
+
*/
|
|
473
|
+
checkUpperCaseConstants(rule, lines, filePath) {
|
|
474
|
+
const violations = [];
|
|
475
|
+
const constPattern = /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=/g;
|
|
476
|
+
for (let i = 0; i < lines.length; i++) {
|
|
477
|
+
const line = lines[i];
|
|
478
|
+
// Skip if inside a function/block (module-level constants only)
|
|
479
|
+
if (line.match(/^\s{2,}/))
|
|
480
|
+
continue;
|
|
481
|
+
let match;
|
|
482
|
+
while ((match = constPattern.exec(line)) !== null) {
|
|
483
|
+
const name = match[1];
|
|
484
|
+
// Check if it's a primitive constant (not object/array/function)
|
|
485
|
+
const valueStart = line.indexOf('=', match.index) + 1;
|
|
486
|
+
const valuePart = line.slice(valueStart).trim();
|
|
487
|
+
// Skip if it's an object, array, or function
|
|
488
|
+
if (valuePart.startsWith('{') || valuePart.startsWith('[') ||
|
|
489
|
+
valuePart.startsWith('function') || valuePart.includes('=>')) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
// Only flag if it looks like a constant (primitive value)
|
|
493
|
+
if (valuePart.match(/^['"`]|^\d|^true|^false|^null/)) {
|
|
494
|
+
if (!NAMING_PATTERNS.UPPER_CASE.test(name) && !NAMING_PATTERNS.PascalCase.test(name)) {
|
|
495
|
+
violations.push({
|
|
496
|
+
ruleId: rule.id,
|
|
497
|
+
ruleName: rule.name,
|
|
498
|
+
severity: rule.severity,
|
|
499
|
+
message: `Module-level constant '${name}' should use UPPER_CASE`,
|
|
500
|
+
file: filePath,
|
|
501
|
+
line: i + 1,
|
|
502
|
+
column: match.index + 1,
|
|
503
|
+
suggestion: name.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
|
|
504
|
+
autoFixable: false,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return violations;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Check meaningful names
|
|
514
|
+
*/
|
|
515
|
+
checkMeaningfulNames(rule, lines, filePath) {
|
|
516
|
+
const violations = [];
|
|
517
|
+
const namePattern = /(?:let|const|var|function)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
518
|
+
for (let i = 0; i < lines.length; i++) {
|
|
519
|
+
const line = lines[i];
|
|
520
|
+
let match;
|
|
521
|
+
while ((match = namePattern.exec(line)) !== null) {
|
|
522
|
+
const name = match[1];
|
|
523
|
+
// Skip if it's a known exception
|
|
524
|
+
if (SHORT_NAME_EXCEPTIONS.has(name.toLowerCase()))
|
|
525
|
+
continue;
|
|
526
|
+
// Flag very short names (1-2 chars)
|
|
527
|
+
if (name.length <= 2) {
|
|
528
|
+
violations.push({
|
|
529
|
+
ruleId: rule.id,
|
|
530
|
+
ruleName: rule.name,
|
|
531
|
+
severity: rule.severity,
|
|
532
|
+
message: `Name '${name}' is too short, use a more descriptive name`,
|
|
533
|
+
file: filePath,
|
|
534
|
+
line: i + 1,
|
|
535
|
+
column: match.index + 1,
|
|
536
|
+
autoFixable: false,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return violations;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Check max line length
|
|
545
|
+
*/
|
|
546
|
+
checkMaxLineLength(rule, lines, filePath) {
|
|
547
|
+
const violations = [];
|
|
548
|
+
const maxLength = rule.options?.maxLength ?? 100;
|
|
549
|
+
for (let i = 0; i < lines.length; i++) {
|
|
550
|
+
const line = lines[i];
|
|
551
|
+
if (line.length > maxLength) {
|
|
552
|
+
violations.push({
|
|
553
|
+
ruleId: rule.id,
|
|
554
|
+
ruleName: rule.name,
|
|
555
|
+
severity: rule.severity,
|
|
556
|
+
message: `Line exceeds ${maxLength} characters (${line.length})`,
|
|
557
|
+
file: filePath,
|
|
558
|
+
line: i + 1,
|
|
559
|
+
column: maxLength + 1,
|
|
560
|
+
autoFixable: false,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return violations;
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Check indentation
|
|
568
|
+
*/
|
|
569
|
+
checkIndentation(rule, lines, filePath) {
|
|
570
|
+
const violations = [];
|
|
571
|
+
const size = rule.options?.size ?? 2;
|
|
572
|
+
for (let i = 0; i < lines.length; i++) {
|
|
573
|
+
const line = lines[i];
|
|
574
|
+
if (!line.trim())
|
|
575
|
+
continue; // Skip empty lines
|
|
576
|
+
const leadingSpaces = line.match(/^(\s*)/)?.[1] ?? '';
|
|
577
|
+
// Check for tabs
|
|
578
|
+
if (leadingSpaces.includes('\t')) {
|
|
579
|
+
violations.push({
|
|
580
|
+
ruleId: rule.id,
|
|
581
|
+
ruleName: rule.name,
|
|
582
|
+
severity: rule.severity,
|
|
583
|
+
message: 'Use spaces instead of tabs for indentation',
|
|
584
|
+
file: filePath,
|
|
585
|
+
line: i + 1,
|
|
586
|
+
column: 1,
|
|
587
|
+
autoFixable: true,
|
|
588
|
+
});
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
// Check indentation is multiple of size
|
|
592
|
+
if (leadingSpaces.length % size !== 0) {
|
|
593
|
+
violations.push({
|
|
594
|
+
ruleId: rule.id,
|
|
595
|
+
ruleName: rule.name,
|
|
596
|
+
severity: rule.severity,
|
|
597
|
+
message: `Indentation should be a multiple of ${size} spaces`,
|
|
598
|
+
file: filePath,
|
|
599
|
+
line: i + 1,
|
|
600
|
+
column: 1,
|
|
601
|
+
autoFixable: true,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return violations;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Check max file lines
|
|
609
|
+
*/
|
|
610
|
+
checkMaxFileLines(rule, lines, filePath) {
|
|
611
|
+
const violations = [];
|
|
612
|
+
const maxLines = rule.options?.maxLines ?? 500;
|
|
613
|
+
if (lines.length > maxLines) {
|
|
614
|
+
violations.push({
|
|
615
|
+
ruleId: rule.id,
|
|
616
|
+
ruleName: rule.name,
|
|
617
|
+
severity: rule.severity,
|
|
618
|
+
message: `File has ${lines.length} lines, exceeds maximum of ${maxLines}`,
|
|
619
|
+
file: filePath,
|
|
620
|
+
line: 1,
|
|
621
|
+
column: 1,
|
|
622
|
+
suggestion: 'Consider splitting into multiple files',
|
|
623
|
+
autoFixable: false,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
return violations;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Check max function lines
|
|
630
|
+
*/
|
|
631
|
+
checkMaxFunctionLines(rule, lines, filePath) {
|
|
632
|
+
const violations = [];
|
|
633
|
+
const maxLines = rule.options?.maxLines ?? 50;
|
|
634
|
+
const funcPattern = /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function|\())/;
|
|
635
|
+
let funcStart = -1;
|
|
636
|
+
let funcName = '';
|
|
637
|
+
let braceCount = 0;
|
|
638
|
+
let inFunction = false;
|
|
639
|
+
for (let i = 0; i < lines.length; i++) {
|
|
640
|
+
const line = lines[i];
|
|
641
|
+
if (!inFunction) {
|
|
642
|
+
const match = line.match(funcPattern);
|
|
643
|
+
if (match) {
|
|
644
|
+
funcName = match[1] || match[2];
|
|
645
|
+
funcStart = i;
|
|
646
|
+
inFunction = true;
|
|
647
|
+
braceCount = 0;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (inFunction) {
|
|
651
|
+
braceCount += (line.match(/{/g) || []).length;
|
|
652
|
+
braceCount -= (line.match(/}/g) || []).length;
|
|
653
|
+
if (braceCount === 0 && funcStart >= 0) {
|
|
654
|
+
const funcLines = i - funcStart + 1;
|
|
655
|
+
if (funcLines > maxLines) {
|
|
656
|
+
violations.push({
|
|
657
|
+
ruleId: rule.id,
|
|
658
|
+
ruleName: rule.name,
|
|
659
|
+
severity: rule.severity,
|
|
660
|
+
message: `Function '${funcName}' has ${funcLines} lines, exceeds maximum of ${maxLines}`,
|
|
661
|
+
file: filePath,
|
|
662
|
+
line: funcStart + 1,
|
|
663
|
+
column: 1,
|
|
664
|
+
suggestion: 'Consider breaking into smaller functions',
|
|
665
|
+
autoFixable: false,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
inFunction = false;
|
|
669
|
+
funcStart = -1;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return violations;
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Check max parameters
|
|
677
|
+
*/
|
|
678
|
+
checkMaxParameters(rule, lines, filePath) {
|
|
679
|
+
const violations = [];
|
|
680
|
+
const maxParams = rule.options?.maxParams ?? 5;
|
|
681
|
+
const funcPattern = /(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?function)\s*\(([^)]*)\)/g;
|
|
682
|
+
for (let i = 0; i < lines.length; i++) {
|
|
683
|
+
const line = lines[i];
|
|
684
|
+
let match;
|
|
685
|
+
while ((match = funcPattern.exec(line)) !== null) {
|
|
686
|
+
const funcName = match[1] || match[2];
|
|
687
|
+
const params = match[3].split(',').filter((p) => p.trim());
|
|
688
|
+
if (params.length > maxParams) {
|
|
689
|
+
violations.push({
|
|
690
|
+
ruleId: rule.id,
|
|
691
|
+
ruleName: rule.name,
|
|
692
|
+
severity: rule.severity,
|
|
693
|
+
message: `Function '${funcName}' has ${params.length} parameters, exceeds maximum of ${maxParams}`,
|
|
694
|
+
file: filePath,
|
|
695
|
+
line: i + 1,
|
|
696
|
+
column: match.index + 1,
|
|
697
|
+
suggestion: 'Consider using an options object',
|
|
698
|
+
autoFixable: false,
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return violations;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Check JSDoc required
|
|
707
|
+
*/
|
|
708
|
+
checkJSDocRequired(rule, lines, filePath) {
|
|
709
|
+
const violations = [];
|
|
710
|
+
const exportPattern = /^export\s+(?:async\s+)?(?:function|class|const|interface|type)/;
|
|
711
|
+
for (let i = 0; i < lines.length; i++) {
|
|
712
|
+
const line = lines[i];
|
|
713
|
+
if (exportPattern.test(line.trim())) {
|
|
714
|
+
// Check if previous lines have JSDoc
|
|
715
|
+
let hasJSDoc = false;
|
|
716
|
+
for (let j = i - 1; j >= 0 && j >= i - 10; j--) {
|
|
717
|
+
const prevLine = lines[j].trim();
|
|
718
|
+
if (prevLine === '*/') {
|
|
719
|
+
hasJSDoc = true;
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
if (prevLine && !prevLine.startsWith('*') && !prevLine.startsWith('//')) {
|
|
723
|
+
break;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (!hasJSDoc) {
|
|
727
|
+
violations.push({
|
|
728
|
+
ruleId: rule.id,
|
|
729
|
+
ruleName: rule.name,
|
|
730
|
+
severity: rule.severity,
|
|
731
|
+
message: 'Exported member should have JSDoc documentation',
|
|
732
|
+
file: filePath,
|
|
733
|
+
line: i + 1,
|
|
734
|
+
column: 1,
|
|
735
|
+
autoFixable: false,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return violations;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Check cyclomatic complexity
|
|
744
|
+
*/
|
|
745
|
+
checkCyclomaticComplexity(rule, content, filePath) {
|
|
746
|
+
const violations = [];
|
|
747
|
+
const maxComplexity = rule.options?.maxComplexity ?? 10;
|
|
748
|
+
// Count complexity indicators
|
|
749
|
+
const complexityPatterns = [
|
|
750
|
+
/\bif\b/g,
|
|
751
|
+
/\belse\s+if\b/g,
|
|
752
|
+
/\bfor\b/g,
|
|
753
|
+
/\bwhile\b/g,
|
|
754
|
+
/\bcase\b/g,
|
|
755
|
+
/\bcatch\b/g,
|
|
756
|
+
/\b\?\s*[^:]/g, // Ternary
|
|
757
|
+
/&&/g,
|
|
758
|
+
/\|\|/g,
|
|
759
|
+
];
|
|
760
|
+
let complexity = 1; // Base complexity
|
|
761
|
+
for (const pattern of complexityPatterns) {
|
|
762
|
+
const matches = content.match(pattern);
|
|
763
|
+
if (matches) {
|
|
764
|
+
complexity += matches.length;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
if (complexity > maxComplexity) {
|
|
768
|
+
violations.push({
|
|
769
|
+
ruleId: rule.id,
|
|
770
|
+
ruleName: rule.name,
|
|
771
|
+
severity: rule.severity,
|
|
772
|
+
message: `File cyclomatic complexity is ${complexity}, exceeds maximum of ${maxComplexity}`,
|
|
773
|
+
file: filePath,
|
|
774
|
+
line: 1,
|
|
775
|
+
column: 1,
|
|
776
|
+
suggestion: 'Consider refactoring to reduce complexity',
|
|
777
|
+
autoFixable: false,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
return violations;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Check max nesting level
|
|
784
|
+
*/
|
|
785
|
+
checkMaxNesting(rule, lines, filePath) {
|
|
786
|
+
const violations = [];
|
|
787
|
+
const maxNesting = rule.options?.maxNesting ?? 4;
|
|
788
|
+
let currentNesting = 0;
|
|
789
|
+
let maxFound = 0;
|
|
790
|
+
let maxFoundLine = 0;
|
|
791
|
+
for (let i = 0; i < lines.length; i++) {
|
|
792
|
+
const line = lines[i];
|
|
793
|
+
// Count braces
|
|
794
|
+
const opens = (line.match(/{/g) || []).length;
|
|
795
|
+
const closes = (line.match(/}/g) || []).length;
|
|
796
|
+
currentNesting += opens;
|
|
797
|
+
if (currentNesting > maxFound) {
|
|
798
|
+
maxFound = currentNesting;
|
|
799
|
+
maxFoundLine = i + 1;
|
|
800
|
+
}
|
|
801
|
+
currentNesting -= closes;
|
|
802
|
+
}
|
|
803
|
+
if (maxFound > maxNesting) {
|
|
804
|
+
violations.push({
|
|
805
|
+
ruleId: rule.id,
|
|
806
|
+
ruleName: rule.name,
|
|
807
|
+
severity: rule.severity,
|
|
808
|
+
message: `Nesting level is ${maxFound}, exceeds maximum of ${maxNesting}`,
|
|
809
|
+
file: filePath,
|
|
810
|
+
line: maxFoundLine,
|
|
811
|
+
column: 1,
|
|
812
|
+
suggestion: 'Consider extracting nested code to separate functions',
|
|
813
|
+
autoFixable: false,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
return violations;
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Check for eval usage
|
|
820
|
+
*/
|
|
821
|
+
checkNoEval(rule, lines, filePath) {
|
|
822
|
+
const violations = [];
|
|
823
|
+
const evalPattern = /\beval\s*\(/g;
|
|
824
|
+
for (let i = 0; i < lines.length; i++) {
|
|
825
|
+
const line = lines[i];
|
|
826
|
+
let match;
|
|
827
|
+
while ((match = evalPattern.exec(line)) !== null) {
|
|
828
|
+
violations.push({
|
|
829
|
+
ruleId: rule.id,
|
|
830
|
+
ruleName: rule.name,
|
|
831
|
+
severity: rule.severity,
|
|
832
|
+
message: 'Avoid using eval() - it poses security risks',
|
|
833
|
+
file: filePath,
|
|
834
|
+
line: i + 1,
|
|
835
|
+
column: match.index + 1,
|
|
836
|
+
autoFixable: false,
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return violations;
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Check for hardcoded secrets
|
|
844
|
+
*/
|
|
845
|
+
checkNoHardcodedSecrets(rule, lines, filePath) {
|
|
846
|
+
const violations = [];
|
|
847
|
+
const secretPatterns = [
|
|
848
|
+
/(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
849
|
+
/(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
850
|
+
/(?:secret|token)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
|
|
851
|
+
/(?:private[_-]?key)\s*[:=]\s*['"][^'"]+['"]/gi,
|
|
852
|
+
];
|
|
853
|
+
for (let i = 0; i < lines.length; i++) {
|
|
854
|
+
const line = lines[i];
|
|
855
|
+
// Skip comments
|
|
856
|
+
if (line.trim().startsWith('//') || line.trim().startsWith('*'))
|
|
857
|
+
continue;
|
|
858
|
+
for (const pattern of secretPatterns) {
|
|
859
|
+
const match = line.match(pattern);
|
|
860
|
+
if (match) {
|
|
861
|
+
violations.push({
|
|
862
|
+
ruleId: rule.id,
|
|
863
|
+
ruleName: rule.name,
|
|
864
|
+
severity: rule.severity,
|
|
865
|
+
message: 'Potential hardcoded secret detected',
|
|
866
|
+
file: filePath,
|
|
867
|
+
line: i + 1,
|
|
868
|
+
column: line.indexOf(match[0]) + 1,
|
|
869
|
+
suggestion: 'Use environment variables or secure vault for secrets',
|
|
870
|
+
autoFixable: false,
|
|
871
|
+
});
|
|
872
|
+
break; // One violation per line
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return violations;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Check if severity should be reported
|
|
880
|
+
*/
|
|
881
|
+
shouldReport(severity) {
|
|
882
|
+
const levels = ['error', 'warning', 'info', 'hint'];
|
|
883
|
+
const minLevel = levels.indexOf(this.config.minSeverity);
|
|
884
|
+
const currentLevel = levels.indexOf(severity);
|
|
885
|
+
return currentLevel <= minLevel;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Check if file should be ignored
|
|
889
|
+
*/
|
|
890
|
+
shouldIgnore(filePath) {
|
|
891
|
+
for (const pattern of this.config.ignorePatterns) {
|
|
892
|
+
const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
|
|
893
|
+
if (regex.test(filePath)) {
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Get enabled rules
|
|
901
|
+
*/
|
|
902
|
+
getEnabledRules() {
|
|
903
|
+
return Array.from(this.rules.values()).filter((r) => r.enabled);
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Enable/disable rule
|
|
907
|
+
*/
|
|
908
|
+
setRuleEnabled(ruleId, enabled) {
|
|
909
|
+
const rule = this.rules.get(ruleId);
|
|
910
|
+
if (rule) {
|
|
911
|
+
rule.enabled = enabled;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Add custom rule
|
|
916
|
+
*/
|
|
917
|
+
addRule(rule) {
|
|
918
|
+
this.rules.set(rule.id, rule);
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Format report as string
|
|
922
|
+
*/
|
|
923
|
+
formatReport(report) {
|
|
924
|
+
const lines = [];
|
|
925
|
+
lines.push('# Coding Standards Report');
|
|
926
|
+
lines.push('');
|
|
927
|
+
lines.push(`**Generated:** ${report.timestamp.toISOString()}`);
|
|
928
|
+
lines.push(`**Duration:** ${report.duration}ms`);
|
|
929
|
+
lines.push('');
|
|
930
|
+
// Summary
|
|
931
|
+
lines.push('## Summary');
|
|
932
|
+
lines.push('');
|
|
933
|
+
lines.push(`| Metric | Value |`);
|
|
934
|
+
lines.push(`|--------|-------|`);
|
|
935
|
+
lines.push(`| Files Checked | ${report.filesChecked} |`);
|
|
936
|
+
lines.push(`| Files Passed | ${report.filesPassed} |`);
|
|
937
|
+
lines.push(`| Files Failed | ${report.filesFailed} |`);
|
|
938
|
+
lines.push(`| Pass Rate | ${(report.passRate * 100).toFixed(1)}% |`);
|
|
939
|
+
lines.push(`| Total Violations | ${report.totalViolations} |`);
|
|
940
|
+
lines.push('');
|
|
941
|
+
// By severity
|
|
942
|
+
lines.push('### By Severity');
|
|
943
|
+
lines.push('');
|
|
944
|
+
lines.push(`- 🔴 Errors: ${report.bySeverity.error}`);
|
|
945
|
+
lines.push(`- 🟡 Warnings: ${report.bySeverity.warning}`);
|
|
946
|
+
lines.push(`- 🔵 Info: ${report.bySeverity.info}`);
|
|
947
|
+
lines.push('');
|
|
948
|
+
// Violations
|
|
949
|
+
if (report.totalViolations > 0) {
|
|
950
|
+
lines.push('## Violations');
|
|
951
|
+
lines.push('');
|
|
952
|
+
for (const result of report.results) {
|
|
953
|
+
if (result.violations.length === 0)
|
|
954
|
+
continue;
|
|
955
|
+
lines.push(`### ${result.file}`);
|
|
956
|
+
lines.push('');
|
|
957
|
+
for (const v of result.violations) {
|
|
958
|
+
const icon = v.severity === 'error' ? '🔴' : v.severity === 'warning' ? '🟡' : '🔵';
|
|
959
|
+
lines.push(`${icon} Line ${v.line}: ${v.message}`);
|
|
960
|
+
if (v.suggestion) {
|
|
961
|
+
lines.push(` 💡 ${v.suggestion}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
lines.push('');
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return lines.join('\n');
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Create coding standards checker instance
|
|
972
|
+
*/
|
|
973
|
+
export function createCodingStandardsChecker(config) {
|
|
974
|
+
return new CodingStandardsChecker(config);
|
|
975
|
+
}
|
|
976
|
+
//# sourceMappingURL=coding-standards.js.map
|