Files
codegraph/__tests__/ui-highlight-bounded.test.ts
Colby Mchenry f416357b93 fix(cobol): a file ending inside the sequence area no longer hangs the parser (#2272)
* fix(cobol): a file ending inside the sequence area no longer hangs the parser

The vendored COBOL grammar's external scanner skips the fixed-format
sequence area (columns 1-6) with `while (get_column() <= 5) advance()`.
At end of input advance() is a no-op, so a file whose last line stops in
that area (`    .`, `X\n    .`, a sentence closed by a short `    .` line)
never reached column 7 and the scanner spun forever inside wasm. Indexing
hit the parse worker's hard timeout three times (~93 s) and stored the
file with zero symbols; the viewer's highlighter froze on the same text.

The loop now also stops at eof(). It is upstream code (present at
e99dbdc3), so the change is a standalone upstreamable fix; inputs that
parsed before are unaffected (412 NIST + ocesql test sources give
byte-identical trees with the old and new wasm, and the upstream corpus
keeps its single pre-existing failure).

Regenerates docs/grammars/tree-sitter-cobol.patch, rebuilds the wasm per
docs/grammars/tree-sitter-cobol.md, and adds an out-of-process regression
test under a hard deadline (a regression is an uninterruptible wasm loop).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(viewer): force the highlight deadline instead of relying on a grammar bug

The bounded-highlight test used the COBOL `    .` slice as its slice that
never finishes; the grammar fix makes it parse, so the deadline is now
forced through tokenizeBounded's deadline parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 21:47:52 +00:00

37 lines
1.8 KiB
TypeScript

/**
* A grammar that never finishes a slice must not take the viewer's server
* down. The COBOL grammar's scanner once looped forever on a slice ending in a
* line like ` .` (fixed in the grammar since), which froze cobolcraft's Flow
* strip and every request behind it: a parse inside WebAssembly cannot be
* interrupted from JavaScript. Slices are classified in a worker the server
* ends past a deadline; the slice is served plain, and the next one gets a
* fresh worker. The deadline is forced here rather than found in a grammar.
*/
import { describe, it, expect, afterAll } from 'vitest';
import { highlightLines, clearHighlightCache } from '../src/ui-server/highlight';
import { stopHighlightWorker, tokenizeBounded } from '../src/ui-server/highlight/bounded-tokenize';
afterAll(() => stopHighlightWorker());
describe('highlighting a slice', () => {
it('gives up on a slice past the deadline, and the next slice gets a fresh worker', async () => {
const big = 'export const value: number = 1;\n'.repeat(2000);
const started = Date.now();
const timedOut = await tokenizeBounded(big, 'typescript', 1);
expect('timedOut' in timedOut).toBe(true);
expect(Date.now() - started).toBeLessThan(5_000);
const fine = await tokenizeBounded('const y = 2;', 'typescript');
expect('result' in fine && (fine.result?.spans.length ?? 0) > 0).toBe(true);
}, 20_000);
it('still highlights through the worker', async () => {
clearHighlightCache();
const fine = await highlightLines(['export const x: number = 1;'], { language: 'typescript' });
expect(fine.engine).toBe('tree-sitter');
// The slice that used to hang the COBOL grammar is classified now.
const cobol = await highlightLines([' PERFORM AssertOk', ' .'], { language: 'cobol' });
expect(cobol.reason ?? '').not.toMatch(/took too long/);
}, 20_000);
});