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
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
// Regression test for the "no download logic in extension/" ESLint gate
|
|
// (eslint.config.mjs, answering gui/docs/ext-requests-m1.md). Runs ESLint's Node API
|
|
// directly against fixture source so a future edit to the rule set can't silently stop
|
|
// catching the patterns it was written for.
|
|
import { ESLint } from 'eslint';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
async function lint(code: string): Promise<number> {
|
|
const eslint = new ESLint({ cwd: new URL('../..', import.meta.url).pathname });
|
|
// Path only needs to match the `files: ['src/**/*.ts']` glob in eslint.config.mjs.
|
|
const [result] = await eslint.lintText(code, { filePath: 'src/background/__fixture.ts' });
|
|
return result.messages.filter((m) => m.severity === 2).length;
|
|
}
|
|
|
|
describe('no-download-logic ESLint gate', () => {
|
|
it('goes red on fetch() + a hand-built Range header + a stream reader', async () => {
|
|
const errors = await lint(`
|
|
export async function grabBytes(url: string) {
|
|
const res = await fetch(url, { headers: { Range: 'bytes=0-1023' } });
|
|
const reader = res.body!.getReader();
|
|
return reader.read();
|
|
}
|
|
`);
|
|
expect(errors).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('goes red on XMLHttpRequest', async () => {
|
|
const errors = await lint(`const x = new XMLHttpRequest();`);
|
|
expect(errors).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('stays green on ordinary transport/RPC code', async () => {
|
|
const errors = await lint(`
|
|
export function greet(name: string): string {
|
|
return \`hello \${name}\`;
|
|
}
|
|
`);
|
|
expect(errors).toBe(0);
|
|
});
|
|
});
|