@sun-asterisk/sunlint 1.3.16 โ†’ 1.3.17

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 (50) hide show
  1. package/config/rule-analysis-strategies.js +3 -3
  2. package/config/rules/enhanced-rules-registry.json +40 -20
  3. package/core/cli-action-handler.js +2 -2
  4. package/core/config-merger.js +28 -6
  5. package/core/constants/defaults.js +1 -1
  6. package/core/file-targeting-service.js +72 -4
  7. package/core/output-service.js +21 -4
  8. package/engines/heuristic-engine.js +5 -0
  9. package/package.json +1 -1
  10. package/rules/common/C002_no_duplicate_code/README.md +115 -0
  11. package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
  12. package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
  13. package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
  14. package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
  15. package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
  16. package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
  17. package/rules/common/C008/analyzer.js +40 -0
  18. package/rules/common/C008/config.json +20 -0
  19. package/rules/common/C008/ts-morph-analyzer.js +1067 -0
  20. package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
  21. package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
  22. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
  23. package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
  24. package/rules/common/C033_separate_service_repository/README.md +131 -20
  25. package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
  26. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
  27. package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
  28. package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
  29. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
  30. package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
  31. package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
  32. package/rules/docs/C002_no_duplicate_code.md +276 -11
  33. package/rules/index.js +5 -1
  34. package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
  35. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
  36. package/rules/security/S010_no_insecure_encryption/README.md +78 -0
  37. package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
  38. package/rules/security/S013_tls_enforcement/README.md +51 -0
  39. package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
  40. package/rules/security/S013_tls_enforcement/config.json +41 -0
  41. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
  42. package/rules/security/S014_tls_version_enforcement/README.md +354 -0
  43. package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
  44. package/rules/security/S014_tls_version_enforcement/config.json +56 -0
  45. package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
  46. package/rules/security/S055_content_type_validation/analyzer.js +121 -279
  47. package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
  48. package/rules/tests/C002_no_duplicate_code.test.js +111 -22
  49. package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
  50. package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
@@ -1,3 +1,4 @@
1
+ ````markdown
1
2
  # C002_no_duplicate_code - CODING Rule
2
3
 
3
4
  ## ๐Ÿ“‹ Overview
@@ -5,42 +6,289 @@
5
6
  **Rule ID**: `C002_no_duplicate_code`
6
7
  **Category**: coding
7
8
  **Severity**: Error
8
- **Status**: pending
9
+ **Status**: active
9
10
 
10
11
  ## ๐ŸŽฏ Description
11
12
 
12
- TODO: Add rule description after migration from ESLint.
13
+ Detects and warns about duplicate code blocks of 10 or more lines across functions and classes. This rule enforces the **DRY (Don't Repeat Yourself)** principle to maintain clean, refactorable code and reduce maintenance burden.
13
14
 
15
+ **Key Objectives:**
16
+ - ๐ŸŽฏ Avoid messy, hard-to-refactor code
17
+ - โ™ป๏ธ Apply DRY principle consistently
18
+ - ๐Ÿ”ง Improve code maintainability
19
+ - ๐Ÿงช Enhance testability
20
+
21
+ **Detection Criteria:**
22
+ - Warns when duplicate code โ‰ฅ 10 lines found in functions/classes
23
+ - Ignores comments and whitespace variations
24
+ - Uses similarity threshold (85%) to detect near-duplicates
25
+ - Cross-file duplicate detection
14
26
 
15
27
  ## ๐Ÿ”„ Migration Info
16
28
 
17
29
  **ESLint Rule**: `c002-no-duplicate-code`
18
- **Compatibility**: partial
19
- **Priority**: medium
30
+ **Compatibility**: Full
31
+ **Priority**: High
20
32
 
21
33
 
22
34
  ## โœ… Valid Code Examples
23
35
 
24
- ```javascript
25
- // TODO: Add valid code examples
36
+ ### Example 1: Extract Common Logic into Utility Functions
37
+
38
+ ```typescript
39
+ // โœ… GOOD: Extracted validation logic into reusable functions
40
+ function validateEmail(email: string): void {
41
+ if (!email || !email.includes('@')) {
42
+ throw new ValidationError('Invalid email format');
43
+ }
44
+ }
45
+
46
+ function validatePassword(password: string): void {
47
+ if (!password || password.length < 8) {
48
+ throw new ValidationError('Password must be at least 8 characters');
49
+ }
50
+ }
51
+
52
+ function registerUser(userData: UserData): void {
53
+ validateEmail(userData.email);
54
+ validatePassword(userData.password);
55
+ // Registration logic
56
+ }
57
+
58
+ function updateUserProfile(userId: string, profileData: ProfileData): void {
59
+ if (profileData.email) {
60
+ validateEmail(profileData.email);
61
+ }
62
+ if (profileData.password) {
63
+ validatePassword(profileData.password);
64
+ }
65
+ // Update logic
66
+ }
67
+ ```
68
+
69
+ ### Example 2: Using Inheritance for Shared Behavior
70
+
71
+ ```typescript
72
+ // โœ… GOOD: Base class with common logic
73
+ abstract class BaseRepository<T> {
74
+ protected async findById(id: string): Promise<T | null> {
75
+ const result = await this.db.query(
76
+ `SELECT * FROM ${this.tableName} WHERE id = ?`,
77
+ [id]
78
+ );
79
+ return result.length > 0 ? this.mapToEntity(result[0]) : null;
80
+ }
81
+
82
+ protected async save(entity: T): Promise<void> {
83
+ const data = this.mapToData(entity);
84
+ await this.db.query(
85
+ `INSERT INTO ${this.tableName} VALUES (?, ?, ?)`,
86
+ Object.values(data)
87
+ );
88
+ }
89
+
90
+ protected abstract mapToEntity(row: any): T;
91
+ protected abstract mapToData(entity: T): any;
92
+ protected abstract tableName: string;
93
+ }
94
+
95
+ class UserRepository extends BaseRepository<User> {
96
+ protected tableName = 'users';
97
+ protected mapToEntity(row: any): User { /* ... */ }
98
+ protected mapToData(entity: User): any { /* ... */ }
99
+ }
100
+
101
+ class ProductRepository extends BaseRepository<Product> {
102
+ protected tableName = 'products';
103
+ protected mapToEntity(row: any): Product { /* ... */ }
104
+ protected mapToData(entity: Product): any { /* ... */ }
105
+ }
106
+ ```
107
+
108
+ ### Example 3: Composition for Shared Logic
109
+
110
+ ```typescript
111
+ // โœ… GOOD: Shared service for common operations
112
+ class ErrorLogger {
113
+ logError(context: string, error: Error): void {
114
+ console.error(`[${context}] ${error.message}`);
115
+ this.sendToMonitoring(error);
116
+ }
117
+
118
+ private sendToMonitoring(error: Error): void {
119
+ // Send to monitoring service
120
+ }
121
+ }
122
+
123
+ class UserService {
124
+ constructor(private errorLogger: ErrorLogger) {}
125
+
126
+ async getUser(id: string): Promise<User> {
127
+ try {
128
+ return await this.userRepo.findById(id);
129
+ } catch (error) {
130
+ this.errorLogger.logError('UserService.getUser', error);
131
+ throw error;
132
+ }
133
+ }
134
+ }
135
+
136
+ class OrderService {
137
+ constructor(private errorLogger: ErrorLogger) {}
138
+
139
+ async getOrder(id: string): Promise<Order> {
140
+ try {
141
+ return await this.orderRepo.findById(id);
142
+ } catch (error) {
143
+ this.errorLogger.logError('OrderService.getOrder', error);
144
+ throw error;
145
+ }
146
+ }
147
+ }
26
148
  ```
27
149
 
28
150
  ## โŒ Invalid Code Examples
29
151
 
30
- ```javascript
31
- // TODO: Add invalid code examples that should trigger violations
152
+ ### Example 1: Duplicated Validation Logic
153
+
154
+ ```typescript
155
+ // โŒ BAD: Duplicated validation in multiple functions
156
+ function registerUser(userData: UserData): void {
157
+ // Duplicated validation logic
158
+ if (!userData.email || !userData.email.includes('@')) {
159
+ throw new ValidationError('Invalid email format');
160
+ }
161
+ if (!userData.password || userData.password.length < 8) {
162
+ throw new ValidationError('Password must be at least 8 characters');
163
+ }
164
+ // Registration logic
165
+ }
166
+
167
+ function updateUserProfile(userId: string, profileData: ProfileData): void {
168
+ // Same validation logic duplicated here
169
+ if (profileData.email && (!profileData.email || !profileData.email.includes('@'))) {
170
+ throw new ValidationError('Invalid email format');
171
+ }
172
+ if (profileData.password && (!profileData.password || profileData.password.length < 8)) {
173
+ throw new ValidationError('Password must be at least 8 characters');
174
+ }
175
+ // Update logic
176
+ }
177
+ ```
178
+
179
+ **Violation Message:**
32
180
  ```
181
+ Duplicate function detected (12 non-comment lines). Extract into a shared utility
182
+ module or helper file. Found in 2 locations: registerUser.ts:5-16, updateUserProfile.ts:10-21
183
+ ```
184
+
185
+ ### Example 2: Duplicated Repository Logic
186
+
187
+ ```typescript
188
+ // โŒ BAD: Same database logic repeated in multiple repositories
189
+ class UserRepository {
190
+ async findById(id: string): Promise<User | null> {
191
+ const result = await this.db.query(
192
+ 'SELECT * FROM users WHERE id = ?',
193
+ [id]
194
+ );
195
+ if (result.length === 0) return null;
196
+ return {
197
+ id: result[0].id,
198
+ name: result[0].name,
199
+ email: result[0].email
200
+ };
201
+ }
202
+ }
203
+
204
+ class ProductRepository {
205
+ async findById(id: string): Promise<Product | null> {
206
+ const result = await this.db.query(
207
+ 'SELECT * FROM products WHERE id = ?',
208
+ [id]
209
+ );
210
+ if (result.length === 0) return null;
211
+ return {
212
+ id: result[0].id,
213
+ name: result[0].name,
214
+ price: result[0].price
215
+ };
216
+ }
217
+ }
218
+ ```
219
+
220
+ **Suggested Fix:**
221
+ - Use inheritance to share common behavior
222
+ - Extract shared logic into a base class or mixin
33
223
 
34
224
  ## โš™๏ธ Configuration
35
225
 
36
226
  ```json
37
227
  {
38
228
  "rules": {
39
- "C002_no_duplicate_code": "error"
229
+ "C002_no_duplicate_code": ["error", {
230
+ "minLines": 10,
231
+ "similarityThreshold": 0.85,
232
+ "ignoreComments": true,
233
+ "ignoreWhitespace": true,
234
+ "ignoreEmptyLines": true
235
+ }]
40
236
  }
41
237
  }
42
238
  ```
43
239
 
240
+ **Configuration Options:**
241
+ - `minLines` (default: 10): Minimum number of lines to consider as duplicate
242
+ - `similarityThreshold` (default: 0.85): Similarity percentage (0-1) to detect near-duplicates
243
+ - `ignoreComments` (default: true): Ignore comments when comparing code
244
+ - `ignoreWhitespace` (default: true): Ignore whitespace differences
245
+ - `ignoreEmptyLines` (default: true): Ignore empty lines
246
+
247
+ ## ๐Ÿ”ง Refactoring Strategies
248
+
249
+ ### 1. Extract Functions/Utilities
250
+ When you have duplicated logic in multiple functions:
251
+ ```typescript
252
+ // Extract common logic into a utility function
253
+ const validateUserInput = (email: string, password: string) => {
254
+ validateEmail(email);
255
+ validatePassword(password);
256
+ };
257
+ ```
258
+
259
+ ### 2. Use Inheritance
260
+ When you have duplicated logic in multiple classes with similar responsibilities:
261
+ ```typescript
262
+ // Create a base class with common behavior
263
+ abstract class BaseService {
264
+ protected abstract entityName: string;
265
+
266
+ protected async findById(id: string) {
267
+ // Common implementation
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### 3. Use Composition
273
+ When inheritance doesn't fit or you need more flexibility:
274
+ ```typescript
275
+ // Create a shared service that can be injected
276
+ class CommonOperations {
277
+ logError(context: string, error: Error) { /* ... */ }
278
+ validateInput(data: any) { /* ... */ }
279
+ }
280
+ ```
281
+
282
+ ### 4. Create Shared Libraries
283
+ For cross-cutting concerns used across multiple modules:
284
+ ```typescript
285
+ // utils/validation.ts
286
+ export const validators = {
287
+ email: (email: string) => { /* ... */ },
288
+ password: (password: string) => { /* ... */ }
289
+ };
290
+ ```
291
+
44
292
  ## ๐Ÿงช Testing
45
293
 
46
294
  ```bash
@@ -49,9 +297,26 @@ npm test -- c002_no_duplicate_code
49
297
 
50
298
  # Test with SunLint CLI
51
299
  sunlint --rules=C002_no_duplicate_code --input=examples/
300
+
301
+ # Analyze specific files
302
+ sunlint analyze --rules=C002 src/**/*.ts
52
303
  ```
53
304
 
305
+ ## ๐Ÿ“š Related Rules
306
+
307
+ - **C005**: Each function should do one thing (Single Responsibility)
308
+ - **C047**: Retry logic must not be duplicated
309
+ - **C014**: Use Dependency Injection instead of direct instantiation
310
+
311
+ ## ๐Ÿ”— References
312
+
313
+ - [DRY Principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself)
314
+ - [Code Duplication (Martin Fowler)](https://refactoring.guru/smells/duplicate-code)
315
+ - [SonarQube: Duplicated Blocks](https://rules.sonarsource.com/java/RSPEC-1192)
316
+
54
317
  ---
55
318
 
56
- **Migration Status**: pending
57
- **Last Updated**: 2025-07-21
319
+ **Migration Status**: active
320
+ **Last Updated**: 2025-10-16
321
+
322
+ ````
package/rules/index.js CHANGED
@@ -48,6 +48,7 @@ function loadRuleConfig(category, ruleId) {
48
48
 
49
49
  // ๐Ÿ”น Common Rules (C-series) - General coding standards
50
50
  const commonRules = {
51
+ C002: loadRule('common', 'C002_no_duplicate_code'),
51
52
  C006: loadRule('common', 'C006_function_naming'),
52
53
  C012: loadRule('common', 'C012_command_query_separation'),
53
54
  C013: loadRule('common', 'C013_no_dead_code'),
@@ -59,17 +60,20 @@ const commonRules = {
59
60
  C024: loadRule('common', 'C024_no_scatter_hardcoded_constants'),
60
61
  C029: loadRule('common', 'C029_catch_block_logging'),
61
62
  C031: loadRule('common', 'C031_validation_separation'),
63
+ C033: loadRule('common', 'C033_separate_service_repository'),
62
64
  C041: loadRule('common', 'C041_no_sensitive_hardcode'),
63
65
  C042: loadRule('common', 'C042_boolean_name_prefix'),
66
+ // C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
64
67
  C048: loadRule('common', 'C048_no_bypass_architectural_layers'),
65
68
  C052: loadRule('common', 'C052_parsing_or_data_transformation'),
66
- C047: loadRule('common', 'C047_no_duplicate_retry_logic'),
67
69
  C060: loadRule('common', 'C060_no_override_superclass'),
70
+ C067: loadRule('common', 'C067_no_hardcoded_config'),
68
71
  };
69
72
 
70
73
  // ๐Ÿ”’ Security Rules (S-series) - Ready for migration
71
74
  const securityRules = {
72
75
  S006: loadRule('security', 'S006_no_plaintext_recovery_codes'),
76
+ S010: loadRule('security', 'S010_no_insecure_encryption'),
73
77
  S015: loadRule('security', 'S015_insecure_tls_certificate'),
74
78
  S023: loadRule('security', 'S023_no_json_injection'),
75
79
  S026: loadRule('security', 'S026_json_schema_validation'),