Files
ccs/eslint.config.mjs
T
Tam Nhu Tran 2f94f35ec3 feat(lint): P7 enforcement gates (no-new-throw-error + max-lines) + docs
Epic P7. Locks in the epic's gains with ESLint gates so the adoption work
does not regress.

- ccs/no-new-throw-error (error): custom flat-config rule that flags NEW
  throw new Error(...) outside a generated baseline allowlist. Forces the
  typed-error taxonomy (src/errors/error-types.ts). Existing 338 sites are
  grandfathered in eslint-rules/throw-error-baseline.json; only NEW violations
  error. Rule normalizes the filename to repo-root-relative to match baseline
  keys regardless of how ESLint reports paths.
- max-lines (warn, 400, skipBlankLines/skipComments): warns on files over
  400 LOC (goal of P5/P6 god-file splits). Currently 51 warnings on the
  not-yet-split god-files (P6 territory).
- scripts/generate-throw-error-baseline.js: emits the allowlist from raw
  source (superset of real throws; never undercounts). Run after intentionally
  grandfathering a site, or quarterly to prune.
- tests/unit/eslint-rules/no-throw-new-error.test.ts: rule logic (flags
  off-allowlist, passes on-allowlist/typed/rethrow, line-sensitivity).
- docs/code-standards.md: Lint Enforcement Gates section.
- docs/logging-contract.md: error.code -> ExitCode table (from P4).

validate + validate:ci-parity green.
2026-06-18 18:48:13 -04:00

70 lines
2.4 KiB
JavaScript

import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import prettier from 'eslint-config-prettier';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import noNewThrowError from './eslint-rules/no-new-throw-error.js';
const __configDir = path.dirname(fileURLToPath(import.meta.url));
const baselinePath = path.join(__configDir, 'eslint-rules', 'throw-error-baseline.json');
const throwErrorBaseline = JSON.parse(fs.readFileSync(baselinePath, 'utf8'));
export default [
{
files: ['src/**/*.ts'],
ignores: ['src/**/__tests__/**'],
languageOptions: {
parser: tsparser,
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json',
},
},
plugins: {
'@typescript-eslint': tseslint,
// Local enforcement rules (P7 gates).
ccs: { rules: { 'no-new-throw-error': noNewThrowError } },
},
rules: {
// TypeScript rules - upgraded to errors for stricter type safety
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-non-null-assertion': 'error',
// General code quality
'no-console': 'off', // CLI tool needs console
'prefer-const': 'error',
'no-var': 'error',
'eqeqeq': ['error', 'always'],
// P7 enforcement gates:
// - no-new-throw-error: error on NEW throw new Error(...) outside the
// generated baseline (eslint-rules/throw-error-baseline.json). Forces
// the typed-error taxonomy (src/errors/error-types.ts). Regenerate the
// baseline with: node scripts/generate-throw-error-baseline.js
// - max-lines: warn on files over 400 LOC (goal of P5/P6 god-file splits).
'ccs/no-new-throw-error': ['error', { allowlist: throwErrorBaseline }],
'max-lines': ['warn', { max: 400, skipBlankLines: true, skipComments: true }],
},
},
{
files: ['tests/**/*.js', 'scripts/**/*.js'],
languageOptions: {
ecmaVersion: 2020,
sourceType: 'commonjs',
},
rules: {
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'prefer-const': 'error',
'no-var': 'error',
},
},
prettier,
];