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
91 lines
3.6 KiB
JavaScript
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',
|
|
},
|
|
},
|
|
);
|