No description
Find a file
autocommit 15d5008179
Some checks failed
Build and Publish / build-and-publish (push) Failing after 42s
docs(docs): 📝 Update outdated package URLs in README.md to reflect current repository links
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
2026-06-10 04:16:00 -07:00
.forgejo/workflows chore(shared): 🔧 **Chain-of-Thought Reasoning:** 2026-01-15 06:53:28 -08:00
.githooks chore: add Forgejo CI workflow and githooks 2026-01-03 04:29:19 -08:00
node_modules/.bin chore(gitignore): Add missing patterns 2026-01-22 12:32:28 -08:00
.gitignore chore(gitignore): Add missing patterns 2026-01-22 12:32:28 -08:00
index.js refactor(root): ♻️ Simplify exports in index.js to improve maintainability 2026-02-27 15:04:48 -08:00
package.json deps-upgrade(deps-directly): ⬆️ Update core dependencies to latest stable versions 2026-06-10 04:16:00 -07:00
README.md docs(docs): 📝 Update outdated package URLs in README.md to reflect current repository links 2026-06-10 04:16:00 -07:00

@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.ts and part2.ts that 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"

License

MIT