@sun-asterisk/sunlint 1.3.0 → 1.3.2

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.
Files changed (124) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/CONTRIBUTING.md +249 -605
  3. package/README.md +3 -4
  4. package/config/ci-cd.json +54 -0
  5. package/config/development.json +56 -0
  6. package/config/large-project.json +143 -0
  7. package/config/presets/all.json +0 -1
  8. package/config/release.json +70 -0
  9. package/config/rule-analysis-strategies.js +38 -3
  10. package/config/rules/enhanced-rules-registry.json +474 -1179
  11. package/config/rules/rules-registry-generated.json +3 -3
  12. package/core/cli-action-handler.js +24 -30
  13. package/core/cli-program.js +11 -3
  14. package/core/config-merger.js +29 -2
  15. package/core/enhanced-rules-registry.js +3 -2
  16. package/core/semantic-engine.js +129 -19
  17. package/core/semantic-rule-base.js +4 -2
  18. package/core/unified-rule-registry.js +1 -1
  19. package/docs/COMMAND-EXAMPLES.md +134 -0
  20. package/docs/LARGE-PROJECT-GUIDE.md +324 -0
  21. package/engines/heuristic-engine.js +135 -16
  22. package/integrations/eslint/plugin/index.js +0 -2
  23. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  24. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  25. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  26. package/origin-rules/common-en.md +19 -15
  27. package/package.json +1 -1
  28. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  29. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  30. package/rules/common/C006_function_naming/analyzer.js +29 -3
  31. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  32. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  33. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  34. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  35. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  36. package/rules/common/C013_no_dead_code/config.json +61 -0
  37. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  38. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  39. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  40. package/rules/common/C014_dependency_injection/config.json +26 -0
  41. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  42. package/rules/common/C017_constructor_logic/analyzer.js +254 -17
  43. package/rules/common/C017_constructor_logic/semantic-analyzer.js +340 -0
  44. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  45. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  46. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  47. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  48. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  49. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  50. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  51. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  52. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  53. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  54. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  55. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  56. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  57. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  58. package/rules/common/C033_separate_service_repository/README.md +78 -0
  59. package/rules/common/C033_separate_service_repository/analyzer.js +160 -0
  60. package/rules/common/C033_separate_service_repository/config.json +50 -0
  61. package/rules/common/C033_separate_service_repository/regex-based-analyzer.js +585 -0
  62. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +368 -0
  63. package/rules/common/C035_error_logging_context/STRATEGY.md +99 -0
  64. package/rules/common/C035_error_logging_context/analyzer.js +232 -0
  65. package/rules/common/C035_error_logging_context/config.json +54 -0
  66. package/rules/common/C035_error_logging_context/regex-based-analyzer.js +299 -0
  67. package/rules/common/C035_error_logging_context/symbol-based-analyzer.js +454 -0
  68. package/rules/common/C040_centralized_validation/analyzer.js +165 -0
  69. package/rules/common/C040_centralized_validation/config.json +46 -0
  70. package/rules/common/C040_centralized_validation/regex-based-analyzer.js +243 -0
  71. package/rules/common/C040_centralized_validation/symbol-based-analyzer.js +416 -0
  72. package/rules/common/{C076_single_test_behavior → C072_single_test_behavior}/analyzer.js +6 -6
  73. package/rules/common/C076_explicit_function_types/README.md +30 -0
  74. package/rules/common/C076_explicit_function_types/analyzer.js +172 -0
  75. package/rules/common/C076_explicit_function_types/config.json +15 -0
  76. package/rules/common/C076_explicit_function_types/semantic-analyzer.js +341 -0
  77. package/rules/index.js +6 -1
  78. package/rules/parser/rule-parser.js +13 -2
  79. package/rules/security/S005_no_origin_auth/README.md +226 -0
  80. package/rules/security/S005_no_origin_auth/analyzer.js +184 -0
  81. package/rules/security/S005_no_origin_auth/ast-analyzer.js +406 -0
  82. package/rules/security/S005_no_origin_auth/config.json +85 -0
  83. package/rules/security/S006_no_plaintext_recovery_codes/README.md +139 -0
  84. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +306 -0
  85. package/rules/security/S006_no_plaintext_recovery_codes/config.json +48 -0
  86. package/rules/security/S007_no_plaintext_otp/README.md +198 -0
  87. package/rules/security/S007_no_plaintext_otp/analyzer.js +406 -0
  88. package/rules/security/S007_no_plaintext_otp/config.json +79 -0
  89. package/rules/security/S007_no_plaintext_otp/semantic-analyzer.js +609 -0
  90. package/rules/security/S007_no_plaintext_otp/semantic-config.json +195 -0
  91. package/rules/security/S007_no_plaintext_otp/semantic-wrapper.js +280 -0
  92. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  93. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  94. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  95. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  96. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  97. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  98. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  99. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  100. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  101. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  102. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  103. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +180 -366
  104. package/rules/security/S027_no_hardcoded_secrets/categories.json +153 -0
  105. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +250 -0
  106. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  107. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  108. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  109. package/rules/security/S055_content_type_validation/README.md +176 -0
  110. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  111. package/rules/security/S055_content_type_validation/config.json +48 -0
  112. package/rules/utils/rule-helpers.js +140 -1
  113. package/scripts/consolidate-config.js +116 -0
  114. package/scripts/prepare-release.sh +1 -1
  115. package/config/rules/rules-registry.json +0 -765
  116. package/docs/ESLINT-INTEGRATION-STRATEGY.md +0 -392
  117. package/docs/FUTURE_PACKAGES.md +0 -83
  118. package/docs/HEURISTIC_VS_AI.md +0 -113
  119. package/docs/PRODUCTION_DEPLOYMENT_ANALYSIS.md +0 -112
  120. package/docs/PRODUCTION_SIZE_IMPACT.md +0 -183
  121. package/docs/RELEASE_GUIDE.md +0 -230
  122. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +0 -156
  123. package/integrations/eslint/plugin/rules/common/c076-single-behavior-per-test.js +0 -254
  124. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -0,0 +1,340 @@
1
+ /**
2
+ * C017 Constructor Logic - Semantic Analyzer (Phase 2)
3
+ * Uses ts-morph for precise symbol-based analysis
4
+ * Detects complex logic in constructors with high accuracy
5
+ */
6
+
7
+ const SemanticRuleBase = require('../../../core/semantic-rule-base');
8
+ const path = require('path');
9
+
10
+ class C017SemanticAnalyzer extends SemanticRuleBase {
11
+ constructor(ruleId = 'C017') {
12
+ super(ruleId);
13
+ this.ruleName = 'C017 - No Complex Logic in Constructors (Semantic)';
14
+ this.description = 'Constructors should only handle dependency injection and simple initialization';
15
+ }
16
+
17
+ /**
18
+ * Analyze a single file using ts-morph semantic analysis
19
+ * @param {string} filePath - Path to the file to analyze
20
+ * @param {Object} options - Analysis options
21
+ */
22
+ async analyzeFile(filePath, options = {}) {
23
+ if (!this.semanticEngine) {
24
+ throw new Error('Semantic engine not initialized');
25
+ }
26
+
27
+ try {
28
+ // Get file's absolute path
29
+ const absolutePath = path.resolve(filePath);
30
+
31
+ // Get source file from semantic engine's project
32
+ const sourceFile = this.semanticEngine.project.getSourceFile(absolutePath);
33
+ if (!sourceFile) {
34
+ if (options.verbose) {
35
+ console.log(`⚠️ [C017-Semantic] Could not load source file: ${filePath}`);
36
+ }
37
+ return;
38
+ }
39
+
40
+ // Find all class declarations
41
+ const classes = sourceFile.getClasses();
42
+
43
+ for (const classDecl of classes) {
44
+ const constructors = classDecl.getConstructors();
45
+
46
+ for (const constructor of constructors) {
47
+ await this.analyzeConstructor(constructor, filePath, options);
48
+ }
49
+ }
50
+
51
+ } catch (error) {
52
+ if (options.verbose) {
53
+ console.warn(`⚠️ [C017-Semantic] Error analyzing ${filePath}:`, error.message);
54
+ }
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Analyze a constructor node for complex logic
60
+ * @param {ts.ConstructorDeclaration} constructor - Constructor node
61
+ * @param {string} filePath - File path
62
+ * @param {Object} options - Analysis options
63
+ */
64
+ async analyzeConstructor(constructor, filePath, options) {
65
+ const body = constructor.getBody();
66
+ if (!body) {
67
+ // Constructor without body (interface, abstract, etc.)
68
+ return;
69
+ }
70
+
71
+ const statements = body.getStatements();
72
+
73
+ for (const statement of statements) {
74
+ const violation = this.analyzeStatement(statement, constructor, filePath);
75
+ if (violation) {
76
+ this.addViolation(violation);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Analyze a statement for complex logic patterns
83
+ * @param {ts.Statement} statement - Statement node
84
+ * @param {ts.ConstructorDeclaration} constructor - Parent constructor
85
+ * @param {string} filePath - File path
86
+ * @returns {Object|null} Violation object or null
87
+ */
88
+ analyzeStatement(statement, constructor, filePath) {
89
+ const statementKind = statement.getKind();
90
+ const line = statement.getStartLineNumber();
91
+ const column = statement.getStart() - statement.getStartLinePos() + 1;
92
+
93
+ // Check for complex logic patterns
94
+ switch (statementKind) {
95
+ case this.SyntaxKind.IfStatement:
96
+ return this.createViolation(
97
+ filePath, line, column,
98
+ 'Conditional logic (if statement) found in constructor',
99
+ 'conditional_logic',
100
+ statement.getText()
101
+ );
102
+
103
+ case this.SyntaxKind.ForStatement:
104
+ case this.SyntaxKind.ForInStatement:
105
+ case this.SyntaxKind.ForOfStatement:
106
+ case this.SyntaxKind.WhileStatement:
107
+ case this.SyntaxKind.DoStatement:
108
+ return this.createViolation(
109
+ filePath, line, column,
110
+ 'Loop logic found in constructor',
111
+ 'loop_logic',
112
+ statement.getText()
113
+ );
114
+
115
+ case this.SyntaxKind.SwitchStatement:
116
+ return this.createViolation(
117
+ filePath, line, column,
118
+ 'Switch statement found in constructor',
119
+ 'switch_logic',
120
+ statement.getText()
121
+ );
122
+
123
+ case this.SyntaxKind.TryStatement:
124
+ return this.createViolation(
125
+ filePath, line, column,
126
+ 'Exception handling (try/catch) found in constructor',
127
+ 'exception_handling',
128
+ statement.getText()
129
+ );
130
+
131
+ case this.SyntaxKind.ExpressionStatement:
132
+ return this.analyzeExpressionStatement(statement, filePath);
133
+
134
+ default:
135
+ return null;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Analyze expression statements for complex patterns
141
+ * @param {ts.ExpressionStatement} statement - Expression statement
142
+ * @param {string} filePath - File path
143
+ * @returns {Object|null} Violation object or null
144
+ */
145
+ analyzeExpressionStatement(statement, filePath) {
146
+ const expression = statement.getExpression();
147
+ const line = statement.getStartLineNumber();
148
+ const column = statement.getStart() - statement.getStartLinePos() + 1;
149
+
150
+ // Check for async operations
151
+ if (this.isAsyncOperation(expression)) {
152
+ return this.createViolation(
153
+ filePath, line, column,
154
+ 'Asynchronous operation found in constructor',
155
+ 'async_operation',
156
+ statement.getText()
157
+ );
158
+ }
159
+
160
+ // Check for complex method calls
161
+ if (this.isComplexMethodCall(expression)) {
162
+ return this.createViolation(
163
+ filePath, line, column,
164
+ 'Complex method call found in constructor',
165
+ 'complex_method_call',
166
+ statement.getText()
167
+ );
168
+ }
169
+
170
+ // Allow simple assignments and DI setup
171
+ if (this.isSimpleAssignment(expression) || this.isConfigurationSetup(expression)) {
172
+ return null;
173
+ }
174
+
175
+ return null;
176
+ }
177
+
178
+ /**
179
+ * Check if expression is an async operation
180
+ * @param {ts.Expression} expression - Expression node
181
+ * @returns {boolean} True if async operation
182
+ */
183
+ isAsyncOperation(expression) {
184
+ const text = expression.getText();
185
+
186
+ // Await expressions
187
+ if (expression.getKind() === this.SyntaxKind.AwaitExpression) {
188
+ return true;
189
+ }
190
+
191
+ // Promise chains
192
+ if (text.includes('.then(') || text.includes('.catch(') || text.includes('.finally(')) {
193
+ return true;
194
+ }
195
+
196
+ return false;
197
+ }
198
+
199
+ /**
200
+ * Check if expression is a complex method call
201
+ * @param {ts.Expression} expression - Expression node
202
+ * @returns {boolean} True if complex method call
203
+ */
204
+ isComplexMethodCall(expression) {
205
+ if (expression.getKind() !== this.SyntaxKind.CallExpression) {
206
+ return false;
207
+ }
208
+
209
+ const callExpr = expression;
210
+ const methodName = this.getMethodName(callExpr);
211
+
212
+ // Allow certain initialization methods
213
+ const allowedMethods = [
214
+ 'makeObservable', 'makeAutoObservable', // MobX
215
+ 'bind', 'bindAll', // Method binding
216
+ 'Object.assign', 'Object.create', // Object utilities
217
+ 'Array.from', 'Array.of', // Array utilities
218
+ ];
219
+
220
+ if (allowedMethods.some(method => methodName.includes(method))) {
221
+ return false;
222
+ }
223
+
224
+ // Check for chained calls (more than 2 levels = complex)
225
+ const chainLength = this.getChainLength(callExpr);
226
+ return chainLength > 2;
227
+ }
228
+
229
+ /**
230
+ * Check if expression is a simple assignment
231
+ * @param {ts.Expression} expression - Expression node
232
+ * @returns {boolean} True if simple assignment
233
+ */
234
+ isSimpleAssignment(expression) {
235
+ if (expression.getKind() !== this.SyntaxKind.BinaryExpression) {
236
+ return false;
237
+ }
238
+
239
+ const binaryExpr = expression;
240
+ const operator = binaryExpr.getOperatorToken();
241
+
242
+ return operator.getKind() === this.SyntaxKind.EqualsToken;
243
+ }
244
+
245
+ /**
246
+ * Check if expression is configuration setup
247
+ * @param {ts.Expression} expression - Expression node
248
+ * @returns {boolean} True if configuration setup
249
+ */
250
+ isConfigurationSetup(expression) {
251
+ const text = expression.getText();
252
+
253
+ // Configuration patterns
254
+ const configPatterns = [
255
+ /new\s+\w+Client\s*\(/, // AWS clients, HTTP clients
256
+ /new\s+\w+\s*\(\s*\{/, // Configuration objects
257
+ /\.getInstance\s*\(/, // Singleton patterns
258
+ /\.create\s*\(/, // Factory patterns
259
+ ];
260
+
261
+ return configPatterns.some(pattern => pattern.test(text));
262
+ }
263
+
264
+ /**
265
+ * Get method name from call expression
266
+ * @param {ts.CallExpression} callExpr - Call expression
267
+ * @returns {string} Method name
268
+ */
269
+ getMethodName(callExpr) {
270
+ const expression = callExpr.getExpression();
271
+
272
+ if (expression.getKind() === this.SyntaxKind.PropertyAccessExpression) {
273
+ return expression.getName();
274
+ }
275
+
276
+ if (expression.getKind() === this.SyntaxKind.Identifier) {
277
+ return expression.getText();
278
+ }
279
+
280
+ return expression.getText();
281
+ }
282
+
283
+ /**
284
+ * Get chain length of method calls
285
+ * @param {ts.CallExpression} callExpr - Call expression
286
+ * @returns {number} Chain length
287
+ */
288
+ getChainLength(callExpr) {
289
+ let current = callExpr.getExpression();
290
+ let length = 1;
291
+
292
+ while (current.getKind() === this.SyntaxKind.PropertyAccessExpression) {
293
+ const propAccess = current;
294
+ current = propAccess.getExpression();
295
+ length++;
296
+ }
297
+
298
+ return length;
299
+ }
300
+
301
+ /**
302
+ * Create a violation object
303
+ * @param {string} filePath - File path
304
+ * @param {number} line - Line number
305
+ * @param {number} column - Column number
306
+ * @param {string} message - Violation message
307
+ * @param {string} type - Violation type
308
+ * @param {string} code - Code snippet
309
+ * @returns {Object} Violation object
310
+ */
311
+ createViolation(filePath, line, column, message, type, code) {
312
+ return {
313
+ ruleId: this.ruleId,
314
+ file: filePath,
315
+ line: line,
316
+ column: column,
317
+ message: `Constructor contains complex logic: ${message}. Move to initialization methods`,
318
+ severity: 'warning',
319
+ code: code.trim(),
320
+ type: type,
321
+ confidence: 95, // High confidence with semantic analysis
322
+ analysisMethod: 'semantic',
323
+ suggestion: 'Move complex logic to separate initialization methods or lifecycle hooks'
324
+ };
325
+ }
326
+
327
+ /**
328
+ * Get SyntaxKind enum from ts-morph
329
+ * @returns {Object} SyntaxKind enum
330
+ */
331
+ get SyntaxKind() {
332
+ if (!this.semanticEngine) {
333
+ throw new Error('Semantic engine not initialized');
334
+ }
335
+ return this.semanticEngine.project.getTypeChecker().compilerObject.SyntaxKind ||
336
+ require('typescript').SyntaxKind;
337
+ }
338
+ }
339
+
340
+ module.exports = C017SemanticAnalyzer;
@@ -0,0 +1,232 @@
1
+ /**
2
+ * C018 Main Analyzer - Do not throw generic errors
3
+ * Primary: Always provide detailed messages and context.
4
+ * Fallback: Regex-based for all other cases
5
+ */
6
+
7
+ const C018SymbolBasedAnalyzer = require('./symbol-based-analyzer');
8
+ const C018RegexBasedAnalyzer = require('./regex-based-analyzer');
9
+
10
+ class C018Analyzer {
11
+ constructor(options = {}) {
12
+ if (process.env.SUNLINT_DEBUG) {
13
+ console.log(`🔧 [C018] Constructor called with options:`, !!options);
14
+ console.log(`🔧 [C018] Options type:`, typeof options, Object.keys(options || {}));
15
+ }
16
+
17
+ this.ruleId = 'C018';
18
+ this.ruleName = 'Do not throw generic errors';
19
+ this.description = 'Do not throw generic errors; always provide detailed messages and context.';
20
+ this.semanticEngine = options.semanticEngine || null;
21
+ this.verbose = options.verbose || false;
22
+
23
+ // Configuration
24
+ this.config = {
25
+ useSymbolBased: true, // Primary approach
26
+ fallbackToRegex: true, // Only when symbol fails completely
27
+ symbolBasedOnly: false // Can be set to true for pure mode
28
+ };
29
+
30
+ // Initialize both analyzers
31
+ try {
32
+ this.symbolAnalyzer = new C018SymbolBasedAnalyzer(this.semanticEngine);
33
+ if (process.env.SUNLINT_DEBUG) {
34
+ console.log(`🔧 [C018] Symbol analyzer created successfully`);
35
+ }
36
+ } catch (error) {
37
+ console.error(`🔧 [C018] Error creating symbol analyzer:`, error);
38
+ }
39
+
40
+ try {
41
+ this.regexAnalyzer = new C018RegexBasedAnalyzer(this.semanticEngine);
42
+ if (process.env.SUNLINT_DEBUG) {
43
+ console.log(`🔧 [C018] Regex analyzer created successfully`);
44
+ }
45
+ } catch (error) {
46
+ console.error(`🔧 [C018] Error creating regex analyzer:`, error);
47
+ }
48
+
49
+ if (process.env.SUNLINT_DEBUG) {
50
+ console.log(`🔧 [C018] Constructor completed`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Initialize with semantic engine
56
+ */
57
+ async initialize(semanticEngine = null) {
58
+ if (semanticEngine) {
59
+ this.semanticEngine = semanticEngine;
60
+ }
61
+ this.verbose = semanticEngine?.verbose || false;
62
+
63
+ // Initialize both analyzers
64
+ await this.symbolAnalyzer.initialize(semanticEngine);
65
+ await this.regexAnalyzer.initialize(semanticEngine);
66
+
67
+ // Ensure verbose flag is propagated
68
+ this.regexAnalyzer.verbose = this.verbose;
69
+ this.symbolAnalyzer.verbose = this.verbose;
70
+
71
+ if (this.verbose) {
72
+ console.log(`🔧 [C018 Hybrid] Analyzer initialized - verbose: ${this.verbose}`);
73
+ }
74
+ }
75
+
76
+ async analyze(files, language, options = {}) {
77
+ if (process.env.SUNLINT_DEBUG) {
78
+ console.log(`🔧 [C018] analyze() method called with ${files.length} files, language: ${language}`);
79
+ }
80
+
81
+ const violations = [];
82
+
83
+ for (const filePath of files) {
84
+ try {
85
+ if (process.env.SUNLINT_DEBUG) {
86
+ console.log(`🔧 [C018] Processing file: ${filePath}`);
87
+ }
88
+
89
+ const fileViolations = await this.analyzeFile(filePath, options);
90
+ violations.push(...fileViolations);
91
+
92
+ if (process.env.SUNLINT_DEBUG) {
93
+ console.log(`🔧 [C018] File ${filePath}: Found ${fileViolations.length} violations`);
94
+ }
95
+ } catch (error) {
96
+ console.warn(`❌ [C018] Analysis failed for ${filePath}:`, error.message);
97
+ }
98
+ }
99
+
100
+ if (process.env.SUNLINT_DEBUG) {
101
+ console.log(`🔧 [C018] Total violations found: ${violations.length}`);
102
+ }
103
+
104
+ return violations;
105
+ }
106
+
107
+ async analyzeFile(filePath, options = {}) {
108
+ if (process.env.SUNLINT_DEBUG) {
109
+ console.log(`🔧 [C018] analyzeFile() called for: ${filePath}`);
110
+ }
111
+
112
+ // 1. Try Symbol-based analysis first (primary)
113
+ if (this.config.useSymbolBased &&
114
+ this.semanticEngine?.project &&
115
+ this.semanticEngine?.initialized) {
116
+ try {
117
+ if (process.env.SUNLINT_DEBUG) {
118
+ console.log(`🔧 [C018] Trying symbol-based analysis...`);
119
+ }
120
+ const sourceFile = this.semanticEngine.project.getSourceFile(filePath);
121
+ if (sourceFile) {
122
+ if (process.env.SUNLINT_DEBUG) {
123
+ console.log(`🔧 [C018] Source file found, analyzing with symbol-based...`);
124
+ }
125
+ const violations = await this.symbolAnalyzer.analyzeFileWithSymbols(filePath, { ...options, verbose: options.verbose });
126
+
127
+ // Mark violations with analysis strategy
128
+ violations.forEach(v => v.analysisStrategy = 'symbol-based');
129
+
130
+ if (process.env.SUNLINT_DEBUG) {
131
+ console.log(`✅ [C018] Symbol-based analysis: ${violations.length} violations`);
132
+ }
133
+ return violations; // Return even if 0 violations - symbol analysis completed successfully
134
+ } else {
135
+ if (process.env.SUNLINT_DEBUG) {
136
+ console.log(`⚠️ [C018] Source file not found in project`);
137
+ }
138
+ }
139
+ } catch (error) {
140
+ console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
141
+ // Continue to fallback
142
+ }
143
+ } else {
144
+ if (process.env.SUNLINT_DEBUG) {
145
+ console.log(`🔄 [C018] Symbol analysis conditions check:`);
146
+ console.log(` - useSymbolBased: ${this.config.useSymbolBased}`);
147
+ console.log(` - semanticEngine: ${!!this.semanticEngine}`);
148
+ console.log(` - semanticEngine.project: ${!!this.semanticEngine?.project}`);
149
+ console.log(` - semanticEngine.initialized: ${this.semanticEngine?.initialized}`);
150
+ console.log(`🔄 [C018] Symbol analysis unavailable, using regex fallback`);
151
+ }
152
+ }
153
+
154
+ // 2. Fallback to regex-based analysis
155
+ if (this.config.fallbackToRegex) {
156
+ try {
157
+ if (process.env.SUNLINT_DEBUG) {
158
+ console.log(`🔧 [C018] Trying regex-based analysis...`);
159
+ }
160
+ const violations = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
161
+
162
+ // Mark violations with analysis strategy
163
+ violations.forEach(v => v.analysisStrategy = 'regex-fallback');
164
+
165
+ if (process.env.SUNLINT_DEBUG) {
166
+ console.log(`🔄 [C018] Regex-based analysis: ${violations.length} violations`);
167
+ }
168
+ return violations;
169
+ } catch (error) {
170
+ console.error(`❌ [C018] Regex analysis failed: ${error.message}`);
171
+ }
172
+ }
173
+
174
+ if (options?.verbose) {
175
+ console.log(`🔧 [C018] No analysis methods succeeded, returning empty`);
176
+ }
177
+ return [];
178
+ }
179
+
180
+ async analyzeFileBasic(filePath, options = {}) {
181
+ console.log(`🔧 [C018] analyzeFileBasic() called for: ${filePath}`);
182
+ console.log(`🔧 [C018] semanticEngine exists: ${!!this.semanticEngine}`);
183
+ console.log(`🔧 [C018] symbolAnalyzer exists: ${!!this.symbolAnalyzer}`);
184
+ console.log(`🔧 [C018] regexAnalyzer exists: ${!!this.regexAnalyzer}`);
185
+
186
+ try {
187
+ // Try symbol-based analysis first
188
+ if (this.semanticEngine?.isSymbolEngineReady?.() &&
189
+ this.semanticEngine.project) {
190
+
191
+ if (this.verbose) {
192
+ console.log(`🔍 [C018] Using symbol-based analysis for ${filePath}`);
193
+ }
194
+
195
+ const violations = await this.symbolAnalyzer.analyzeFileBasic(filePath, options);
196
+ return violations;
197
+ }
198
+ } catch (error) {
199
+ if (this.verbose) {
200
+ console.warn(`⚠️ [C018] Symbol analysis failed: ${error.message}`);
201
+ }
202
+ }
203
+
204
+ // Fallback to regex-based analysis
205
+ if (this.verbose) {
206
+ console.log(`🔄 [C018] Using regex-based analysis (fallback) for ${filePath}`);
207
+ }
208
+
209
+ console.log(`🔧 [C018] About to call regexAnalyzer.analyzeFileBasic()`);
210
+ try {
211
+ const result = await this.regexAnalyzer.analyzeFileBasic(filePath, options);
212
+ console.log(`🔧 [C018] Regex analyzer returned: ${result.length} violations`);
213
+ return result;
214
+ } catch (error) {
215
+ console.error(`🔧 [C018] Error in regex analyzer:`, error);
216
+ return [];
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Methods for compatibility with different engine invocation patterns
222
+ */
223
+ async analyzeFileWithSymbols(filePath, options = {}) {
224
+ return this.analyzeFile(filePath, options);
225
+ }
226
+
227
+ async analyzeWithSemantics(filePath, options = {}) {
228
+ return this.analyzeFile(filePath, options);
229
+ }
230
+ }
231
+
232
+ module.exports = C018Analyzer;
@@ -0,0 +1,50 @@
1
+ {
2
+ "id": "C018",
3
+ "name": "C018_do_not_throw_generic_errors",
4
+ "category": "architecture",
5
+ "description": "C018 - Always provide detailed messages and context.",
6
+ "severity": "warning",
7
+ "enabled": true,
8
+ "semantic": {
9
+ "enabled": true,
10
+ "priority": "high",
11
+ "fallback": "heuristic"
12
+ },
13
+ "patterns": {
14
+ "include": [
15
+ "**/*.js",
16
+ "**/*.ts",
17
+ "**/*.jsx",
18
+ "**/*.tsx"
19
+ ],
20
+ "exclude": [
21
+ "**/*.test.*",
22
+ "**/*.spec.*",
23
+ "**/*.mock.*",
24
+ "**/test/**",
25
+ "**/tests/**",
26
+ "**/spec/**"
27
+ ]
28
+ },
29
+ "options": {
30
+ "strictMode": false,
31
+ "allowedDbMethods": [],
32
+ "repositoryPatterns": [
33
+ "*Repository*",
34
+ "*Repo*",
35
+ "*DAO*",
36
+ "*Store*"
37
+ ],
38
+ "servicePatterns": [
39
+ "*Service*",
40
+ "*UseCase*",
41
+ "*Handler*",
42
+ "*Manager*"
43
+ ],
44
+ "complexityThreshold": {
45
+ "methodLength": 200,
46
+ "cyclomaticComplexity": 5,
47
+ "nestedDepth": 3
48
+ }
49
+ }
50
+ }