@sun-asterisk/sunlint 1.1.7 → 1.2.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.
Files changed (74) hide show
  1. package/.sunlint.json +1 -1
  2. package/CHANGELOG.md +83 -0
  3. package/README.md +66 -4
  4. package/config/presets/all.json +125 -0
  5. package/config/presets/beginner.json +16 -8
  6. package/config/presets/ci.json +12 -4
  7. package/config/presets/maintainability.json +38 -0
  8. package/config/presets/performance.json +32 -0
  9. package/config/presets/quality.json +103 -0
  10. package/config/presets/recommended.json +36 -12
  11. package/config/presets/security.json +88 -0
  12. package/config/presets/strict.json +15 -5
  13. package/config/rules/rules-registry-generated.json +6312 -0
  14. package/config/rules-summary.json +1941 -0
  15. package/core/adapters/sunlint-rule-adapter.js +452 -0
  16. package/core/analysis-orchestrator.js +4 -4
  17. package/core/config-manager.js +28 -5
  18. package/core/rule-selection-service.js +52 -55
  19. package/docs/CONFIGURATION.md +111 -3
  20. package/docs/LANGUAGE-SPECIFIC-RULES.md +308 -0
  21. package/docs/README.md +3 -0
  22. package/docs/STANDARDIZED-CATEGORY-FILTERING.md +156 -0
  23. package/engines/eslint-engine.js +92 -2
  24. package/engines/heuristic-engine.js +8 -31
  25. package/origin-rules/common-en.md +1320 -0
  26. package/origin-rules/dart-en.md +289 -0
  27. package/origin-rules/java-en.md +60 -0
  28. package/origin-rules/kotlin-mobile-en.md +453 -0
  29. package/origin-rules/reactjs-en.md +102 -0
  30. package/origin-rules/security-en.md +1055 -0
  31. package/origin-rules/swift-en.md +449 -0
  32. package/origin-rules/typescript-en.md +136 -0
  33. package/package.json +6 -5
  34. package/scripts/copy-rules.js +86 -0
  35. package/rules/README.md +0 -252
  36. package/rules/common/C002_no_duplicate_code/analyzer.js +0 -65
  37. package/rules/common/C002_no_duplicate_code/config.json +0 -23
  38. package/rules/common/C003_no_vague_abbreviations/analyzer.js +0 -418
  39. package/rules/common/C003_no_vague_abbreviations/config.json +0 -35
  40. package/rules/common/C006_function_naming/analyzer.js +0 -349
  41. package/rules/common/C006_function_naming/config.json +0 -86
  42. package/rules/common/C010_limit_block_nesting/analyzer.js +0 -389
  43. package/rules/common/C013_no_dead_code/analyzer.js +0 -206
  44. package/rules/common/C014_dependency_injection/analyzer.js +0 -338
  45. package/rules/common/C017_constructor_logic/analyzer.js +0 -314
  46. package/rules/common/C019_log_level_usage/analyzer.js +0 -362
  47. package/rules/common/C019_log_level_usage/config.json +0 -121
  48. package/rules/common/C029_catch_block_logging/analyzer.js +0 -373
  49. package/rules/common/C029_catch_block_logging/config.json +0 -59
  50. package/rules/common/C031_validation_separation/analyzer.js +0 -186
  51. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +0 -292
  52. package/rules/common/C042_boolean_name_prefix/analyzer.js +0 -300
  53. package/rules/common/C043_no_console_or_print/analyzer.js +0 -304
  54. package/rules/common/C047_no_duplicate_retry_logic/analyzer.js +0 -351
  55. package/rules/common/C075_explicit_return_types/analyzer.js +0 -103
  56. package/rules/common/C076_single_test_behavior/analyzer.js +0 -121
  57. package/rules/docs/C002_no_duplicate_code.md +0 -57
  58. package/rules/docs/C031_validation_separation.md +0 -72
  59. package/rules/index.js +0 -149
  60. package/rules/migration/converter.js +0 -385
  61. package/rules/migration/mapping.json +0 -164
  62. package/rules/security/S026_json_schema_validation/analyzer.js +0 -251
  63. package/rules/security/S026_json_schema_validation/config.json +0 -27
  64. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +0 -263
  65. package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
  66. package/rules/security/S029_csrf_protection/analyzer.js +0 -264
  67. package/rules/tests/C002_no_duplicate_code.test.js +0 -50
  68. package/rules/universal/C010/generic.js +0 -0
  69. package/rules/universal/C010/tree-sitter-analyzer.js +0 -0
  70. package/rules/utils/ast-utils.js +0 -191
  71. package/rules/utils/base-analyzer.js +0 -98
  72. package/rules/utils/pattern-matchers.js +0 -239
  73. package/rules/utils/rule-helpers.js +0 -264
  74. package/rules/utils/severity-constants.js +0 -93
@@ -1,103 +0,0 @@
1
- /**
2
- * C075 Rule: Functions must have explicit return type declarations
3
- * Ensures type safety by requiring explicit return type annotations
4
- * Severity: warning
5
- * Category: Quality
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- class C075ExplicitReturnTypesAnalyzer {
12
- constructor() {
13
- this.ruleId = 'C075';
14
- this.ruleName = 'Explicit Function Return Types';
15
- this.description = 'Functions must have explicit return type declarations';
16
- this.severity = 'warning';
17
- }
18
-
19
- async analyze(files, language, config) {
20
- const violations = [];
21
-
22
- for (const filePath of files) {
23
- try {
24
- const fileContent = fs.readFileSync(filePath, 'utf8');
25
- const fileViolations = await this.analyzeFile(filePath, fileContent, language, config);
26
- violations.push(...fileViolations);
27
- } catch (error) {
28
- console.warn(`C075 analysis error for ${filePath}:`, error.message);
29
- }
30
- }
31
-
32
- return violations;
33
- }
34
-
35
- async analyzeFile(filePath, fileContent, language, config) {
36
- const violations = [];
37
-
38
- try {
39
- // Skip non-TypeScript files
40
- if (!this.isTypeScriptFile(filePath)) {
41
- return violations;
42
- }
43
-
44
- // Simple regex-based analysis for now
45
- const lines = fileContent.split('\n');
46
-
47
- for (let i = 0; i < lines.length; i++) {
48
- const line = lines[i];
49
- const lineNumber = i + 1;
50
-
51
- // Look for function declarations without return types
52
- const functionPatterns = [
53
- /^(\s*)(function\s+\w+\s*\([^)]*\))\s*\{/, // function name() {
54
- /^(\s*)(export\s+function\s+\w+\s*\([^)]*\))\s*\{/, // export function name() {
55
- /^(\s*)(\w+\s*=\s*function\s*\([^)]*\))\s*\{/, // name = function() {
56
- /^(\s*)(\w+\s*=\s*\([^)]*\)\s*=>\s*)\{/, // name = () => {
57
- /^(\s*)(\w+\([^)]*\))\s*\{/, // method() {
58
- ];
59
-
60
- for (const pattern of functionPatterns) {
61
- const match = line.match(pattern);
62
- if (match) {
63
- const fullMatch = match[2];
64
-
65
- // Skip if already has return type annotation
66
- if (fullMatch.includes('):') || line.includes('):')) {
67
- continue;
68
- }
69
-
70
- // Skip constructors
71
- if (fullMatch.includes('constructor')) {
72
- continue;
73
- }
74
-
75
- violations.push({
76
- ruleId: this.ruleId,
77
- severity: this.severity,
78
- message: `Function is missing explicit return type annotation`,
79
- filePath: filePath,
80
- line: lineNumber,
81
- column: match[1].length + 1,
82
- source: line.trim(),
83
- suggestion: 'Add explicit return type annotation (: ReturnType)'
84
- });
85
- }
86
- }
87
- }
88
-
89
- } catch (error) {
90
- console.warn(`C075 analysis error for ${filePath}:`, error.message);
91
- }
92
-
93
- return violations;
94
- }
95
-
96
- isTypeScriptFile(filePath) {
97
- return /\.(ts|tsx)$/.test(filePath);
98
- }
99
- }
100
-
101
- module.exports = C075ExplicitReturnTypesAnalyzer;
102
-
103
- module.exports = C075ExplicitReturnTypesAnalyzer;
@@ -1,121 +0,0 @@
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;
@@ -1,57 +0,0 @@
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
@@ -1,72 +0,0 @@
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 DELETED
@@ -1,149 +0,0 @@
1
- /**
2
- * SunLint Heuristic Rules Registry
3
- * Central registry for all heuristic rules organized by category
4
- */
5
-
6
- const path = require('path');
7
-
8
- /**
9
- * Load rule analyzer from category folder
10
- * @param {string} category - Rule category (common, security, typescript)
11
- * @param {string} ruleId - Rule ID (e.g., C006_function_naming)
12
- * @returns {Object} Rule analyzer module
13
- */
14
- function loadRule(category, ruleId) {
15
- try {
16
- const rulePath = path.join(__dirname, category, ruleId, 'analyzer.js');
17
- return require(rulePath);
18
- } catch (error) {
19
- console.warn(`Failed to load rule ${category}/${ruleId}:`, error.message);
20
- return null;
21
- }
22
- }
23
-
24
- /**
25
- * Load rule configuration
26
- * @param {string} category - Rule category
27
- * @param {string} ruleId - Rule ID
28
- * @returns {Object} Rule configuration
29
- */
30
- function loadRuleConfig(category, ruleId) {
31
- try {
32
- const configPath = path.join(__dirname, category, ruleId, 'config.json');
33
- return require(configPath);
34
- } catch (error) {
35
- console.warn(`Failed to load config for ${category}/${ruleId}:`, error.message);
36
- return {};
37
- }
38
- }
39
-
40
- // 🔹 Common Rules (C-series) - General coding standards
41
- const commonRules = {
42
- C006: loadRule('common', 'C006_function_naming'),
43
- C013: loadRule('common', 'C013_no_dead_code'),
44
- C014: loadRule('common', 'C014_dependency_injection'),
45
- C019: loadRule('common', 'C019_log_level_usage'),
46
- C029: loadRule('common', 'C029_catch_block_logging'),
47
- C031: loadRule('common', 'C031_validation_separation'),
48
- C041: loadRule('common', 'C041_no_sensitive_hardcode'),
49
- C042: loadRule('common', 'C042_boolean_name_prefix'),
50
- C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
51
- };
52
-
53
- // 🔒 Security Rules (S-series) - Ready for migration
54
- const securityRules = {
55
- S026: loadRule('security', 'S026_json_schema_validation'),
56
- S027: loadRule('security', 'S027_no_hardcoded_secrets'),
57
- // S001: loadRule('security', 'S001_fail_securely'),
58
- // S003: loadRule('security', 'S003_no_unvalidated_redirect'),
59
- // S012: loadRule('security', 'S012_hardcode_secret'),
60
- // ... 46 more security rules ready for migration
61
- };
62
-
63
- // 📘 TypeScript Rules (T-series) - Ready for migration
64
- const typescriptRules = {
65
- // T002: loadRule('typescript', 'T002_interface_prefix_i'),
66
- // T003: loadRule('typescript', 'T003_ts_ignore_reason'),
67
- // T004: loadRule('typescript', 'T004_interface_public_only'),
68
- // ... 7 more TypeScript rules ready for migration
69
- };
70
-
71
- /**
72
- * Get all available rules by category
73
- * @returns {Object} Organized rules object
74
- */
75
- function getAllRules() {
76
- return {
77
- common: commonRules,
78
- security: securityRules,
79
- typescript: typescriptRules
80
- };
81
- }
82
-
83
- /**
84
- * Get rule by ID (searches all categories)
85
- * @param {string} ruleId - Rule ID (e.g., 'C006', 'S001', 'T002')
86
- * @returns {Object|null} Rule analyzer or null if not found
87
- */
88
- function getRuleById(ruleId) {
89
- // Check all categories for the rule
90
- if (commonRules[ruleId]) return commonRules[ruleId];
91
- if (securityRules[ruleId]) return securityRules[ruleId];
92
- if (typescriptRules[ruleId]) return typescriptRules[ruleId];
93
-
94
- return null;
95
- }
96
-
97
- /**
98
- * Get active rule count by category
99
- * @returns {Object} Rule counts
100
- */
101
- function getRuleCounts() {
102
- const counts = {
103
- common: Object.keys(commonRules).filter(id => commonRules[id]).length,
104
- security: Object.keys(securityRules).filter(id => securityRules[id]).length,
105
- typescript: Object.keys(typescriptRules).filter(id => typescriptRules[id]).length,
106
- };
107
-
108
- counts.total = counts.common + counts.security + counts.typescript;
109
- return counts;
110
- }
111
-
112
- /**
113
- * List all available rules with metadata
114
- * @returns {Array} Array of rule information
115
- */
116
- function listRules() {
117
- const rules = [];
118
- const allRules = getAllRules();
119
-
120
- for (const category in allRules) {
121
- for (const ruleId in allRules[category]) {
122
- if (allRules[category][ruleId]) {
123
- const config = loadRuleConfig(category, ruleId);
124
- rules.push({
125
- id: ruleId,
126
- category,
127
- name: config.name || ruleId,
128
- description: config.description || 'No description',
129
- status: 'active'
130
- });
131
- }
132
- }
133
- }
134
-
135
- return rules;
136
- }
137
-
138
- module.exports = {
139
- // Main exports
140
- getAllRules,
141
- getRuleById,
142
- getRuleCounts,
143
- listRules,
144
-
145
- // Category exports
146
- common: commonRules,
147
- security: securityRules,
148
- typescript: typescriptRules
149
- };