import { describe, expect, it } from 'vitest' import { blankStringContents, stripComments } from './source-tree-scan' /** * These guards are only worth having if they cannot under-report. * * The first version of `stripComments` read the slash-star inside a POSIX glob * as a comment opener and blanked 24,000 characters of live code, so the * windowsHide guard walked past a real unguarded spawn and called the file * clean. Every case here is a shape that produced, or would produce, that. */ describe('stripComments', () => { it('leaves a POSIX glob in a template literal alone', () => { const source = ['const s = `', ' case "$root"/*/home) echo hit;; esac', '`', 'spawn(x)'].join( '\n' ) expect(stripComments(source)).toContain('case "$root"/*/home)') expect(stripComments(source)).toContain('spawn(x)') }) it('still removes a real block comment', () => { expect(stripComments('/* spawn(bad) */ spawn(good)')).not.toContain('bad') expect(stripComments('/* spawn(bad) */ spawn(good)')).toContain('good') }) it('keeps line count stable so reported lines stay honest', () => { const source = '/*\n\n*/\nspawn(x)' expect(stripComments(source).split('\n')).toHaveLength(source.split('\n').length) }) it('is not derailed by an apostrophe in prose', () => { // An unterminated quote used to swallow the rest of the file, so every // later comment stopped being stripped. const source = ["// don't do this", '/* spawn(bad) */', 'spawn(good)'].join('\n') expect(stripComments(source)).not.toContain('bad') }) it('is not derailed by a quote inside a regex literal', () => { const source = ['const re = /[\'"]/', '/* spawn(bad) */', 'spawn(good)'].join('\n') expect(stripComments(source)).not.toContain('bad') }) it('handles an escaped quote inside a string', () => { const source = ["const s = 'it\\'s'", '/* spawn(bad) */'].join('\n') expect(stripComments(source)).not.toContain('bad') }) }) describe('blankStringContents', () => { it('neutralises parentheses inside a string so a call is matched whole', () => { // A shell script embedded as a string closed the call early, so the options // object fell outside the match and its flags read as absent. const source = `execFileSync('wsl.exe', ['-c', 'test "$(cat x)" = y'], { windowsHide: true })` const blanked = blankStringContents(source) let depth = 0 let end = 0 for (let i = blanked.indexOf('('); i < blanked.length; i += 1) { if (blanked[i] === '(') { depth += 1 } else if (blanked[i] === ')') { depth -= 1 if (depth === 0) { end = i break } } } expect(blanked.slice(0, end)).toContain('windowsHide') }) it('keeps the quotes themselves, so an import can still be recognised', () => { expect(blankStringContents(`from 'node:child_process'`)).toContain("'") }) it('preserves newlines', () => { const source = 'const a = `x\ny`\n' expect(blankStringContents(source).split('\n')).toHaveLength(source.split('\n').length) }) }) describe('regex literals', () => { it('does not let an apostrophe in a pattern open a string', () => { // The real shape, from a shell quoter: the `'` inside /'/g read as a string // opener and desynced every later line, so a scan of the file silently // found nothing and its guard reported clean. const source = "const q = (v: string): string => `'${v.replace(/'/g, \"x\")}'`\nspawn('wsl.exe')\n" expect(blankStringContents(source, true)).not.toBe('desynced') expect(blankStringContents(source)).toContain('spawn(') }) it('treats a leading block comment as a comment, not a pattern', () => { // `/*` at index 0 has no preceding token, so the prev-token test called it // a pattern and swallowed to the next `/` -- 110k characters of a real // file, in the direction that hides offenders. const source = '/* banner with a renderer/Electron slash */\nspawn("wsl.exe")\n' // Assert the swallowed span itself survives: `spawn(` sits after the bad // region, so checking only for it passes even while the banner is eaten. expect(blankStringContents(source)).toContain('banner with a renderer') }) it.each([ [ 'postfix decrement', "const half = n-- / 2; execFile(bin, args, { cwd: '/usr/bin' })", 'execFile(bin, args' ], ['non-null assertion', 'const b = done! / total!\nexecFile(y)\n', 'execFile(y)'], [ 'JSX self-close', 'const a = c ? : \nexecFile(x)\n', 'execFile(x)' ] ])('reads %s as division, not a pattern', (_case, source, survives) => { // Blanking live code is the dangerous direction: the swallowed span took a // whole execFile call with it and left no desync behind, so the guard saw // zero calls and called the file clean. expect(blankStringContents(source)).toContain(survives) }) it('still reads division as division', () => { const source = 'const a = (x) / 2\nconst b = arr[i] / 2\nspawn("wsl.exe")\n' expect(blankStringContents(source)).toContain('spawn(') expect(blankStringContents(source, true)).not.toBe('desynced') }) })