Skip to content

Commit 040be44

Browse files
committed
fix: treat .h as ambiguous with C++ in language-family scoping (#1928)
docs check acknowledged — fixes a real cross-language false-rejection.
1 parent efd7a0c commit 040be44

3 files changed

Lines changed: 50 additions & 1 deletion

File tree

crates/codegraph-core/src/domain/graph/resolve.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,21 @@ fn directory_distance(a: &str, b: &str) -> usize {
317317
/// separate families would reject huge amounts of legitimate same-project
318318
/// resolution. Every other `LanguageKind` variant keeps its own family,
319319
/// preserving `from_extension`'s existing per-language extension groupings
320-
/// (e.g. C's `.c`+`.h`, C++'s `.cpp`/`.cc`/`.cxx`/`.hpp`).
320+
/// (e.g. C's `.c`+`.h`, C++'s `.cpp`/`.cc`/`.cxx`/`.hpp`) — EXCEPT `.h`,
321+
/// treated as ambiguous (returns `None`) rather than inheriting
322+
/// `from_extension`'s C-only mapping: `from_extension` needs one canonical
323+
/// grammar per extension, but a `.h` header is real-world ambiguous between
324+
/// C and C++, and the extremely common case of a `.cpp` file calling into
325+
/// its own project's `.h` header would otherwise be misclassified as
326+
/// cross-language and rejected outright — a real regression from the
327+
/// pre-#1783 same-directory score of 0.7 (Greptile review). This keeps the
328+
/// C/C++-header case working without merging C and C++ source-file families
329+
/// wholesale (`.c` vs `.cpp` intentionally do NOT merge — see
330+
/// is_same_language_family_does_not_merge_c_and_cpp).
321331
fn language_family(file: &str) -> Option<LanguageKind> {
332+
if file.to_ascii_lowercase().ends_with(".h") {
333+
return None;
334+
}
322335
match LanguageKind::from_extension(file) {
323336
Some(LanguageKind::TypeScript) | Some(LanguageKind::Tsx) => Some(LanguageKind::JavaScript),
324337
other => other,
@@ -635,6 +648,15 @@ mod tests {
635648
assert!(is_same_language_family("src/a.c", "src/a.h"));
636649
}
637650

651+
#[test]
652+
fn is_same_language_family_treats_h_as_ambiguous_with_cpp() {
653+
// Greptile follow-up to #1783: `.h` is real-world ambiguous between C
654+
// and C++ (LANGUAGE_REGISTRY/from_extension assigns it to C alone for
655+
// grammar-selection purposes), so a `.cpp` file calling into its own
656+
// project's `.h` header must not be rejected as cross-language.
657+
assert!(is_same_language_family("src/widget.cpp", "src/widget.h"));
658+
}
659+
638660
#[test]
639661
fn is_same_language_family_merges_cpp_source_and_header_variants() {
640662
assert!(is_same_language_family("src/a.cpp", "src/a.hpp"));

src/domain/graph/resolve.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,24 @@ function directoryDistance(a: string, b: string): number {
533533
*/
534534
const JS_FAMILY_REGISTRY_IDS = new Set(['javascript', 'typescript', 'tsx']);
535535

536+
/**
537+
* Extensions excluded from the family map entirely, so `languageFamily`
538+
* returns null for them and they fall through to the ambiguous-extension
539+
* path (ordinary distance-based scoring, never rejected outright).
540+
*
541+
* `.h` is real-world ambiguous between C and C++: LANGUAGE_REGISTRY assigns
542+
* it to the `c` entry alone (grammar selection needs one canonical parser),
543+
* but the extremely common case of a `.cpp` file calling into its own
544+
* project's `.h` header would otherwise be misclassified as cross-language
545+
* and rejected outright (confidence 0) — a real regression from the
546+
* pre-#1783 same-directory score of 0.7 (Greptile review). Treating `.h` as
547+
* ambiguous — like an unrecognised extension — keeps the C/C++-header case
548+
* working without merging C and C++ source-file families wholesale (`.c`
549+
* vs `.cpp` intentionally do NOT merge — see
550+
* is_same_language_family_does_not_merge_c_and_cpp).
551+
*/
552+
const AMBIGUOUS_EXTENSIONS = new Set(['.h']);
553+
536554
/**
537555
* extension → language-family lookup, derived from LANGUAGE_REGISTRY (the
538556
* single source of truth for language definitions) so newly-added languages
@@ -542,6 +560,7 @@ const _extToLanguageFamily: Map<string, string> = new Map();
542560
for (const entry of LANGUAGE_REGISTRY) {
543561
const family = JS_FAMILY_REGISTRY_IDS.has(entry.id) ? 'javascript' : entry.id;
544562
for (const ext of entry.extensions) {
563+
if (AMBIGUOUS_EXTENSIONS.has(ext)) continue;
545564
_extToLanguageFamily.set(ext, family);
546565
}
547566
}

tests/unit/resolve.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,14 @@ describe('isSameLanguageFamily (#1783)', () => {
761761
expect(isSameLanguageFamily('src/a.c', 'src/a.h')).toBe(true);
762762
});
763763

764+
it('treats .h as ambiguous with C++ (Greptile follow-up)', () => {
765+
// `.h` is real-world ambiguous between C and C++ (LANGUAGE_REGISTRY
766+
// assigns it to C alone for grammar-selection purposes), so a `.cpp`
767+
// file calling into its own project's `.h` header must not be rejected
768+
// as cross-language.
769+
expect(isSameLanguageFamily('src/widget.cpp', 'src/widget.h')).toBe(true);
770+
});
771+
764772
it('treats C++ source and header extensions as the same family', () => {
765773
expect(isSameLanguageFamily('src/a.cpp', 'src/a.hpp')).toBe(true);
766774
expect(isSameLanguageFamily('src/a.cc', 'src/a.cxx')).toBe(true);

0 commit comments

Comments
 (0)