fix: skip unavailable parsers in sequential parsing

Mirror the existing import/call/heritage availability contract so sequential parsing skips missing language parsers without calling loadLanguage and can emit the same verbose warning path.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Gujiassh 2026-03-16 12:03:20 +09:00 committed by gujishh
parent d9960c62bf
commit adfcd4e1b3
2 changed files with 55 additions and 12 deletions

View file

@ -9,6 +9,7 @@ import { ASTCache } from './ast-cache.js';
import { getLanguageFromFilename, SupportedLanguages } from 'gitnexus-shared';
import { extractVueScript, isVueSetupTopLevel } from './vue-sfc-extractor.js';
import { yieldToEventLoop } from './utils/event-loop.js';
import { isVerboseIngestionEnabled } from './utils/verbose.js';
import {
getDefinitionNodeFromCaptures,
findEnclosingClassInfo,
@ -284,7 +285,8 @@ const processParsingSequential = async (
) => {
const parser = await loadParser();
const total = files.length;
const skippedLanguages = new Map<string, number>();
const logSkipped = isVerboseIngestionEnabled();
const skippedByLang = logSkipped ? new Map<string, number>() : null;
for (let i = 0; i < files.length; i++) {
const file = files[i];
@ -303,10 +305,10 @@ const processParsingSequential = async (
const language = getLanguageFromFilename(file.path);
if (!language) continue;
// Skip unsupported languages (e.g. Swift when tree-sitter-swift not installed)
if (!isLanguageAvailable(language)) {
skippedLanguages.set(language, (skippedLanguages.get(language) || 0) + 1);
if (skippedByLang) {
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
}
continue;
}
@ -331,7 +333,7 @@ const processParsingSequential = async (
continue; // parser unavailable — safety net
}
let tree;
let tree: Parser.Tree;
try {
tree = parser.parse(parseContent, undefined, {
bufferSize: getTreeSitterBufferSize(parseContent.length),
@ -349,8 +351,8 @@ const processParsingSequential = async (
continue;
}
let query;
let matches;
let query: Parser.Query;
let matches: Parser.QueryMatch[];
try {
const language = parser.getLanguage();
query = new Parser.Query(language, queryString);
@ -635,11 +637,12 @@ const processParsingSequential = async (
});
}
if (skippedLanguages.size > 0) {
const summary = Array.from(skippedLanguages.entries())
.map(([lang, count]) => `${lang}: ${count}`)
.join(', ');
console.warn(` Skipped unsupported languages: ${summary}`);
if (skippedByLang && skippedByLang.size > 0) {
for (const [lang, count] of skippedByLang.entries()) {
console.warn(
`[ingestion] Skipped ${count} ${lang} file(s) in parsing processing — ${lang} parser not available.`,
);
}
}
};

View file

@ -11,9 +11,11 @@ vi.mock('../../src/core/tree-sitter/parser-loader.js', () => ({
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
import { createASTCache } from '../../src/core/ingestion/ast-cache.js';
import { processParsing } from '../../src/core/ingestion/parsing-processor.js';
import { processImports } from '../../src/core/ingestion/import-processor.js';
import { processCalls } from '../../src/core/ingestion/call-processor.js';
import { processHeritage } from '../../src/core/ingestion/heritage-processor.js';
import { createSymbolTable } from '../../src/core/ingestion/symbol-table.js';
import { createResolutionContext } from '../../src/core/ingestion/resolution-context.js';
import * as parserLoader from '../../src/core/tree-sitter/parser-loader.js';
@ -147,4 +149,42 @@ describe('sequential native parser availability', () => {
process.env.GITNEXUS_VERBOSE = previous;
}
});
it('skips Swift files in processParsing when the native parser is unavailable', async () => {
vi.mocked(parserLoader.isLanguageAvailable).mockReturnValue(false);
await expect(processParsing(
createKnowledgeGraph(),
[{ path: 'App.swift', content: 'class AppViewController: UIViewController {}' }],
createSymbolTable(),
createASTCache(),
)).resolves.toBeNull();
expect(parserLoader.loadLanguage).not.toHaveBeenCalled();
});
it('warns when processParsing skips files in verbose mode', async () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
const previous = process.env.GITNEXUS_VERBOSE;
process.env.GITNEXUS_VERBOSE = '1';
vi.mocked(parserLoader.isLanguageAvailable).mockReturnValue(false);
await processParsing(
createKnowledgeGraph(),
[{ path: 'App.swift', content: 'class AppViewController: UIViewController {}' }],
createSymbolTable(),
createASTCache(),
);
expect(warnSpy).toHaveBeenCalledWith(
'[ingestion] Skipped 1 swift file(s) in parsing processing — swift parser not available.',
);
warnSpy.mockRestore();
if (previous === undefined) {
delete process.env.GITNEXUS_VERBOSE;
} else {
process.env.GITNEXUS_VERBOSE = previous;
}
});
});