mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
c266a5e1a7
The client lint ran untyped with two react-hooks rules, so nothing saw a floating promise, an async handler where a void one is expected, a whole-store Zustand subscription or a window event bus, all of which client/CLAUDE.md forbids. eslint.config.mjs now lints with the project service and adds, as warnings: no-floating-promises, no-misused-promises (void returns in JSX attributes included), the rest of react-hooks v7's recommended set, complexity 20, max-depth 4, max-params 5 and max-lines-per-function (120 in a .ts function, 300 in a .tsx one, whose body also holds its markup), no-console and no-non-null-assertion. Three local rules in scripts/lib/eslint-rules.mjs cover what no stock rule sees: an empty .catch() callback, use*Store() without a selector (the message points to useShallow, since an object selector without it loops on Zustand 5), and window.__* globals and window.dispatchEvent with an event of our own. scripts/eslint-baseline.json takes today's count of each new rule, so the existing code is frozen, not changed, and each new instance fails lint:warnings. The CI step gets the 8 GB heap typed linting needs.
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
import { RuleTester } from 'eslint';
|
|
import tseslint from 'typescript-eslint';
|
|
import { describe, it } from 'vitest';
|
|
import trek from '../../../scripts/lib/eslint-rules.mjs';
|
|
|
|
// RuleTester reports through the test framework's describe/it.
|
|
RuleTester.describe = describe;
|
|
RuleTester.it = it;
|
|
|
|
const tester = new RuleTester({
|
|
languageOptions: {
|
|
parser: tseslint.parser,
|
|
parserOptions: { ecmaFeatures: { jsx: true } },
|
|
},
|
|
});
|
|
|
|
describe('trek/no-swallowed-catch', () => {
|
|
tester.run('no-swallowed-catch', trek.rules['no-swallowed-catch'], {
|
|
valid: [
|
|
'load().catch((err) => toast.error(err.message))',
|
|
'load().catch(() => setFailed(true))',
|
|
'promise.then(() => {})',
|
|
'try { a() } catch { b() }',
|
|
],
|
|
invalid: [
|
|
{ code: 'load().catch(() => {})', errors: [{ messageId: 'swallowed' }] },
|
|
{ code: 'load().catch(() => undefined)', errors: [{ messageId: 'swallowed' }] },
|
|
{ code: 'load().catch(() => null)', errors: [{ messageId: 'swallowed' }] },
|
|
{ code: 'load().catch(function () {})', errors: [{ messageId: 'swallowed' }] },
|
|
],
|
|
});
|
|
});
|
|
|
|
describe('trek/store-without-selector', () => {
|
|
tester.run('store-without-selector', trek.rules['store-without-selector'], {
|
|
valid: [
|
|
'const id = useTripStore((s) => s.trip.id)',
|
|
'const { a, b } = useTripStore(useShallow((s) => ({ a: s.a, b: s.b })))',
|
|
'useTripStore.getState().loadTrip(1)',
|
|
'const t = useTranslation()',
|
|
'const s = useStore()',
|
|
],
|
|
invalid: [
|
|
{ code: 'const store = useTripStore()', errors: [{ messageId: 'whole', data: { name: 'useTripStore' } }] },
|
|
{ code: 'const { settings } = useSettingsStore()', errors: [{ messageId: 'whole' }] },
|
|
],
|
|
});
|
|
});
|
|
|
|
describe('trek/no-window-globals', () => {
|
|
tester.run('no-window-globals', trek.rules['no-window-globals'], {
|
|
valid: [
|
|
"window.dispatchEvent(new Event('resize'))",
|
|
"window.addEventListener('resize', onResize)",
|
|
'window.location.reload()',
|
|
'const data = dragSession.current',
|
|
],
|
|
invalid: [
|
|
{ code: 'window.__dragData = { placeId: 1 }', errors: [{ messageId: 'global', data: { name: '__dragData' } }] },
|
|
{ code: "window.__addToast?.('saved', 'success')", errors: [{ messageId: 'global' }] },
|
|
{ code: '(window as any).__dragData = null', errors: [{ messageId: 'global' }] },
|
|
{ code: "window.dispatchEvent(new CustomEvent('accommodations:refresh'))", errors: [{ messageId: 'bus' }] },
|
|
{ code: "window.dispatchEvent(new Event('collab-files-changed'))", errors: [{ messageId: 'bus' }] },
|
|
],
|
|
});
|
|
});
|