@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
````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
````