From 4dd29ea5c15e54b30400612e92e9a679705a047b Mon Sep 17 00:00:00 2001 From: Colby Mchenry Date: Thu, 16 Jul 2026 15:04:38 -0500 Subject: [PATCH] fix(cpp): strip template args from out-of-line method receiver qualifiers (#1309) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit template T Box::get() stored qualified_name Box::get — the qualifier never matched the class node indexed as Box, so the method didn't link to its class, while the inline form of the same method produced Box::get. ICU-shaped multi-line template parameter lists leaked whole <…> blocks (newlines included) into qualified_name, exceeding NAME_MAX for downstream consumers. extractCppReceiverType now applies stripCppTemplateArgs (the #1043 normalization for base-class refs) to the receiver qualifier. fmt re-index: template-arg-in-qualifier names 25 -> 4 (remaining are a FMT_BEGIN_EXPORT misparse artifact and gmock conversion-operator names, both distinct pre-existing shapes), node count byte-stable at 7,536. Fixes #1286 Co-authored-by: Claude Fable 5 --- CHANGELOG.md | 1 + __tests__/extraction.test.ts | 60 +++++++++++++++++++++++++++++++ src/extraction/languages/c-cpp.ts | 10 +++++- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4802407..7d5dde8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- C++ methods defined out-of-line on a template class (`template T Box::get() { ... }`) no longer keep the template parameter list in their qualified name. They now index as `Box::get` — identical to an inline definition of the same method — so they link to their class and resolve from call sites again, and pathological multi-line template parameter lists can no longer blow the qualified name past filesystem name limits. (#1286) - Go route detection no longer misidentifies ordinary method calls that share HTTP verb names — `cache.Put("key", value)`, `store.Get("config", out)`, `bus.Handle("user.created", handler)` and the like were being indexed as HTTP routes, polluting route listings in cache-heavy codebases. A registration now has to look like one: its first argument must be a `/`-prefixed path (all routers) or a Go 1.22 `"METHOD /path"` pattern on `Handle`/`HandleFunc`, which now also extracts the method instead of listing the route as `ANY`. (#1259) - Progress output on Windows no longer mixes ASCII `|` rails with the Unicode `│ ◆ ●` frame around them. In terminals that render Unicode (Windows Terminal, VS Code, ConEmu/Cmder, JetBrains, Alacritty), the whole `codegraph init` / `index` / `sync` block now draws with matching box-drawing characters; unrecognized legacy consoles keep the safe all-ASCII output that avoids garbled characters. `CODEGRAPH_ASCII=1` / `CODEGRAPH_UNICODE=1` still override in either direction. (#398) - CLI output now honors the `NO_COLOR` convention and new `--color` / `--no-color` flags, and goes plain automatically when piped: commands like `codegraph status`, `query`, `callers`, and `files` no longer embed ANSI color codes when stdout isn't a terminal, and a piped `codegraph init` / `index` / `sync` prints simple per-phase lines instead of progress-animation control characters. `FORCE_COLOR` or `--color` forces color back on for pipes that render it. (#1281) diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index d794707..6498d42 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -4074,6 +4074,66 @@ class Both : public Base, public Plain {}; }); }); + describe('C++ out-of-line template method receivers (#1286)', () => { + // `template T Box::get()` used to store qualified_name + // `Box::get` — the `` qualifier never matched the class node indexed + // as `Box`, and long multi-line parameter lists could push qualified_name + // past NAME_MAX. Inline definitions of the same method produce `Box::get`, + // so the out-of-line form must normalize to the identical name. + it('strips the template parameter list from the receiver qualifier', () => { + const code = `template +class Box { +public: + T get() const; + void set(T v); +private: + T value; +}; + +template T Box::get() const { return value; } +template void Box::set(T v) { value = v; } +`; + const result = extractFromSource('box.cpp', code); + expect(result.errors).toHaveLength(0); + const methods = result.nodes.filter((n) => n.kind === 'method'); + const qns = methods.map((n) => n.qualifiedName).sort(); + // Out-of-line definitions carry the SAME qualifier as the class node. + expect(qns).toContain('Box::get'); + expect(qns).toContain('Box::set'); + expect(qns.find((q) => q?.includes('<'))).toBeUndefined(); + // Names themselves stay clean. + expect(methods.map((n) => n.name).sort()).toEqual(expect.arrayContaining(['get', 'set'])); + }); + + it('multi-line template parameter lists cannot leak into qualified_name (NAME_MAX overflow shape)', () => { + // The ICU capi_helper.h shape: enormous multi-line parameter names made + // qualified_name 272 bytes (> NAME_MAX 255) including embedded newlines. + const code = `template +class ApiHelper { +public: + CPPType* validate(); +}; + +template +CPPType* ApiHelper::validate() { + return nullptr; +} +`; + const result = extractFromSource('capi_helper.h', code); + const validate = result.nodes.find((n) => n.kind === 'method' && n.name === 'validate' && n.qualifiedName?.includes('::')); + expect(validate).toBeDefined(); + expect(validate!.qualifiedName).toBe('ApiHelper::validate'); + expect(validate!.qualifiedName!.length).toBeLessThan(255); + expect(validate!.qualifiedName).not.toMatch(/[<>\n]/); + }); + }); + describe('C++ stack-allocation construction (#1035)', () => { // `Calculator calc(0)` (direct-init) and `Widget w{1, 2}` (brace-init) carry // the constructor args directly on the declarator — no call/new node — so diff --git a/src/extraction/languages/c-cpp.ts b/src/extraction/languages/c-cpp.ts index 76a0939..3c20e24 100644 --- a/src/extraction/languages/c-cpp.ts +++ b/src/extraction/languages/c-cpp.ts @@ -89,7 +89,15 @@ function extractCppReceiverType(node: SyntaxNode, source: string): string | unde const qid = findDeclaratorQualifiedId(declarator); if (!qid) return undefined; const parts = getNodeText(qid, source).trim().split('::').filter(Boolean); - return parts.length > 1 ? parts.slice(0, -1).join('::') : undefined; + if (parts.length <= 1) return undefined; + // An out-of-line template method definition carries the class's template + // parameter list in the qualifier (`template T Box::get()`), + // but the class node is indexed as bare `Box` — strip `<…>` so the receiver + // matches it, the same normalization #1043 applies to base-class refs. + // Multi-line parameter lists otherwise leak whole `<…>` blocks (newlines + // included) into qualified_name, which can exceed NAME_MAX (#1286). + const receiver = stripCppTemplateArgs(parts.slice(0, -1).join('::')); + return receiver || undefined; } /**