fix(ingestion): skip empty scope extraction (#1100)
This commit is contained in:
@@ -13,8 +13,11 @@
|
||||
* `emitScopeCaptures`. Returns `undefined`; zero work done. This is
|
||||
* the state of every language today — `ParsedFile` production stays
|
||||
* dormant until a language migrates.
|
||||
* 2. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
|
||||
* 3. **Swallows exceptions from either side.** A failure here returns
|
||||
* 2. Short-circuits empty / whitespace-only files. There is no scope
|
||||
* content to extract, and some tree-sitter queries do not match an
|
||||
* otherwise valid empty root node.
|
||||
* 3. Invokes the hook + feeds its output to `ScopeExtractor.extract`.
|
||||
* 4. **Swallows exceptions from either side.** A failure here returns
|
||||
* `undefined` and emits a warning via `onWarn`; legacy parsing on
|
||||
* the same file continues unaffected by the scope-extraction miss.
|
||||
* Scope-based resolution is the new path under construction — it
|
||||
@@ -41,6 +44,7 @@ export function extractParsedFile(
|
||||
cachedTree?: unknown,
|
||||
): ParsedFile | undefined {
|
||||
if (provider.emitScopeCaptures === undefined) return undefined;
|
||||
if (sourceText.trim().length === 0) return undefined;
|
||||
try {
|
||||
const captures = provider.emitScopeCaptures(sourceText, filePath, cachedTree);
|
||||
return extractScope(captures, filePath, provider);
|
||||
|
||||
@@ -67,6 +67,27 @@ describe('extractParsedFile', () => {
|
||||
});
|
||||
|
||||
describe('provider HAS migrated', () => {
|
||||
it('returns undefined without warning for whitespace-only source', () => {
|
||||
const warnings: string[] = [];
|
||||
let called = false;
|
||||
const provider = fakeProvider({
|
||||
emitScopeCaptures: () => {
|
||||
called = true;
|
||||
throw new Error('should not inspect empty source');
|
||||
},
|
||||
});
|
||||
|
||||
for (const src of ['', ' \n\t']) {
|
||||
const result = extractParsedFile(provider, src, 'pkg/__init__.py', (msg) => {
|
||||
warnings.push(msg);
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
}
|
||||
|
||||
expect(called).toBe(false);
|
||||
expect(warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it('threads emitScopeCaptures output through ScopeExtractor', () => {
|
||||
const provider = fakeProvider({
|
||||
emitScopeCaptures: () => [moduleScopeMatch()],
|
||||
|
||||
@@ -56,15 +56,23 @@ function findDef(file: ParsedFile, name: string) {
|
||||
|
||||
describe('Python scopes — module / class / function', () => {
|
||||
it('case 01: minimal module produces a single Module scope', () => {
|
||||
// Empty source produces a zero-range module node; the central
|
||||
// extractor treats zero-range scopes as malformed (and rightly so —
|
||||
// they collide with sibling-overlap detection on subsequent reparses).
|
||||
// Real Python files always have at least a newline.
|
||||
const f = parse('pass\n');
|
||||
expect(f.scopes).toHaveLength(1);
|
||||
expect(f.scopes[0]!.kind).toBe('Module');
|
||||
});
|
||||
|
||||
it('case 01a: empty and whitespace-only files are skipped without warnings', () => {
|
||||
for (const src of ['', ' \n\t']) {
|
||||
const warnings: string[] = [];
|
||||
const parsed = extractParsedFile(pythonProvider, src, 'pkg/__init__.py', (msg) => {
|
||||
warnings.push(msg);
|
||||
});
|
||||
|
||||
expect(parsed).toBeUndefined();
|
||||
expect(warnings).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it('case 01b: large cache-miss files use the adaptive tree-sitter buffer', () => {
|
||||
const padding = 'x'.repeat(600 * 1024);
|
||||
const f = parse(`# ${padding}\ndef after_padding():\n return 1\n`);
|
||||
|
||||
Reference in New Issue
Block a user