@sun-asterisk/sunlint 1.2.1 → 1.2.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 (77) hide show
  1. package/config/rule-analysis-strategies.js +18 -2
  2. package/engines/eslint-engine.js +9 -11
  3. package/engines/heuristic-engine.js +55 -31
  4. package/package.json +2 -1
  5. package/rules/README.md +252 -0
  6. package/rules/common/C002_no_duplicate_code/analyzer.js +65 -0
  7. package/rules/common/C002_no_duplicate_code/config.json +23 -0
  8. package/rules/common/C003_no_vague_abbreviations/analyzer.js +418 -0
  9. package/rules/common/C003_no_vague_abbreviations/config.json +35 -0
  10. package/rules/common/C006_function_naming/analyzer.js +504 -0
  11. package/rules/common/C006_function_naming/config.json +86 -0
  12. package/rules/common/C006_function_naming/smart-analyzer.js +503 -0
  13. package/rules/common/C010_limit_block_nesting/analyzer.js +389 -0
  14. package/rules/common/C012_command_query_separation/analyzer.js +481 -0
  15. package/rules/common/C012_command_query_separation/ast-analyzer.js +495 -0
  16. package/rules/common/C013_no_dead_code/analyzer.js +206 -0
  17. package/rules/common/C014_dependency_injection/analyzer.js +338 -0
  18. package/rules/common/C017_constructor_logic/analyzer.js +314 -0
  19. package/rules/common/C019_log_level_usage/analyzer.js +362 -0
  20. package/rules/common/C019_log_level_usage/config.json +121 -0
  21. package/rules/common/C029_catch_block_logging/analyzer-backup.js +426 -0
  22. package/rules/common/C029_catch_block_logging/analyzer-fixed.js +130 -0
  23. package/rules/common/C029_catch_block_logging/analyzer-multi-tech.js +487 -0
  24. package/rules/common/C029_catch_block_logging/analyzer-simple.js +110 -0
  25. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +755 -0
  26. package/rules/common/C029_catch_block_logging/analyzer.js +129 -0
  27. package/rules/common/C029_catch_block_logging/ast-analyzer-backup.js +441 -0
  28. package/rules/common/C029_catch_block_logging/ast-analyzer-new.js +127 -0
  29. package/rules/common/C029_catch_block_logging/ast-analyzer.js +133 -0
  30. package/rules/common/C029_catch_block_logging/cfg-analyzer.js +408 -0
  31. package/rules/common/C029_catch_block_logging/config.json +59 -0
  32. package/rules/common/C029_catch_block_logging/dataflow-analyzer.js +454 -0
  33. package/rules/common/C029_catch_block_logging/multi-language-ast-engine.js +700 -0
  34. package/rules/common/C029_catch_block_logging/pattern-learning-analyzer.js +568 -0
  35. package/rules/common/C029_catch_block_logging/semantic-analyzer.js +459 -0
  36. package/rules/common/C031_validation_separation/analyzer.js +186 -0
  37. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +292 -0
  38. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +296 -0
  39. package/rules/common/C042_boolean_name_prefix/analyzer.js +300 -0
  40. package/rules/common/C043_no_console_or_print/analyzer.js +431 -0
  41. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +590 -0
  42. package/rules/common/C075_explicit_return_types/analyzer.js +103 -0
  43. package/rules/common/C076_single_test_behavior/analyzer.js +121 -0
  44. package/rules/docs/C002_no_duplicate_code.md +57 -0
  45. package/rules/docs/C031_validation_separation.md +72 -0
  46. package/rules/index.js +155 -0
  47. package/rules/migration/converter.js +385 -0
  48. package/rules/migration/mapping.json +164 -0
  49. package/rules/parser/constants.js +31 -0
  50. package/rules/parser/file-config.js +80 -0
  51. package/rules/parser/rule-parser-simple.js +305 -0
  52. package/rules/parser/rule-parser.js +527 -0
  53. package/rules/security/S015_insecure_tls_certificate/analyzer.js +150 -0
  54. package/rules/security/S015_insecure_tls_certificate/ast-analyzer.js +237 -0
  55. package/rules/security/S023_no_json_injection/analyzer.js +278 -0
  56. package/rules/security/S023_no_json_injection/ast-analyzer.js +359 -0
  57. package/rules/security/S026_json_schema_validation/analyzer.js +251 -0
  58. package/rules/security/S026_json_schema_validation/config.json +27 -0
  59. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +436 -0
  60. package/rules/security/S027_no_hardcoded_secrets/config.json +29 -0
  61. package/rules/security/S029_csrf_protection/analyzer.js +330 -0
  62. package/rules/tests/C002_no_duplicate_code.test.js +50 -0
  63. package/rules/universal/C010/generic.js +0 -0
  64. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  65. package/rules/utils/ast-utils.js +191 -0
  66. package/rules/utils/base-analyzer.js +98 -0
  67. package/rules/utils/pattern-matchers.js +239 -0
  68. package/rules/utils/rule-helpers.js +264 -0
  69. package/rules/utils/severity-constants.js +93 -0
  70. package/scripts/generate_insights.js +188 -0
  71. package/scripts/merge-reports.js +0 -424
  72. package/scripts/test-scripts/README.md +0 -22
  73. package/scripts/test-scripts/test-c041-comparison.js +0 -114
  74. package/scripts/test-scripts/test-c041-eslint.js +0 -67
  75. package/scripts/test-scripts/test-eslint-rules.js +0 -146
  76. package/scripts/test-scripts/test-real-world.js +0 -44
  77. package/scripts/test-scripts/test-rules-on-real-projects.js +0 -86
@@ -0,0 +1,121 @@
1
+ /**
2
+ * C076 Rule: Each test should assert only one behavior
3
+ * Ensures test focus and maintainability by limiting assertions per test
4
+ * Severity: warning
5
+ * Category: Quality
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ class C076SingleTestBehaviorAnalyzer {
12
+ constructor() {
13
+ this.ruleId = 'C076';
14
+ this.ruleName = 'Single Test Behavior';
15
+ this.description = 'Each test should assert only one behavior';
16
+ this.severity = 'warning';
17
+ this.maxAssertions = 1;
18
+ }
19
+
20
+ async analyze(files, language, config) {
21
+ const violations = [];
22
+
23
+ for (const filePath of files) {
24
+ try {
25
+ const fileContent = fs.readFileSync(filePath, 'utf8');
26
+ const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
27
+ violations.push(...fileViolations);
28
+ } catch (error) {
29
+ console.warn(`C076 analysis error for ${filePath}:`, error.message);
30
+ }
31
+ }
32
+
33
+ return violations;
34
+ }
35
+
36
+ async analyzeFile(filePath, fileContent, language, config) {
37
+ const violations = [];
38
+
39
+ try {
40
+ // Skip non-test files
41
+ if (!this.isTestFile(filePath)) {
42
+ return violations;
43
+ }
44
+
45
+ const lines = fileContent.split('\n');
46
+ let inTestBlock = false;
47
+ let testStartLine = 0;
48
+ let testName = '';
49
+ let braceLevel = 0;
50
+ let expectCount = 0;
51
+
52
+ for (let i = 0; i < lines.length; i++) {
53
+ const line = lines[i];
54
+ const lineNumber = i + 1;
55
+
56
+ // Check for test function start
57
+ const testMatch = line.match(/^\s*(?:it|test)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?:async\s+)?\(.*\)\s*=>\s*\{|^\s*(?:it|test)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(?:async\s+)?function/);
58
+ if (testMatch) {
59
+ inTestBlock = true;
60
+ testStartLine = lineNumber;
61
+ testName = testMatch[1] || testMatch[2] || 'unnamed test';
62
+ braceLevel = 1;
63
+ expectCount = 0;
64
+ continue;
65
+ }
66
+
67
+ if (inTestBlock) {
68
+ // Count braces to track test scope
69
+ const openBraces = (line.match(/\{/g) || []).length;
70
+ const closeBraces = (line.match(/\}/g) || []).length;
71
+ braceLevel = braceLevel + openBraces - closeBraces;
72
+
73
+ // Count expect/assert statements
74
+ const expectMatches = line.match(/\b(?:expect|assert|should)\s*\(/g);
75
+ if (expectMatches) {
76
+ expectCount += expectMatches.length;
77
+ }
78
+
79
+ // Check if test block ended
80
+ if (braceLevel <= 0) {
81
+ inTestBlock = false;
82
+
83
+ // Report violation if too many expectations
84
+ if (expectCount > this.maxAssertions) {
85
+ violations.push({
86
+ ruleId: this.ruleId,
87
+ severity: this.severity,
88
+ message: `Test '${testName}' has ${expectCount} assertions. Each test should focus on one behavior (max ${this.maxAssertions} assertions)`,
89
+ filePath: filePath,
90
+ line: testStartLine,
91
+ column: 1,
92
+ source: lines[testStartLine - 1]?.trim() || '',
93
+ suggestion: 'Consider splitting into separate test cases'
94
+ });
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ } catch (error) {
101
+ console.warn(`C076 analysis error for ${filePath}:`, error.message);
102
+ }
103
+
104
+ return violations;
105
+ }
106
+
107
+ isTestFile(filePath) {
108
+ const testPatterns = [
109
+ /\.test\.(js|ts|jsx|tsx)$/,
110
+ /\.spec\.(js|ts|jsx|tsx)$/,
111
+ /\/__tests__\//,
112
+ /\/tests?\//,
113
+ /\.e2e\./,
114
+ /\.integration\./
115
+ ];
116
+
117
+ return testPatterns.some(pattern => pattern.test(filePath));
118
+ }
119
+ }
120
+
121
+ module.exports = C076SingleTestBehaviorAnalyzer;
@@ -0,0 +1,57 @@
1
+ # C002_no_duplicate_code - CODING Rule
2
+
3
+ ## 📋 Overview
4
+
5
+ **Rule ID**: `C002_no_duplicate_code`
6
+ **Category**: coding
7
+ **Severity**: Error
8
+ **Status**: pending
9
+
10
+ ## 🎯 Description
11
+
12
+ TODO: Add rule description after migration from ESLint.
13
+
14
+
15
+ ## 🔄 Migration Info
16
+
17
+ **ESLint Rule**: `c002-no-duplicate-code`
18
+ **Compatibility**: partial
19
+ **Priority**: medium
20
+
21
+
22
+ ## ✅ Valid Code Examples
23
+
24
+ ```javascript
25
+ // TODO: Add valid code examples
26
+ ```
27
+
28
+ ## ❌ Invalid Code Examples
29
+
30
+ ```javascript
31
+ // TODO: Add invalid code examples that should trigger violations
32
+ ```
33
+
34
+ ## ⚙️ Configuration
35
+
36
+ ```json
37
+ {
38
+ "rules": {
39
+ "C002_no_duplicate_code": "error"
40
+ }
41
+ }
42
+ ```
43
+
44
+ ## 🧪 Testing
45
+
46
+ ```bash
47
+ # Run rule-specific tests
48
+ npm test -- c002_no_duplicate_code
49
+
50
+ # Test with SunLint CLI
51
+ sunlint --rules=C002_no_duplicate_code --input=examples/
52
+ ```
53
+
54
+ ---
55
+
56
+ **Migration Status**: pending
57
+ **Last Updated**: 2025-07-21
@@ -0,0 +1,72 @@
1
+ # Rule C031 - Validation Logic Separation
2
+
3
+ ## Description
4
+ Logic kiểm tra dữ liệu (validate) phải nằm riêng biệt khỏi business logic.
5
+
6
+ ## Rationale
7
+ Tách biệt validation logic giúp:
8
+ - Code dễ đọc và maintain
9
+ - Validation có thể reuse
10
+ - Testing dễ dàng hơn
11
+ - Tuân thủ Single Responsibility Principle
12
+
13
+ ## Examples
14
+
15
+ ### ❌ Bad - Validation mixed with business logic
16
+ ```javascript
17
+ function processOrder(order) {
18
+ // Validation mixed with business logic
19
+ if (!order.customerId) {
20
+ throw new Error('Customer ID is required');
21
+ }
22
+ if (!order.items || order.items.length === 0) {
23
+ throw new Error('Order must have items');
24
+ }
25
+ if (order.total < 0) {
26
+ throw new Error('Total cannot be negative');
27
+ }
28
+
29
+ // Business logic
30
+ const discount = calculateDiscount(order);
31
+ const tax = calculateTax(order);
32
+ return processPayment(order, discount, tax);
33
+ }
34
+ ```
35
+
36
+ ### ✅ Good - Separate validation
37
+ ```javascript
38
+ function validateOrder(order) {
39
+ if (!order.customerId) {
40
+ throw new Error('Customer ID is required');
41
+ }
42
+ if (!order.items || order.items.length === 0) {
43
+ throw new Error('Order must have items');
44
+ }
45
+ if (order.total < 0) {
46
+ throw new Error('Total cannot be negative');
47
+ }
48
+ }
49
+
50
+ function processOrder(order) {
51
+ validateOrder(order);
52
+
53
+ // Pure business logic
54
+ const discount = calculateDiscount(order);
55
+ const tax = calculateTax(order);
56
+ return processPayment(order, discount, tax);
57
+ }
58
+ ```
59
+
60
+ ## Configuration
61
+ ```json
62
+ {
63
+ "C031": {
64
+ "enabled": true,
65
+ "severity": "warning",
66
+ "options": {
67
+ "maxValidationStatementsInFunction": 3,
68
+ "requireSeparateValidationFunction": true
69
+ }
70
+ }
71
+ }
72
+ ```
package/rules/index.js ADDED
@@ -0,0 +1,155 @@
1
+ /**
2
+ * SunLint Heuristic Rules Registry
3
+ * Central reconst securityRules = {
4
+ S015: require('./security/S015_insecure_tls_certificate/analyzer'),
5
+ S023: require('./security/S023_no_json_injection/analyzer'),stry for all heuristic rules organized by category
6
+ */
7
+
8
+ const path = require('path');
9
+
10
+ /**
11
+ * Load rule analyzer from category folder
12
+ * @param {string} category - Rule category (common, security, typescript)
13
+ * @param {string} ruleId - Rule ID (e.g., C006_function_naming)
14
+ * @returns {Object} Rule analyzer module
15
+ */
16
+ function loadRule(category, ruleId) {
17
+ try {
18
+ const rulePath = path.join(__dirname, category, ruleId, 'analyzer.js');
19
+ return require(rulePath);
20
+ } catch (error) {
21
+ console.warn(`Failed to load rule ${category}/${ruleId}:`, error.message);
22
+ return null;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Load rule configuration
28
+ * @param {string} category - Rule category
29
+ * @param {string} ruleId - Rule ID
30
+ * @returns {Object} Rule configuration
31
+ */
32
+ function loadRuleConfig(category, ruleId) {
33
+ try {
34
+ const configPath = path.join(__dirname, category, ruleId, 'config.json');
35
+ return require(configPath);
36
+ } catch (error) {
37
+ console.warn(`Failed to load config for ${category}/${ruleId}:`, error.message);
38
+ return {};
39
+ }
40
+ }
41
+
42
+ // 🔹 Common Rules (C-series) - General coding standards
43
+ const commonRules = {
44
+ C006: loadRule('common', 'C006_function_naming'),
45
+ C012: loadRule('common', 'C012_command_query_separation'),
46
+ C013: loadRule('common', 'C013_no_dead_code'),
47
+ C014: loadRule('common', 'C014_dependency_injection'),
48
+ C019: loadRule('common', 'C019_log_level_usage'),
49
+ C029: loadRule('common', 'C029_catch_block_logging'),
50
+ C031: loadRule('common', 'C031_validation_separation'),
51
+ C041: loadRule('common', 'C041_no_sensitive_hardcode'),
52
+ C042: loadRule('common', 'C042_boolean_name_prefix'),
53
+ C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
54
+ };
55
+
56
+ // 🔒 Security Rules (S-series) - Ready for migration
57
+ const securityRules = {
58
+ S015: loadRule('security', 'S015_insecure_tls_certificate'),
59
+ S023: loadRule('security', 'S023_no_json_injection'),
60
+ S026: loadRule('security', 'S026_json_schema_validation'),
61
+ S027: loadRule('security', 'S027_no_hardcoded_secrets'),
62
+ S029: loadRule('security', 'S029_csrf_protection'),
63
+ // S001: loadRule('security', 'S001_fail_securely'),
64
+ // S003: loadRule('security', 'S003_no_unvalidated_redirect'),
65
+ // S012: loadRule('security', 'S012_hardcode_secret'),
66
+ // ... 46 more security rules ready for migration
67
+ };
68
+
69
+ // 📘 TypeScript Rules (T-series) - Ready for migration
70
+ const typescriptRules = {
71
+ // T002: loadRule('typescript', 'T002_interface_prefix_i'),
72
+ // T003: loadRule('typescript', 'T003_ts_ignore_reason'),
73
+ // T004: loadRule('typescript', 'T004_interface_public_only'),
74
+ // ... 7 more TypeScript rules ready for migration
75
+ };
76
+
77
+ /**
78
+ * Get all available rules by category
79
+ * @returns {Object} Organized rules object
80
+ */
81
+ function getAllRules() {
82
+ return {
83
+ common: commonRules,
84
+ security: securityRules,
85
+ typescript: typescriptRules
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Get rule by ID (searches all categories)
91
+ * @param {string} ruleId - Rule ID (e.g., 'C006', 'S001', 'T002')
92
+ * @returns {Object|null} Rule analyzer or null if not found
93
+ */
94
+ function getRuleById(ruleId) {
95
+ // Check all categories for the rule
96
+ if (commonRules[ruleId]) return commonRules[ruleId];
97
+ if (securityRules[ruleId]) return securityRules[ruleId];
98
+ if (typescriptRules[ruleId]) return typescriptRules[ruleId];
99
+
100
+ return null;
101
+ }
102
+
103
+ /**
104
+ * Get active rule count by category
105
+ * @returns {Object} Rule counts
106
+ */
107
+ function getRuleCounts() {
108
+ const counts = {
109
+ common: Object.keys(commonRules).filter(id => commonRules[id]).length,
110
+ security: Object.keys(securityRules).filter(id => securityRules[id]).length,
111
+ typescript: Object.keys(typescriptRules).filter(id => typescriptRules[id]).length,
112
+ };
113
+
114
+ counts.total = counts.common + counts.security + counts.typescript;
115
+ return counts;
116
+ }
117
+
118
+ /**
119
+ * List all available rules with metadata
120
+ * @returns {Array} Array of rule information
121
+ */
122
+ function listRules() {
123
+ const rules = [];
124
+ const allRules = getAllRules();
125
+
126
+ for (const category in allRules) {
127
+ for (const ruleId in allRules[category]) {
128
+ if (allRules[category][ruleId]) {
129
+ const config = loadRuleConfig(category, ruleId);
130
+ rules.push({
131
+ id: ruleId,
132
+ category,
133
+ name: config.name || ruleId,
134
+ description: config.description || 'No description',
135
+ status: 'active'
136
+ });
137
+ }
138
+ }
139
+ }
140
+
141
+ return rules;
142
+ }
143
+
144
+ module.exports = {
145
+ // Main exports
146
+ getAllRules,
147
+ getRuleById,
148
+ getRuleCounts,
149
+ listRules,
150
+
151
+ // Category exports
152
+ common: commonRules,
153
+ security: securityRules,
154
+ typescript: typescriptRules
155
+ };