UNPKG

@sun-asterisk/sunlint

Version:

โ˜€๏ธ SunLint - Multi-language static analysis tool for code quality and security | Sun* Engineering Standards

323 lines (265 loc) โ€ข 8.61 kB
````markdown # C002_no_duplicate_code - CODING Rule ## ๐Ÿ“‹ Overview **Rule ID**: `C002_no_duplicate_code` **Category**: coding **Severity**: Error **Status**: active ## ๐ŸŽฏ Description 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. **Key Objectives:** - ๐ŸŽฏ Avoid messy, hard-to-refactor code - โ™ป๏ธ Apply DRY principle consistently - ๐Ÿ”ง Improve code maintainability - ๐Ÿงช Enhance testability **Detection Criteria:** - Warns when duplicate code โ‰ฅ 10 lines found in functions/classes - Ignores comments and whitespace variations - Uses similarity threshold (85%) to detect near-duplicates - Cross-file duplicate detection ## ๐Ÿ”„ Migration Info **ESLint Rule**: `c002-no-duplicate-code` **Compatibility**: Full **Priority**: High ## โœ… Valid Code Examples ### Example 1: Extract Common Logic into Utility Functions ```typescript // โœ… GOOD: Extracted validation logic into reusable functions function validateEmail(email: string): void { if (!email || !email.includes('@')) { throw new ValidationError('Invalid email format'); } } function validatePassword(password: string): void { if (!password || password.length < 8) { throw new ValidationError('Password must be at least 8 characters'); } } function registerUser(userData: UserData): void { validateEmail(userData.email); validatePassword(userData.password); // Registration logic } function updateUserProfile(userId: string, profileData: ProfileData): void { if (profileData.email) { validateEmail(profileData.email); } if (profileData.password) { validatePassword(profileData.password); } // Update logic } ``` ### Example 2: Using Inheritance for Shared Behavior ```typescript // โœ… GOOD: Base class with common logic abstract class BaseRepository<T> { protected async findById(id: string): Promise<T | null> { const result = await this.db.query( `SELECT * FROM ${this.tableName} WHERE id = ?`, [id] ); return result.length > 0 ? this.mapToEntity(result[0]) : null; } protected async save(entity: T): Promise<void> { const data = this.mapToData(entity); await this.db.query( `INSERT INTO ${this.tableName} VALUES (?, ?, ?)`, Object.values(data) ); } protected abstract mapToEntity(row: any): T; protected abstract mapToData(entity: T): any; protected abstract tableName: string; } class UserRepository extends BaseRepository<User> { protected tableName = 'users'; protected mapToEntity(row: any): User { /* ... */ } protected mapToData(entity: User): any { /* ... */ } } class ProductRepository extends BaseRepository<Product> { protected tableName = 'products'; protected mapToEntity(row: any): Product { /* ... */ } protected mapToData(entity: Product): any { /* ... */ } } ``` ### Example 3: Composition for Shared Logic ```typescript // โœ… GOOD: Shared service for common operations class ErrorLogger { logError(context: string, error: Error): void { console.error(`[${context}] ${error.message}`); this.sendToMonitoring(error); } private sendToMonitoring(error: Error): void { // Send to monitoring service } } class UserService { constructor(private errorLogger: ErrorLogger) {} async getUser(id: string): Promise<User> { try { return await this.userRepo.findById(id); } catch (error) { this.errorLogger.logError('UserService.getUser', error); throw error; } } } class OrderService { constructor(private errorLogger: ErrorLogger) {} async getOrder(id: string): Promise<Order> { try { return await this.orderRepo.findById(id); } catch (error) { this.errorLogger.logError('OrderService.getOrder', error); throw error; } } } ``` ## โŒ Invalid Code Examples ### Example 1: Duplicated Validation Logic ```typescript // โŒ BAD: Duplicated validation in multiple functions function registerUser(userData: UserData): void { // Duplicated validation logic if (!userData.email || !userData.email.includes('@')) { throw new ValidationError('Invalid email format'); } if (!userData.password || userData.password.length < 8) { throw new ValidationError('Password must be at least 8 characters'); } // Registration logic } function updateUserProfile(userId: string, profileData: ProfileData): void { // Same validation logic duplicated here if (profileData.email && (!profileData.email || !profileData.email.includes('@'))) { throw new ValidationError('Invalid email format'); } if (profileData.password && (!profileData.password || profileData.password.length < 8)) { throw new ValidationError('Password must be at least 8 characters'); } // Update logic } ``` **Violation Message:** ``` Duplicate function detected (12 non-comment lines). Extract into a shared utility module or helper file. Found in 2 locations: registerUser.ts:5-16, updateUserProfile.ts:10-21 ``` ### Example 2: Duplicated Repository Logic ```typescript // โŒ BAD: Same database logic repeated in multiple repositories class UserRepository { async findById(id: string): Promise<User | null> { const result = await this.db.query( 'SELECT * FROM users WHERE id = ?', [id] ); if (result.length === 0) return null; return { id: result[0].id, name: result[0].name, email: result[0].email }; } } class ProductRepository { async findById(id: string): Promise<Product | null> { const result = await this.db.query( 'SELECT * FROM products WHERE id = ?', [id] ); if (result.length === 0) return null; return { id: result[0].id, name: result[0].name, price: result[0].price }; } } ``` **Suggested Fix:** - Use inheritance to share common behavior - Extract shared logic into a base class or mixin ## โš™๏ธ Configuration ```json { "rules": { "C002_no_duplicate_code": ["error", { "minLines": 10, "similarityThreshold": 0.85, "ignoreComments": true, "ignoreWhitespace": true, "ignoreEmptyLines": true }] } } ``` **Configuration Options:** - `minLines` (default: 10): Minimum number of lines to consider as duplicate - `similarityThreshold` (default: 0.85): Similarity percentage (0-1) to detect near-duplicates - `ignoreComments` (default: true): Ignore comments when comparing code - `ignoreWhitespace` (default: true): Ignore whitespace differences - `ignoreEmptyLines` (default: true): Ignore empty lines ## ๐Ÿ”ง Refactoring Strategies ### 1. Extract Functions/Utilities When you have duplicated logic in multiple functions: ```typescript // Extract common logic into a utility function const validateUserInput = (email: string, password: string) => { validateEmail(email); validatePassword(password); }; ``` ### 2. Use Inheritance When you have duplicated logic in multiple classes with similar responsibilities: ```typescript // Create a base class with common behavior abstract class BaseService { protected abstract entityName: string; protected async findById(id: string) { // Common implementation } } ``` ### 3. Use Composition When inheritance doesn't fit or you need more flexibility: ```typescript // Create a shared service that can be injected class CommonOperations { logError(context: string, error: Error) { /* ... */ } validateInput(data: any) { /* ... */ } } ``` ### 4. Create Shared Libraries For cross-cutting concerns used across multiple modules: ```typescript // utils/validation.ts export const validators = { email: (email: string) => { /* ... */ }, password: (password: string) => { /* ... */ } }; ``` ## ๐Ÿงช Testing ```bash # Run rule-specific tests npm test -- c002_no_duplicate_code # Test with SunLint CLI sunlint --rules=C002_no_duplicate_code --input=examples/ # Analyze specific files sunlint analyze --rules=C002 src/**/*.ts ``` ## ๐Ÿ“š Related Rules - **C005**: Each function should do one thing (Single Responsibility) - **C047**: Retry logic must not be duplicated - **C014**: Use Dependency Injection instead of direct instantiation ## ๐Ÿ”— References - [DRY Principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) - [Code Duplication (Martin Fowler)](https://refactoring.guru/smells/duplicate-code) - [SonarQube: Duplicated Blocks](https://rules.sonarsource.com/java/RSPEC-1192) --- **Migration Status**: active **Last Updated**: 2025-10-16 ````