Tooling: - Prettier as the single formatting authority (110 columns, single quotes, measured against the existing style); Markdown excluded because table-cell padding turns the README glossary into 1000-character lines. - Type-aware ESLint ending in eslint-config-prettier, so a rule always means "this is a bug", never "this looks different". - .editorconfig and an `npm run check` that runs typecheck, lint, format and tests in one go. Findings fixed on the first run: three more duplicate imports, four String(unknown) calls (parameter types narrowed instead of casts), two dead initialisers, two redundant casts, a console.log for a real error. Two warnings were false here and are disabled with a reason in place. Tests 132 -> 269: - node-catalog.test.ts covers every node with a run(), with a guard that fails when a new node has no row. - behavior-events.test.ts covers the ten event nodes through a running World, checking timing rather than existence. - Input's state machine (press/release/releaseAll) is reachable without DOM events now; the DOM handlers are thin adapters. Compare fell back to == without a configured operator although the schema default is <; aligned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
2.6 KiB
JavaScript
63 lines
2.6 KiB
JavaScript
import js from '@eslint/js';
|
||
import prettier from 'eslint-config-prettier';
|
||
import globals from 'globals';
|
||
import tseslint from 'typescript-eslint';
|
||
|
||
/**
|
||
* Formatting is Prettier's job – `eslint-config-prettier` switches every stylistic rule off,
|
||
* so a rule here always means "this is a mistake", never "this looks different".
|
||
*/
|
||
export default tseslint.config(
|
||
{ ignores: ['dist/**', 'data/**', 'node_modules/**', 'index.html'] },
|
||
|
||
// Type-aware linting for everything the tsconfig covers (src, tests, vite.config.ts).
|
||
{
|
||
files: ['src/**/*.ts', 'tests/**/*.ts', 'vite.config.ts'],
|
||
extends: [js.configs.recommended, ...tseslint.configs.recommendedTypeChecked],
|
||
languageOptions: {
|
||
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
|
||
globals: globals.browser,
|
||
},
|
||
rules: {
|
||
// The duplicate `import` statements the last refactoring round had to find by hand.
|
||
'no-duplicate-imports': 'error',
|
||
// `tsc` already reports unused locals; keep ESLint quiet about the same thing.
|
||
'@typescript-eslint/no-unused-vars': 'off',
|
||
// Off by design: the codebase builds objects from closures (`createFieldRow` returns
|
||
// `{ setValue }`, test fixtures return `{ sceneOf, object }`). Those have no `this`,
|
||
// but the rule cannot tell them from class methods and fired ~50 times on them.
|
||
'@typescript-eslint/unbound-method': 'off',
|
||
// A `?? []` on something that is never nullish hides that the type says otherwise.
|
||
'@typescript-eslint/no-unnecessary-condition': 'error',
|
||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||
'@typescript-eslint/consistent-type-imports': ['error', { fixStyle: 'inline-type-imports' }],
|
||
'@typescript-eslint/no-import-type-side-effects': 'error',
|
||
// Async work that nobody awaits silently swallows its rejection.
|
||
'@typescript-eslint/no-floating-promises': 'error',
|
||
'@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }],
|
||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||
'no-console': ['error', { allow: ['warn', 'error'] }],
|
||
'prefer-const': 'error',
|
||
'object-shorthand': ['error', 'properties'],
|
||
},
|
||
},
|
||
|
||
// Node scripts: plain JavaScript, no type information available.
|
||
{
|
||
files: ['tools/**/*.mjs'],
|
||
extends: [js.configs.recommended],
|
||
languageOptions: {
|
||
ecmaVersion: 2023,
|
||
sourceType: 'module',
|
||
globals: globals.node,
|
||
},
|
||
rules: {
|
||
'no-duplicate-imports': 'error',
|
||
eqeqeq: ['error', 'always', { null: 'ignore' }],
|
||
'prefer-const': 'error',
|
||
},
|
||
},
|
||
|
||
prettier,
|
||
);
|