Files
vdm/extension/eslint.config.mjs
samiandClaude Sonnet 5 2cb1959bff ext: no-download-logic ESLint gate in the extension-lint CI job
CLAUDE.md §3's rule was prose only for extension/ (GUI already has
gui_no_download_logic as a ctest). eslint.config.mjs adds a
no-restricted-syntax/no-restricted-globals rule banning fetch/XHR/Request,
ReadableStream.getReader, Range/Content-Range header construction, and
IndexedDB in src/**/*.ts. Verified red on a planted violation (fetch +
Range header + stream reader) and green on ordinary code; that check is
now a permanent regression test (tests/lint/no-download-logic.test.ts)
rather than a one-off manual run. Wired into the existing extension-lint
job in .github/workflows/ci.yml, ahead of web-ext lint.

Generated protocol code (src/shared/protocol/**) is excluded from lint
entirely — it must never be hand-edited, so flagging it as fixable would
be a lie.

Answers gui/docs/ext-requests-m1.md.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ed8KEmAW48v4YHdxLtqsMB
2026-09-11 12:25:19 +04:00

91 lines
3.6 KiB
JavaScript

// 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',
},
},
);