|
Some checks failed
Build and Publish / build-and-publish (push) Failing after 42s
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| .githooks | ||
| node_modules/.bin | ||
| .gitignore | ||
| index.js | ||
| package.json | ||
| README.md | ||
@lilith/eslint-plugin-file-length
ESLint plugin enforcing file length limits with dual-threshold (warn/error) support.
Philosophy
Large files are symptoms, not the disease.
When a file exceeds 400 lines, it's usually a signal that the code violates the Single Responsibility Principle (SRP)—one of SOLID's core tenets. A file with too many responsibilities:
- Has multiple reasons to change
- Is harder to test in isolation
- Creates coupling between unrelated concerns
- Makes code review and maintenance painful
This plugin doesn't just enforce arbitrary limits. It provides early warning (400 lines) and hard stops (600 lines) to prompt architectural reflection.
Resolution Strategies
When you see a file-length warning, don't just split the file arbitrarily. Apply these principles:
1. Identify Responsibilities (SRP)
Ask: "What are the different reasons this file might change?"
// BAD: user-service.ts (500 lines) - Multiple responsibilities
export class UserService {
// Auth logic (reason to change: auth requirements)
login() { /* ... */ }
logout() { /* ... */ }
// Profile logic (reason to change: profile features)
updateProfile() { /* ... */ }
getProfile() { /* ... */ }
// Notification logic (reason to change: notification system)
sendWelcomeEmail() { /* ... */ }
sendPasswordReset() { /* ... */ }
}
// GOOD: Split by responsibility
// auth.service.ts - One reason to change: auth requirements
// profile.service.ts - One reason to change: profile features
// notification.service.ts - One reason to change: notification system
2. Extract Shared Logic (DRY)
If multiple files share similar patterns, extract them:
// BAD: Duplicated validation in multiple files
// user.controller.ts
function validateUserInput(data) { /* 50 lines */ }
// admin.controller.ts
function validateAdminInput(data) { /* similar 50 lines */ }
// GOOD: Extract to shared module
// validation/user-validation.ts
export function validateUserInput(data) { /* ... */ }
export function validateAdminInput(data) { /* ... */ }
3. Separate Concerns by Layer
Large files often mix concerns across architectural layers:
// BAD: Mixed concerns
// orders.ts (600 lines)
// - Database queries
// - Business logic
// - HTTP response formatting
// - Error handling
// GOOD: Layer separation
// orders/
// repository.ts - Data access
// service.ts - Business logic
// controller.ts - HTTP handling
// errors.ts - Domain errors
4. Extract Configuration/Constants
Large files sometimes contain embedded configuration:
// BAD: Inline configuration bloats file
const COUNTRY_CODES = { /* 200 lines of codes */ };
const CURRENCY_MAP = { /* 100 lines of mappings */ };
// GOOD: Extract to data files
// constants/country-codes.ts
// constants/currencies.ts
5. Use Composition Over Inheritance
Replace deep inheritance hierarchies with composition:
// BAD: God class with everything
class BaseController {
// Auth methods (100 lines)
// Logging methods (50 lines)
// Response formatting (75 lines)
// Error handling (100 lines)
}
// GOOD: Compose behaviors
class OrderController {
constructor(
private auth: AuthService,
private logger: LoggerService,
private responder: ResponseFormatter,
) {}
}
What NOT to Do
Don't game the metrics:
- Splitting a file into
part1.tsandpart2.tsthat import each other - Moving code to functions that are only called once from one place
- Creating "utils" dumping grounds
- Suppressing the rule without architectural review
Don't split prematurely:
- A 350-line file that's cohesive is better than 7 fragmented 50-line files
- The warning at 400 is a prompt to evaluate, not an order to split
Installation
pnpm add -D @lilith/eslint-plugin-file-length
Usage
// .eslintrc.cjs
module.exports = {
plugins: ['@lilith/file-length'],
rules: {
'@lilith/file-length/file-length': ['warn', {
warnThreshold: 400, // Warning: consider splitting
errorThreshold: 600, // Error: must split
}],
},
};
Or use the recommended config:
module.exports = {
extends: ['plugin:@lilith/file-length/recommended'],
};
Options
| Option | Type | Default | Description |
|---|---|---|---|
warnThreshold |
number | 400 | Lines before warning |
errorThreshold |
number | 600 | Lines before error |
skipBlankLines |
boolean | false | Exclude blank lines from count |
skipComments |
boolean | false | Exclude comment lines from count |
Thresholds Rationale
- 400 lines (warn): File is getting complex. Review for SRP violations.
- 600 lines (error): File almost certainly has multiple responsibilities. Must refactor.
These defaults work well for most codebases. Adjust based on your context, but be wary of raising limits just to avoid refactoring.
Exemptions
Some files legitimately need more lines:
// .eslintrc.cjs
module.exports = {
overrides: [
{
// Data files are exempt
files: ['**/*.seed.ts', '**/*.data.ts', '**/migrations/*.ts'],
rules: {
'@lilith/file-length/file-length': 'off',
},
},
],
};
Valid exemptions:
- Generated code
- Migration files
- Seed/fixture data
- Configuration schemas
Invalid exemptions:
- "It's too hard to refactor"
- "Everything is related"
- "We don't have time"
Related
- SOLID Principles
- @lilith/eslint-config-base - Uses this plugin
License
MIT