@sun-asterisk/sunlint 1.3.1 → 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 (66) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/CONTRIBUTING.md +210 -1691
  3. package/config/rule-analysis-strategies.js +17 -1
  4. package/config/rules/enhanced-rules-registry.json +369 -1135
  5. package/config/rules/rules-registry-generated.json +1 -1
  6. package/core/enhanced-rules-registry.js +2 -1
  7. package/core/semantic-engine.js +15 -3
  8. package/core/semantic-rule-base.js +4 -2
  9. package/engines/heuristic-engine.js +65 -4
  10. package/integrations/eslint/plugin/rules/common/c003-no-vague-abbreviations.js +59 -1
  11. package/integrations/eslint/plugin/rules/common/c006-function-name-verb-noun.js +26 -1
  12. package/integrations/eslint/plugin/rules/common/c030-use-custom-error-classes.js +54 -19
  13. package/origin-rules/common-en.md +11 -7
  14. package/package.json +1 -1
  15. package/rules/common/C002_no_duplicate_code/analyzer.js +334 -36
  16. package/rules/common/C003_no_vague_abbreviations/analyzer.js +220 -35
  17. package/rules/common/C006_function_naming/analyzer.js +29 -3
  18. package/rules/common/C010_limit_block_nesting/analyzer.js +181 -337
  19. package/rules/common/C010_limit_block_nesting/config.json +64 -0
  20. package/rules/common/C010_limit_block_nesting/regex-based-analyzer.js +379 -0
  21. package/rules/common/C010_limit_block_nesting/symbol-based-analyzer.js +231 -0
  22. package/rules/common/C013_no_dead_code/analyzer.js +75 -177
  23. package/rules/common/C013_no_dead_code/config.json +61 -0
  24. package/rules/common/C013_no_dead_code/regex-based-analyzer.js +345 -0
  25. package/rules/common/C013_no_dead_code/symbol-based-analyzer.js +640 -0
  26. package/rules/common/C014_dependency_injection/analyzer.js +48 -313
  27. package/rules/common/C014_dependency_injection/config.json +26 -0
  28. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +751 -0
  29. package/rules/common/C018_no_throw_generic_error/analyzer.js +232 -0
  30. package/rules/common/C018_no_throw_generic_error/config.json +50 -0
  31. package/rules/common/C018_no_throw_generic_error/regex-based-analyzer.js +387 -0
  32. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +314 -0
  33. package/rules/common/C019_log_level_usage/analyzer.js +110 -317
  34. package/rules/common/C019_log_level_usage/pattern-analyzer.js +88 -0
  35. package/rules/common/C019_log_level_usage/system-log-analyzer.js +1267 -0
  36. package/rules/common/C023_no_duplicate_variable/analyzer.js +180 -0
  37. package/rules/common/C023_no_duplicate_variable/config.json +50 -0
  38. package/rules/common/C023_no_duplicate_variable/symbol-based-analyzer.js +158 -0
  39. package/rules/common/C024_no_scatter_hardcoded_constants/analyzer.js +180 -0
  40. package/rules/common/C024_no_scatter_hardcoded_constants/config.json +50 -0
  41. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +181 -0
  42. package/rules/common/C030_use_custom_error_classes/analyzer.js +200 -0
  43. package/rules/common/C035_error_logging_context/analyzer.js +3 -1
  44. package/rules/index.js +5 -1
  45. package/rules/security/S009_no_insecure_encryption/README.md +158 -0
  46. package/rules/security/S009_no_insecure_encryption/analyzer.js +319 -0
  47. package/rules/security/S009_no_insecure_encryption/config.json +55 -0
  48. package/rules/security/S010_no_insecure_encryption/README.md +224 -0
  49. package/rules/security/S010_no_insecure_encryption/analyzer.js +493 -0
  50. package/rules/security/S010_no_insecure_encryption/config.json +48 -0
  51. package/rules/security/S016_no_sensitive_querystring/STRATEGY.md +149 -0
  52. package/rules/security/S016_no_sensitive_querystring/analyzer.js +276 -0
  53. package/rules/security/S016_no_sensitive_querystring/config.json +127 -0
  54. package/rules/security/S016_no_sensitive_querystring/regex-based-analyzer.js +258 -0
  55. package/rules/security/S016_no_sensitive_querystring/symbol-based-analyzer.js +495 -0
  56. package/rules/security/S048_no_current_password_in_reset/README.md +222 -0
  57. package/rules/security/S048_no_current_password_in_reset/analyzer.js +366 -0
  58. package/rules/security/S048_no_current_password_in_reset/config.json +48 -0
  59. package/rules/security/S055_content_type_validation/README.md +176 -0
  60. package/rules/security/S055_content_type_validation/analyzer.js +312 -0
  61. package/rules/security/S055_content_type_validation/config.json +48 -0
  62. package/rules/utils/rule-helpers.js +140 -1
  63. package/scripts/consolidate-config.js +116 -0
  64. package/config/rules/S027-categories.json +0 -122
  65. package/config/rules/rules-registry.json +0 -777
  66. package/rules/common/C006_function_naming/smart-analyzer.js +0 -503
@@ -1,64 +1,362 @@
1
1
  /**
2
- * C002_no_duplicate_code - Heuristic Rule Analyzer
2
+ * C002_no_duplicate_code - Enhanced Regex-based Rule Analyzer
3
3
  * Category: coding
4
4
  *
5
- * TODO: Migrate logic from ESLint rule
6
- * ESLint rule: integrations/eslint/plugin/rules/coding/c002-no_duplicate_code.js
5
+ * Detects duplicate code blocks longer than specified threshold (default: 10 lines)
6
+ * Uses regex-based approach with proper comment filtering for multi-language support
7
7
  */
8
8
 
9
- const ts = require('typescript');
10
- const { PatternMatcher } = require('../../utils/pattern-matchers');
11
- const { RuleHelper } = require('../../utils/rule-helpers');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const { CommentDetector } = require('../../utils/rule-helpers');
12
12
 
13
13
  class C002_no_duplicate_codeAnalyzer {
14
14
  constructor(config = {}) {
15
- this.config = config;
16
- this.patternMatcher = new PatternMatcher();
17
- this.helper = new RuleHelper();
15
+ this.config = {
16
+ minLines: config.minLines || 5,
17
+ ignoreComments: config.ignoreComments !== false,
18
+ ignoreWhitespace: config.ignoreWhitespace !== false,
19
+ ignoreEmptyLines: config.ignoreEmptyLines !== false,
20
+ similarityThreshold: config.similarityThreshold || 0.80, // 80% similarity
21
+ ...config
22
+ };
23
+ this.codeBlocks = new Map();
24
+ this.reportedBlocks = new Set();
18
25
  }
19
26
 
20
27
  /**
21
- * Analyze code content for rule violations
22
- * @param {string} content - File content
23
- * @param {string} filePath - File path
24
- * @param {Object} context - Analysis context
28
+ * Analyze files for duplicate code violations (heuristic engine interface)
29
+ * @param {Array} files - Array of file paths
30
+ * @param {string} language - Programming language
31
+ * @param {Object} options - Analysis options
25
32
  * @returns {Array} Array of violations
26
33
  */
27
- analyze(content, filePath, context = {}) {
34
+ analyze(files, language, options = {}) {
28
35
  const violations = [];
29
36
 
30
- // TODO: Implement heuristic analysis logic
31
- // This should replicate the ESLint rule behavior using pattern matching
32
-
33
37
  try {
34
- // Example pattern-based analysis
35
- // const patterns = this.getViolationPatterns();
36
- // const matches = this.patternMatcher.findMatches(content, patterns);
37
- //
38
- // matches.forEach(match => {
39
- // violations.push(this.helper.createViolation({
40
- // ruleId: 'C002_no_duplicate_code',
41
- // message: 'Rule violation detected',
42
- // line: match.line,
43
- // column: match.column,
44
- // severity: 'error'
45
- // }));
46
- // });
38
+ console.log(`[C002 DEBUG] Analyzing ${files.length} files for duplicate code`);
39
+
40
+ // Reset state for new analysis
41
+ this.reset();
42
+
43
+ // Collect all code blocks from all files
44
+ const allCodeBlocks = [];
45
+
46
+ for (const filePath of files) {
47
+ console.log(`[C002 DEBUG] Processing file: ${filePath}`);
48
+ const content = this.readFileContent(filePath);
49
+ if (content) {
50
+ console.log(`[C002 DEBUG] File content length: ${content.length}`);
51
+ const codeBlocks = this.extractCodeBlocks(content, filePath);
52
+ console.log(`[C002 DEBUG] Extracted ${codeBlocks.length} code blocks from ${filePath}`);
53
+ codeBlocks.forEach((block, i) => {
54
+ console.log(`[C002 DEBUG] Block ${i}: ${block.type} at lines ${block.startLine}-${block.endLine} (${block.lineCount} lines)`);
55
+ });
56
+ allCodeBlocks.push(...codeBlocks);
57
+ }
58
+ }
59
+
60
+ console.log(`[C002 DEBUG] Total code blocks: ${allCodeBlocks.length}`);
61
+
62
+ // Find duplicates across all files
63
+ const duplicates = this.findDuplicates(allCodeBlocks);
64
+ console.log(`[C002 DEBUG] Found ${duplicates.length} duplicate groups`);
65
+
66
+ // Generate violations for each file
67
+ files.forEach(filePath => {
68
+ duplicates.forEach(duplicate => {
69
+ const fileViolations = this.createViolations(duplicate, filePath);
70
+ console.log(`[C002 DEBUG] Created ${fileViolations.length} violations for ${filePath}`);
71
+ violations.push(...fileViolations);
72
+ });
73
+ });
47
74
 
48
75
  } catch (error) {
49
- console.warn(`Error analyzing ${filePath} with C002_no_duplicate_code:`, error.message);
76
+ console.warn(`Error analyzing files with C002:`, error.message, error.stack);
50
77
  }
51
78
 
79
+ console.log(`[C002 DEBUG] Total violations: ${violations.length}`);
52
80
  return violations;
53
81
  }
54
82
 
55
83
  /**
56
- * Get violation patterns for this rule
57
- * @returns {Array} Array of patterns to match
84
+ * Read file content safely
85
+ * @param {string} filePath - Path to file
86
+ * @returns {string|null} File content or null if error
87
+ */
88
+ readFileContent(filePath) {
89
+ try {
90
+ return fs.readFileSync(filePath, 'utf8');
91
+ } catch (error) {
92
+ console.warn(`C002: Cannot read file ${filePath}:`, error.message);
93
+ return null;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Extract code blocks from content
99
+ * @param {string} content - File content
100
+ * @param {string} filePath - File path for context
101
+ * @returns {Array} Array of code blocks with metadata
102
+ */
103
+ extractCodeBlocks(content, filePath) {
104
+ const lines = content.split('\n');
105
+ const blocks = [];
106
+
107
+ // Extract function blocks, class methods, etc.
108
+ const functionPattern = /^\s*(function\s+\w+|const\s+\w+\s*=\s*(async\s+)?\([^)]*\)\s*=>|class\s+\w+|\w+\s*\([^)]*\)\s*:\s*[^{]*\{)/;
109
+ let currentBlock = null;
110
+ let braceLevel = 0;
111
+
112
+ lines.forEach((line, index) => {
113
+ const lineNum = index + 1;
114
+ const trimmedLine = line.trim();
115
+
116
+ // Use CommentDetector to filter out comments
117
+ const filteredLines = CommentDetector.filterCommentLines([line]);
118
+ if (filteredLines[0].isComment) {
119
+ return;
120
+ }
121
+
122
+ // Skip empty lines if configured
123
+ if (this.config.ignoreEmptyLines && !trimmedLine) {
124
+ return;
125
+ }
126
+
127
+ // Detect function/method/class start
128
+ if (functionPattern.test(trimmedLine)) {
129
+ currentBlock = {
130
+ startLine: lineNum,
131
+ lines: [line],
132
+ filePath: filePath,
133
+ type: this.detectBlockType(trimmedLine)
134
+ };
135
+ braceLevel = (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
136
+ } else if (currentBlock) {
137
+ currentBlock.lines.push(line);
138
+ braceLevel += (line.match(/{/g) || []).length - (line.match(/}/g) || []).length;
139
+
140
+ // End of block
141
+ if (braceLevel <= 0) {
142
+ currentBlock.endLine = lineNum;
143
+ currentBlock.lineCount = currentBlock.lines.length;
144
+
145
+ // Only consider blocks that meet minimum line requirement
146
+ if (currentBlock.lineCount >= this.config.minLines) {
147
+ currentBlock.normalizedCode = this.normalizeCode(currentBlock.lines.join('\n'));
148
+ if (currentBlock.normalizedCode.length > 20) { // Skip if too short after normalization
149
+ blocks.push(currentBlock);
150
+ }
151
+ }
152
+ currentBlock = null;
153
+ braceLevel = 0;
154
+ }
155
+ }
156
+ });
157
+
158
+ return blocks;
159
+ }
160
+
161
+ /**
162
+ * Detect the type of code block
163
+ * @param {string} line - First line of the block
164
+ * @returns {string} Block type
165
+ */
166
+ detectBlockType(line) {
167
+ if (line.includes('function')) return 'function';
168
+ if (line.includes('class')) return 'class';
169
+ if (line.includes('interface')) return 'interface';
170
+ if (line.includes('=>')) return 'arrow-function';
171
+ return 'method';
172
+ }
173
+
174
+ /**
175
+ * Normalize code for comparison
176
+ * @param {string} code - Raw code
177
+ * @returns {string} Normalized code
178
+ */
179
+ normalizeCode(code) {
180
+ let normalized = code;
181
+
182
+ if (this.config.ignoreComments) {
183
+ // Remove single line comments (// comments)
184
+ normalized = normalized.replace(/\/\/.*$/gm, '');
185
+ // Remove multi-line comments (/* comments */)
186
+ normalized = normalized.replace(/\/\*[\s\S]*?\*\//g, '');
187
+ // Remove # comments (for other languages)
188
+ normalized = normalized.replace(/#.*$/gm, '');
189
+ }
190
+
191
+ if (this.config.ignoreWhitespace) {
192
+ // Normalize whitespace
193
+ normalized = normalized
194
+ .replace(/\s+/g, ' ') // Multiple spaces to single space
195
+ .replace(/\s*{\s*/g, '{') // Remove spaces around braces
196
+ .replace(/\s*}\s*/g, '}')
197
+ .replace(/\s*;\s*/g, ';') // Remove spaces around semicolons
198
+ .replace(/\s*,\s*/g, ',') // Remove spaces around commas
199
+ .trim();
200
+ }
201
+
202
+ if (this.config.ignoreEmptyLines) {
203
+ // Remove empty lines
204
+ normalized = normalized
205
+ .split('\n')
206
+ .filter(line => line.trim().length > 0)
207
+ .join('\n');
208
+ }
209
+
210
+ console.log(`[C002 DEBUG] Normalized code block:
211
+ ${normalized}
212
+ ---`);
213
+
214
+ return normalized;
215
+ }
216
+
217
+ /**
218
+ * Find duplicate code blocks
219
+ * @param {Array} blocks - Array of code blocks
220
+ * @returns {Array} Array of duplicate groups
221
+ */
222
+ findDuplicates(blocks) {
223
+ const duplicateGroups = [];
224
+ const processedBlocks = new Set();
225
+
226
+ for (let i = 0; i < blocks.length; i++) {
227
+ if (processedBlocks.has(i)) continue;
228
+
229
+ const currentBlock = blocks[i];
230
+ const duplicates = [currentBlock];
231
+
232
+ for (let j = i + 1; j < blocks.length; j++) {
233
+ if (processedBlocks.has(j)) continue;
234
+
235
+ const otherBlock = blocks[j];
236
+ const similarity = this.calculateSimilarity(
237
+ currentBlock.normalizedCode,
238
+ otherBlock.normalizedCode
239
+ );
240
+
241
+ if (similarity >= this.config.similarityThreshold) {
242
+ duplicates.push(otherBlock);
243
+ processedBlocks.add(j);
244
+ }
245
+ }
246
+
247
+ if (duplicates.length > 1) {
248
+ duplicateGroups.push(duplicates);
249
+ processedBlocks.add(i);
250
+ }
251
+ }
252
+
253
+ return duplicateGroups;
254
+ }
255
+
256
+ /**
257
+ * Calculate similarity between two code strings
258
+ * @param {string} code1 - First code string
259
+ * @param {string} code2 - Second code string
260
+ * @returns {number} Similarity ratio (0-1)
261
+ */
262
+ calculateSimilarity(code1, code2) {
263
+ if (code1 === code2) return 1.0;
264
+
265
+ // Use Levenshtein distance for similarity calculation
266
+ const longer = code1.length > code2.length ? code1 : code2;
267
+ const shorter = code1.length > code2.length ? code2 : code1;
268
+
269
+ if (longer.length === 0) return 1.0;
270
+
271
+ const distance = this.levenshteinDistance(longer, shorter);
272
+ return (longer.length - distance) / longer.length;
273
+ }
274
+
275
+ /**
276
+ * Calculate Levenshtein distance between two strings
277
+ * @param {string} str1 - First string
278
+ * @param {string} str2 - Second string
279
+ * @returns {number} Edit distance
280
+ */
281
+ levenshteinDistance(str1, str2) {
282
+ const matrix = Array(str2.length + 1).fill().map(() => Array(str1.length + 1).fill(0));
283
+
284
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
285
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
286
+
287
+ for (let j = 1; j <= str2.length; j++) {
288
+ for (let i = 1; i <= str1.length; i++) {
289
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
290
+ matrix[j][i] = Math.min(
291
+ matrix[j - 1][i] + 1, // deletion
292
+ matrix[j][i - 1] + 1, // insertion
293
+ matrix[j - 1][i - 1] + cost // substitution
294
+ );
295
+ }
296
+ }
297
+
298
+ return matrix[str2.length][str1.length];
299
+ }
300
+
301
+ /**
302
+ * Create violation objects for duplicate code
303
+ * @param {Array} duplicateGroup - Group of duplicate blocks
304
+ * @param {string} filePath - Current file path
305
+ * @returns {Array} Array of violation objects
306
+ */
307
+ createViolations(duplicateGroup, filePath) {
308
+ const violations = [];
309
+
310
+ duplicateGroup.forEach((block, index) => {
311
+ // Skip if not in current file or already reported
312
+ if (block.filePath !== filePath) return;
313
+
314
+ const blockId = `${block.filePath}:${block.startLine}-${block.endLine}`;
315
+ if (this.reportedBlocks.has(blockId)) return;
316
+
317
+ this.reportedBlocks.add(blockId);
318
+
319
+ violations.push({
320
+ ruleId: 'C002',
321
+ severity: 'error',
322
+ message: `Duplicate ${block.type} found (${block.lineCount} lines). Consider extracting into a shared function or module. Found ${duplicateGroup.length} similar blocks.`,
323
+ line: block.startLine,
324
+ column: 1,
325
+ endLine: block.endLine,
326
+ endColumn: 1,
327
+ filePath: filePath, // Add filePath field for engine compatibility
328
+ data: {
329
+ lineCount: block.lineCount,
330
+ blockType: block.type,
331
+ duplicateCount: duplicateGroup.length,
332
+ locations: duplicateGroup.map(b => `${path.basename(b.filePath)}:${b.startLine}-${b.endLine}`)
333
+ }
334
+ });
335
+ });
336
+
337
+ return violations;
338
+ }
339
+
340
+ /**
341
+ * Reset analyzer state for new analysis session
342
+ */
343
+ reset() {
344
+ this.codeBlocks.clear();
345
+ this.reportedBlocks.clear();
346
+ }
347
+
348
+ /**
349
+ * Get configuration for this rule
350
+ * @returns {Object} Configuration object
58
351
  */
59
- getViolationPatterns() {
60
- // TODO: Define patterns based on ESLint rule logic
61
- return [];
352
+ getConfig() {
353
+ return {
354
+ minLines: this.config.minLines,
355
+ ignoreComments: this.config.ignoreComments,
356
+ ignoreWhitespace: this.config.ignoreWhitespace,
357
+ ignoreEmptyLines: this.config.ignoreEmptyLines,
358
+ similarityThreshold: this.config.similarityThreshold
359
+ };
62
360
  }
63
361
  }
64
362