mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-19 06:22:38 +08:00
fix(extraction): preload the Objective-C grammar for C-family headers (#1628)
A .h file is classified from its path as C, so the preload set that the parse
workers receive contains c (and cpp, added explicitly for exactly this
reason). Parse-time detection then reads the source and can return objc — a
grammar no worker holds, so the file fails outright with 'Failed to get
parser for language: objc' and nothing in it is indexed. A project with any
.m file happened to work, because that pulled objc into the set by path.
Preload objc alongside cpp whenever c is present: both are readings that
content-aware .h detection can produce, so both have to be available before
the workers start.
The same three lines existed twice (full index and changed-file reindex), and
only one of them would have been easy to remember to change, so this extracts
preloadLanguagesForFiles() and uses it in both places.
Verified end to end against the reporter's reproduction — a directory holding
only an Objective-C .h file:
before: 1 parser initialization failures / 'Failed to get parser for
language: objc' in .codegraph/errors.log
after: Indexed 1 files, 2 nodes, 1 edges
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -242,6 +242,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
- Fixed a long-running `codegraph ui` session serving a symbol that a sync had already deleted. The viewer keeps one connection to your index open, and its in-memory lookup didn't notice when another process — your agent's sync, or `codegraph sync` — rewrote the file underneath it, so a symbol screen could keep showing a body with no callers while search correctly reported it had moved. Because a symbol's identity includes the line it starts on, this happened after almost any edit above it.
|
||||
|
||||
- Objective-C headers now index in a project that has no `.m` file. A `.h` file is read as C from its name alone, and only later — once its contents are read — recognized as Objective-C; the grammar for that was never loaded up front, so the file failed with a parser error and nothing in it reached the index. Adding any `.m` file used to make the same header work, which is what made this look arbitrary. Thanks @Juddd. (#1628)
|
||||
|
||||
## [1.6.0] - 2026-08-26
|
||||
|
||||
### Highlights
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Grammar preload set for a file list (#1628).
|
||||
*
|
||||
* Path-only detection calls every `.h` file C, but parse-time detection reads
|
||||
* the source and can reclassify it as C++ or Objective-C. Workers only ever
|
||||
* receive the grammars named by this set, so a header that turns out to be
|
||||
* Objective-C in a project with no `.m` file had no parser to go to and the
|
||||
* file failed outright with `Failed to get parser for language: objc`.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { preloadLanguagesForFiles } from '../src/extraction';
|
||||
|
||||
describe('grammar preload set (#1628)', () => {
|
||||
it('covers both ambiguous readings of a .h file, C++ and Objective-C', () => {
|
||||
const langs = preloadLanguagesForFiles(['repro.h']);
|
||||
// Path-only detection says C…
|
||||
expect(langs).toContain('c');
|
||||
// …and parse-time detection may say either of these instead.
|
||||
expect(langs).toContain('cpp');
|
||||
expect(langs).toContain('objc');
|
||||
});
|
||||
|
||||
it('adds nothing for a project with no C-family headers', () => {
|
||||
const langs = preloadLanguagesForFiles(['a.ts', 'b.py']);
|
||||
expect(langs).not.toContain('c');
|
||||
expect(langs).not.toContain('cpp');
|
||||
expect(langs).not.toContain('objc');
|
||||
});
|
||||
|
||||
it('does not duplicate a language the files already need', () => {
|
||||
const langs = preloadLanguagesForFiles(['repro.h', 'seed.m', 'other.cpp']);
|
||||
expect(langs.filter((l) => l === 'objc')).toHaveLength(1);
|
||||
expect(langs.filter((l) => l === 'cpp')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('honors extension overrides when detecting the base set', () => {
|
||||
const langs = preloadLanguagesForFiles(['weird.frob'], { '.frob': 'python' });
|
||||
expect(langs).toContain('python');
|
||||
});
|
||||
});
|
||||
+26
-11
@@ -742,6 +742,30 @@ function findNestedGitRepos(absDir: string, relPrefix: string): string[] {
|
||||
* scope cannot diverge from each other or from `git ls-files --exclude-standard`
|
||||
* (#1728).
|
||||
*/
|
||||
|
||||
/**
|
||||
* The grammars to preload for a file set.
|
||||
*
|
||||
* Path-only detection calls every `.h` file C, but parse-time detection reads
|
||||
* the source and can reclassify it as C++ or Objective-C (`detectLanguage`
|
||||
* with a `source` argument). Workers only ever get the grammars named here, so
|
||||
* a header that turns out to be Objective-C in a project with no `.m` file
|
||||
* found no parser and failed with `Failed to get parser for language: objc`
|
||||
* (#1628). C++ was already covered; Objective-C was not.
|
||||
*/
|
||||
export function preloadLanguagesForFiles(
|
||||
files: string[],
|
||||
overrides?: Record<string, Language>
|
||||
): Language[] {
|
||||
const languages = [...new Set(files.map((f) => detectLanguage(f, undefined, overrides)))];
|
||||
if (languages.includes('c')) {
|
||||
for (const ambiguous of ['cpp', 'objc'] as const) {
|
||||
if (!languages.includes(ambiguous)) languages.push(ambiguous);
|
||||
}
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
export class ScopeIgnore {
|
||||
private embedded: Array<{ root: string; matcher: Ignore }>;
|
||||
private defaults: Ignore = defaultsOnlyIgnore();
|
||||
@@ -1798,11 +1822,7 @@ export class ExtractionOrchestrator {
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
|
||||
// Detect needed languages and load grammars in the parse worker
|
||||
const neededLanguages = [...new Set(files.map((f) => detectLanguage(f, undefined, overrides)))];
|
||||
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded when c is needed
|
||||
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
||||
neededLanguages.push('cpp');
|
||||
}
|
||||
const neededLanguages = preloadLanguagesForFiles(files, overrides);
|
||||
|
||||
// Parse files on a pool of worker threads (keeps the main thread free for UI
|
||||
// and uses every core). Falls back to in-process parsing when the compiled
|
||||
@@ -3020,12 +3040,7 @@ export class ExtractionOrchestrator {
|
||||
// Load only grammars needed for changed files
|
||||
if (filesToIndex.length > 0) {
|
||||
const overrides = loadExtensionOverrides(this.rootDir);
|
||||
const neededLanguages = [...new Set(filesToIndex.map((f) => detectLanguage(f, undefined, overrides)))];
|
||||
// .h files default to 'c' but may be C++ — ensure cpp grammar is loaded
|
||||
if (neededLanguages.includes('c') && !neededLanguages.includes('cpp')) {
|
||||
neededLanguages.push('cpp');
|
||||
}
|
||||
await loadGrammarsForLanguages(neededLanguages);
|
||||
await loadGrammarsForLanguages(preloadLanguagesForFiles(filesToIndex, overrides));
|
||||
}
|
||||
|
||||
// Index changed files
|
||||
|
||||
Reference in New Issue
Block a user