// ESLint config for the extension. // // The "no download logic in extension/" rule (CLAUDE.md §3) used to be prose only. // GUI turned its half into a ctest (gui/tests/no_download_logic.cmake); this is EXT's // equivalent — a build-failing gate instead of something a reviewer has to remember to // look for. See gui/docs/ext-requests-m1.md for the request this answers. // // The extension's whole job is: collect URL + headers + cookies, hand them to veloxd, // render what comes back. It must never fetch bytes, assemble a Range request, or read // a response body itself — that is download logic, and it belongs in core/daemon only. import js from '@eslint/js'; import tseslint from 'typescript-eslint'; const noDownloadLogic = { name: 'velox/no-download-logic', files: ['src/**/*.ts'], rules: { 'no-restricted-syntax': [ 'error', { selector: "NewExpression[callee.name='XMLHttpRequest']", message: 'No XMLHttpRequest in extension/ — the extension hands URLs to veloxd, it never fetches bytes itself (CLAUDE.md §3).', }, { selector: "NewExpression[callee.name='Request']", message: 'No `new Request(...)` in extension/ — that is download-side plumbing. Hand the URL to veloxd instead (CLAUDE.md §3).', }, { selector: "CallExpression[callee.name='fetch']", message: 'No fetch() in extension/ — the extension never retrieves download bytes itself (CLAUDE.md §3). Talking to veloxd goes through transport/, not fetch.', }, { selector: "MemberExpression[property.name='getReader']", message: 'No ReadableStream reader in extension/ — reading a response body here is download logic (CLAUDE.md §3); the daemon owns transfer bytes.', }, { selector: "Property[key.name='Range'], Property[key.name='range'], Property[key.name='Content-Range'], Property[key.name='content-range']", message: 'No Range/Content-Range header construction in extension/ — resumption is the daemon\'s job (CLAUDE.md §3, docs/05).', }, { selector: "NewExpression[callee.object.name='indexedDB'], CallExpression[callee.object.name='indexedDB']", message: 'No IndexedDB in extension/ for moving bytes — hand off to veloxd instead (CLAUDE.md §3).', }, ], 'no-restricted-globals': [ 'error', { name: 'fetch', message: 'No fetch() in extension/ — see CLAUDE.md §3.' }, { name: 'XMLHttpRequest', message: 'No XMLHttpRequest in extension/ — see CLAUDE.md §3.' }, { name: 'indexedDB', message: 'No IndexedDB in extension/ — see CLAUDE.md §3.' }, ], }, }; export default tseslint.config( { // src/shared/protocol/** is generated (contracts/codegen/gen_ts.py) and must never // be hand-edited — linting it as if we could fix a finding would be a lie. ignores: ['dist/**', 'node_modules/**', 'scripts/**', 'src/shared/protocol/**'], }, js.configs.recommended, ...tseslint.configs.recommended, { files: ['src/**/*.ts', 'tests/**/*.ts'], languageOptions: { parserOptions: { project: false, }, }, rules: { '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, }, noDownloadLogic, { // Tests legitimately construct fake Requests/fetch mocks to exercise transport code // against a fake server; the rule protects src/, not the harness that pokes at it. files: ['tests/**/*.ts'], rules: { 'no-restricted-syntax': 'off', 'no-restricted-globals': 'off', }, }, );