diff --git a/__tests__/ui-server-api.test.ts b/__tests__/ui-server-api.test.ts index 9212ed0..207e9ea 100644 --- a/__tests__/ui-server-api.test.ts +++ b/__tests__/ui-server-api.test.ts @@ -192,6 +192,15 @@ ${callers} ` ); + // CRLF on purpose: tree-sitter numbers rows by `\n`, so a CRLF file must come + // back with the same line numbers the graph recorded — and without the stray + // `\r` rendering at the end of every line. This is what a Windows checkout + // with core.autocrlf looks like, and it is decided by bytes, not by the OS. + fs.writeFileSync( + path.join(srcDir, 'crlf.ts'), + ['export function windowsStyle(n: number): number {', ' return n + 1;', '}', ''].join('\r\n') + ); + fs.writeFileSync( path.join(testsDir, 'service.test.ts'), `import { Service } from '../src/service'; @@ -613,6 +622,27 @@ describe('GET /api/source', () => { expect(body.lines).toHaveLength(2); }); + it('keeps a CRLF file on the index line numbering, without the stray carriage returns', async () => { + const node = await getJson(`/api/node/${await idOf('windowsStyle', 'function')}`); + expect(node.node.file).toBe('src/crlf.ts'); + + const body = await getJson('/api/source?file=src/crlf.ts'); + expect(body.drift).toBe(false); + expect(body.totalLines).toBe(3); + expect(body.lines).toEqual([ + 'export function windowsStyle(n: number): number {', + ' return n + 1;', + '}', + ]); + expect(body.lines.some((l: string) => l.includes('\r'))).toBe(false); + + // The symbol's indexed range still names its own body. + const slice = await getJson( + `/api/source?file=src/crlf.ts&from=${node.node.line}&to=${node.node.endLine}` + ); + expect(slice.lines[0]).toContain('windowsStyle'); + }); + it('refuses a path that escapes the project', async () => { const traversal = await getStatusAndJson( '/api/source?file=' + encodeURIComponent('../../../etc/passwd')