Files
TheLastGarden/eslint.config.js
T
josh 8c1d839adf test(01-02): add CORE-10 firewall test + violator fixture
- src/sim/__test_violation__/violator.ts deliberately imports from
  src/render/__firewall_target__.ts to trigger the firewall rule.
- src/sim/__test_violation__/lint-firewall.test.ts runs ESLint
  programmatically (with ignore: false) against the violator and
  asserts boundaries/element-types fires with severity=error and the
  message mentions both 'sim' and 'render'.
- src/render/__firewall_target__.ts is a minimal export so the
  boundaries plugin can resolve the import to a real path on disk.
  Without a real target, the plugin marks the import as isUnknown
  and silently skips the rule (verified empirically; see SUMMARY).
- eslint.config.js gains an import/resolver: typescript block so the
  TS-aware resolver follows extension-less imports
  ('../../render/foo' -> src/render/foo.ts). Required by the
  boundaries plugin's element classification of import targets.
- tsconfig.app.json excludes *.test.ts and src/sim/__test_violation__/
  so 'tsc -b' does not try to typecheck Node-API-using test code with
  the DOM-only project's lib settings; vitest still discovers them
  via its own include glob.
- Added eslint-import-resolver-typescript as devDep.

Verifies green:
  npm run lint        -> 0 errors, 0 warnings (violator excluded)
  npm test            -> 2/2 pass (sentinel + firewall)
  npm run build       -> tsc -b clean, vite build clean
  npx eslint --no-ignore src/sim/__test_violation__/violator.ts
                      -> exits 1 with the expected
                         boundaries/element-types error

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 23:34:01 -04:00

129 lines
5.6 KiB
JavaScript

// eslint.config.js — ESLint 9 flat config
//
// Phase 1, Plan 02 (CORE-10): the architectural firewall.
//
// This file declares the seven src/ subsystem element types plus the
// template-provided `app` and `game` types, and one rule:
//
// `src/sim/` MUST NOT import from `src/render/` or `src/ui/`.
//
// The simulation core must remain rendering-agnostic and headless so the
// offline-catchup math in Phase 2 can run deterministically without React
// or Phaser. See CLAUDE.md "Architectural Firewall (load-bearing)" and
// .planning/phases/01-foundations-and-doctrine/01-CONTEXT.md (D-10).
//
// We intentionally do NOT pull in `js.configs.recommended` or the
// typescript-eslint *rule sets* here. Plan 02 owns exactly one
// architectural rule; broader code-quality lint is out of scope for
// Phase 1 (and would expand Wave-2 surface area on a clean greenfield
// codebase). Future phases may layer more rules on top of this config
// without touching the firewall block.
//
// We DO use `typescript-eslint`'s *parser* — it is the only way ESLint
// can parse `.ts` / `.tsx` files at all (Espree, ESLint's default
// parser, doesn't understand TypeScript syntax or JSX). This is a
// parser-only integration; no `tseslint.configs.*` rule sets are
// applied. This is documented as a Plan 02 deviation (Rule 3 — Blocking)
// in 01-02-SUMMARY.md.
import boundaries from 'eslint-plugin-boundaries';
import tseslint from 'typescript-eslint';
export default [
// ---------------------------------------------------------------------
// 1. Default-lint exclusions.
//
// The deliberate-violation fixture under src/sim/__test_violation__/
// exists ONLY to be lint-tested by Task 2's Vitest test (which runs
// ESLint programmatically with `ignore: false`). It must NOT trip
// `npm run lint` in CI — the rule is verified by the unit test, not
// by the default lint glob.
// ---------------------------------------------------------------------
{
ignores: [
'src/sim/__test_violation__/**',
'dist/**',
'node_modules/**',
'coverage/**',
'*.tsbuildinfo',
],
},
// ---------------------------------------------------------------------
// 2. Phase-1 architectural firewall (CORE-10).
//
// Seven src/ subsystem types matching CONTEXT D-10's directory layout,
// plus `app` (the React/Phaser bridge files at src/main.tsx, src/App.tsx,
// src/PhaserGame.tsx) and `game` (the Phaser scene tree at src/game/**).
//
// Default posture is `allow` — Phase 1 enforces ONE rule, not a
// closed-by-default architecture. Future phases may add cross-subsystem
// restrictions (e.g., `render` cannot import `save`) without changing
// the default.
// ---------------------------------------------------------------------
{
files: ['src/**/*.{ts,tsx,js,jsx,mjs,cjs}'],
plugins: { boundaries },
languageOptions: {
// Parser-only integration with typescript-eslint. Lets ESLint
// parse TS / TSX (incl. JSX) so the boundaries rule can inspect
// imports. No tseslint rule sets are enabled — that is out of
// Phase-1 scope (Plan 02 owns ONE rule: CORE-10).
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: { jsx: true },
},
},
settings: {
'boundaries/elements': [
{ type: 'sim', pattern: 'src/sim/**' },
{ type: 'render', pattern: 'src/render/**' },
{ type: 'ui', pattern: 'src/ui/**' },
{ type: 'save', pattern: 'src/save/**' },
{ type: 'content', pattern: 'src/content/**' },
{ type: 'audio', pattern: 'src/audio/**' },
{ type: 'store', pattern: 'src/store/**' },
{ type: 'app', pattern: 'src/{main,App,PhaserGame}.{ts,tsx}' },
{ type: 'game', pattern: 'src/game/**' },
],
'boundaries/include': ['src/**/*'],
// Quietly tolerate files that aren't classified (e.g., src/vite-env.d.ts,
// src/__sentinel__.test.ts). The firewall rule only fires on
// sim → {render, ui} edges; unclassified files don't trigger it.
'boundaries/ignore': ['src/vite-env.d.ts', 'src/__sentinel__.test.ts'],
// eslint-plugin-boundaries needs to RESOLVE import paths to disk
// files in order to classify the import target's element type.
// Without a TS-aware resolver, `import x from '../../render/foo'`
// (no extension) cannot be resolved to `src/render/foo.ts` and
// the target is marked `isUnknown`, silently skipping the rule.
// eslint-import-resolver-typescript reads tsconfig.json to follow
// bare-extension TS imports. Verified empirically during Plan 02
// execution; see 01-02-SUMMARY.md "Deviations" (Rule 1 — Bug fix).
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: ['./tsconfig.app.json', './tsconfig.node.json'],
// Suppress "Multiple projects found" noise — we deliberately
// use the referenced-projects tsconfig layout (root tsconfig
// with `references`) per Plan 01.
noWarnOnMultipleProjects: true,
},
},
},
rules: {
// CORE-10: the simulation core cannot reach into render or UI.
// Severity MUST be `error` — `npm run lint` runs with
// `--max-warnings 0` (per Plan 01), so a warning would also fail
// CI, but `error` makes intent unambiguous.
'boundaries/element-types': ['error', {
default: 'allow',
rules: [
{ from: ['sim'], disallow: ['render', 'ui'] },
],
}],
},
},
];