import test from 'node:test' import assert from 'node:assert/strict' import { readdirSync, readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join, basename } from 'node:path' const srcDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'src') // Files that are allowed to touch the outside world. const excluded = (file) => ['runtime.js', 'browser.js', 'main.js'].includes(basename(file)) const banned = [ ['let', /\blet\b/], ['var', /\bvar\b/], ['++', /\+\+/], ['--', /--/], ['.push(', /\.push\(/], ['.pop(', /\.pop\(/], ['.shift(', /\.shift\(/], ['.unshift(', /\.unshift\(/], ['.splice(', /\.splice\(/], ['.sort(', /\.sort\(/], ['.reverse(', /\.reverse\(/], ['Math.random', /\bMath\.random\b/], ['Date.now', /\bDate\.now\b/], ['performance.now', /\bperformance\.now\b/], ['document', /\bdocument\b/], ['window', /\bwindow\b/], ['addEventListener', /\baddEventListener\b/], ] const stripComments = (code) => code.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '') const violations = (code) => banned.filter(([, re]) => re.test(stripComments(code))).map(([name]) => name) const jsFiles = (dir) => readdirSync(dir, { withFileTypes: true }).flatMap((e) => e.isDirectory() ? jsFiles(join(dir, e.name)) : e.name.endsWith('.js') ? [join(dir, e.name)] : []) banned.forEach(([name]) => test(`purity scanner flags a known-bad sample using ${name}`, () => { const samples = { let: 'let n = 0', var: 'var n = 0', '++': 'n++', '--': 'n--', '.push(': 'xs.push(1)', '.pop(': 'xs.pop()', '.shift(': 'xs.shift()', '.unshift(': 'xs.unshift(1)', '.splice(': 'xs.splice(0, 1)', '.sort(': 'xs.sort()', '.reverse(': 'xs.reverse()', 'Math.random': 'const r = Math.random()', 'Date.now': 'const t = Date.now()', 'performance.now': 'const t = performance.now()', document: 'document.title', window: 'window.alert(1)', addEventListener: 'x.addEventListener("click", f)', } assert.ok(violations(samples[name]).includes(name)) })) test('purity scanner ignores banned words inside comments', () => { const code = '// let var Math.random()\n/* xs.push(1); document; n++ */\nconst a = 1 // window\n' assert.deepEqual(violations(code), []) }) test('purity scanner accepts ordinary pure code', () => assert.deepEqual(violations('export const f = (xs) => xs.map((x) => x + 1)'), [])) jsFiles(srcDir) .filter((f) => !excluded(f)) .forEach((f) => test(`purity: ${f.slice(srcDir.length + 1)} has no banned constructs`, () => assert.deepEqual(violations(readFileSync(f, 'utf8')), []))) // helpers.js is loaded by every test file; importing src/ there would make all tests fail at load time against the stubs. test('test/helpers.js imports nothing from src/', () => assert.doesNotMatch(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'helpers.js'), 'utf8'), /from\s+['"][^'"]*src\//))