fix: gracefully skip files when language parser is unavailable (#136)

Instead of crashing the pipeline when a native tree-sitter binding
(e.g. tree-sitter-swift) fails to build, skip those files early and
warn the user with an actionable message.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
abhigyanpatwari 2026-03-02 09:23:05 +05:30
parent 161f4e31e9
commit ee0a7f076d
4 changed files with 38 additions and 6 deletions

View file

@ -286,7 +286,11 @@ const processParsingSequential = async (
// Skip very large files — they can crash tree-sitter or cause OOM
if (file.content.length > 512 * 1024) continue;
await loadLanguage(language, file.path);
try {
await loadLanguage(language, file.path);
} catch {
continue; // parser unavailable — already warned in pipeline
}
let tree;
try {

View file

@ -11,6 +11,7 @@ import { createASTCache } from './ast-cache.js';
import { PipelineProgress, PipelineResult } from '../../types/pipeline.js';
import { walkRepositoryPaths, readFileContents } from './filesystem-walker.js';
import { getLanguageFromFilename } from './utils.js';
import { isLanguageAvailable } from '../tree-sitter/parser-loader.js';
import { createWorkerPool, WorkerPool } from './workers/worker-pool.js';
const isDev = process.env.NODE_ENV === 'development';
@ -88,7 +89,23 @@ export const runPipelineFromRepo = async (
// Group parseable files into byte-budget chunks so only ~20MB of source
// is in memory at a time. Each chunk is: read → parse → extract → free.
const parseableScanned = scannedFiles.filter(f => getLanguageFromFilename(f.path));
const parseableScanned = scannedFiles.filter(f => {
const lang = getLanguageFromFilename(f.path);
return lang && isLanguageAvailable(lang);
});
// Warn about files skipped due to unavailable parsers
const skippedByLang = new Map<string, number>();
for (const f of scannedFiles) {
const lang = getLanguageFromFilename(f.path);
if (lang && !isLanguageAvailable(lang)) {
skippedByLang.set(lang, (skippedByLang.get(lang) || 0) + 1);
}
}
for (const [lang, count] of skippedByLang) {
console.warn(`Skipping ${count} ${lang} file(s) — ${lang} parser not available (native binding may not have built). Try: npm rebuild tree-sitter-${lang}`);
}
const totalParseable = parseableScanned.length;
// Build byte-budget chunks

View file

@ -576,14 +576,22 @@ const processBatch = (files: ParseWorkerInput[], onProgress?: (filesProcessed: n
// Process regular files for this language
if (regularFiles.length > 0) {
setLanguage(language, regularFiles[0].path);
processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
try {
setLanguage(language, regularFiles[0].path);
processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
} catch {
// parser unavailable — skip this language group
}
}
// Process tsx files separately (different grammar)
if (tsxFiles.length > 0) {
setLanguage(language, tsxFiles[0].path);
processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
try {
setLanguage(language, tsxFiles[0].path);
processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
} catch {
// parser unavailable — skip this language group
}
}
}

View file

@ -36,6 +36,9 @@ const languageMap: Record<string, any> = {
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
};
export const isLanguageAvailable = (language: SupportedLanguages): boolean =>
language in languageMap;
export const loadParser = async (): Promise<Parser> => {
if (parser) return parser;
parser = new Parser();