Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
co-authored by
Colby McHenry
parent
4fd816b610
commit
7b339373b4
@@ -0,0 +1,63 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const BIN = path.resolve(__dirname, '../dist/bin/codegraph.js');
|
||||
const COLLAPSE_WARNING = 'parse produced no symbols (tree has errors)';
|
||||
const SOURCE = `const char* kTemplate = R"FILE_TEMPLATE_V1(
|
||||
struct Ignored { int v; };
|
||||
)FILE_TEMPLATE_V1";
|
||||
|
||||
int after_the_raw_string(int x) {
|
||||
return x + 1;
|
||||
}
|
||||
`;
|
||||
|
||||
describe('CLI parse warnings (#1522)', () => {
|
||||
let root: string;
|
||||
|
||||
beforeEach(() => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-parse-warning-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
function run(args: string[]) {
|
||||
const result = spawnSync(process.execPath, [BIN, ...args], {
|
||||
cwd: root,
|
||||
encoding: 'utf-8',
|
||||
timeout: 20_000,
|
||||
env: {
|
||||
...process.env,
|
||||
CODEGRAPH_NO_DAEMON: '1',
|
||||
CODEGRAPH_WASM_RELAUNCHED: '1',
|
||||
CODEGRAPH_TELEMETRY: '0',
|
||||
NO_COLOR: '1',
|
||||
},
|
||||
});
|
||||
return { status: result.status, out: (result.stdout ?? '') + (result.stderr ?? '') };
|
||||
}
|
||||
|
||||
it('shows a collapsed parse without failing, then stays quiet after a healthy re-index', () => {
|
||||
const sourcePath = path.join(root, 'min.cpp');
|
||||
fs.writeFileSync(sourcePath, SOURCE);
|
||||
|
||||
const collapsed = run(['init', '--yes']);
|
||||
expect(collapsed.status, collapsed.out).toBe(0);
|
||||
expect(collapsed.out).toContain('Indexed 1 files');
|
||||
expect(collapsed.out).toContain(`min.cpp: ${COLLAPSE_WARNING}`);
|
||||
|
||||
fs.writeFileSync(sourcePath, SOURCE.replaceAll('FILE_TEMPLATE_V1', 'FILE_TEMPLATE_V'));
|
||||
const healthy = run(['index']);
|
||||
expect(healthy.status, healthy.out).toBe(0);
|
||||
expect(healthy.out).not.toContain(COLLAPSE_WARNING);
|
||||
|
||||
const query = run(['query', 'after_the_raw_string']);
|
||||
expect(query.status, query.out).toBe(0);
|
||||
expect(query.out).toMatch(/function\s+after_the_raw_string/);
|
||||
}, 30_000);
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
import { extractFromSource } from '../src/extraction';
|
||||
import { getParser, initGrammars, loadGrammarsForLanguages } from '../src/extraction/grammars';
|
||||
|
||||
function rawStringSource(delimiter: string): string {
|
||||
return `const char* kTemplate = R"${delimiter}(
|
||||
struct Ignored { int v; };
|
||||
)${delimiter}";
|
||||
|
||||
int after_the_raw_string(int x) {
|
||||
return x + 1;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
describe('C++ raw-string delimiter parse collapse (#1522)', () => {
|
||||
beforeAll(async () => {
|
||||
await initGrammars();
|
||||
await loadGrammarsForLanguages(['cpp', 'c']);
|
||||
});
|
||||
|
||||
it('warns when a legal 16-character delimiter swallows every symbol', () => {
|
||||
const result = extractFromSource('min.cpp', rawStringSource('FILE_TEMPLATE_V1'));
|
||||
|
||||
// The vendored tree-sitter-cpp scanner currently rejects the standard's
|
||||
// maximum delimiter length, consuming the following function as ERROR.
|
||||
expect(result.nodes.filter((n) => n.kind === 'function')).toEqual([]);
|
||||
expect(result.nodes.map((n) => n.kind)).toEqual(['file']);
|
||||
expect(result.errors).toEqual([
|
||||
{
|
||||
message:
|
||||
'min.cpp: parse produced no symbols (tree has errors) — ' +
|
||||
'the file is indexed but contributes nothing to the graph',
|
||||
severity: 'warning',
|
||||
code: 'parse_error',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('extracts the function after a 15-character delimiter without warning', () => {
|
||||
const result = extractFromSource('min.cpp', rawStringSource('FILE_TEMPLATE_V'));
|
||||
|
||||
expect(result.nodes.filter((n) => n.kind === 'function').map((n) => n.name))
|
||||
.toEqual(['after_the_raw_string']);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it.each(['min.cpp', 'min.c', 'min.h'])('does not warn on a healthy include-only %s', (filePath) => {
|
||||
const result = extractFromSource(filePath, '#include <stdio.h>\n#include <stdlib.h>\n');
|
||||
|
||||
expect(result.nodes.filter((n) => n.kind !== 'file' && n.kind !== 'import')).toEqual([]);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not warn on a healthy empty file with zero symbols', () => {
|
||||
const result = extractFromSource('empty.cpp', '');
|
||||
|
||||
expect(result.nodes.map((n) => n.kind)).toEqual(['file']);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not warn on parse errors when a function survives', () => {
|
||||
const source = 'int before_the_raw_string() { return 0; }\n' + rawStringSource('FILE_TEMPLATE_V1');
|
||||
const tree = getParser('cpp')!.parse(source)!;
|
||||
try {
|
||||
expect(tree.rootNode.hasError).toBe(true);
|
||||
} finally {
|
||||
tree.delete();
|
||||
}
|
||||
|
||||
const result = extractFromSource('min.cpp', source);
|
||||
|
||||
expect(result.nodes.filter((n) => n.kind === 'function').map((n) => n.name))
|
||||
.toEqual(['before_the_raw_string']);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user