mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 16:57:09 +08:00
a6cfb523717184f59fa67a44eff6ea1021b0cecd
1497 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a6cfb52371 |
fix(sync): an import a barrel passes on under another name relinks when its module gains it (#2665)
A sync that gives a module a definition retries the failed references
waiting for the names the module declares, and rebinds the edges into the
names that changed (CG-33). An importer that reaches the module through a
barrel passing it on under another name waits under neither: matx's
`export { default as MatxLoading } from './MatxLoading'` hands
MatxLoading.jsx to `import { MatxLoading } from 'app/components'`, a name
the file never declares. That import and the calls through it stayed
failed, and where a name match had taken the name elsewhere while the
module lacked it (vben's `alert`, which its alert barrel passes on from
`vbenAlert`, onto the `alert.vue` component), the edge stayed there, until
the importer changed or the project was indexed again.
reopenReferencesThroughBarrels now records each importer below a barrel
with the names the barrel passes the changed files on as, and re-opens that
importer's failed references through those names and, for the names the
files never declare, its edges bound before the sync resolved anything, as
the added-file path does. A declared name stays the retry's and the
rebind's, which cap a common one. The per-importer lookup replaces the
repo-wide retry by the passed-on names, whose per-name ceiling skips a name
as common as `alert`; the barrels' module keys are still retried for lazy
routes. passedOn, which the added-file path shares, now also reads an
import a barrel exports again, with the names it exports it as (`import
Card from './Card'; export { Card as AppCard }`), which only the lazy-route
walk followed before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
90d6501b09 |
fix(go): a call of a standard method name like err.Error() links to its receiver's declared type, never to one it shares a word with (#2664)
A Go call of a standard method name (GO_STD_METHODS: Error, String, Err,
Lock, Len, Header, ...) on a receiver whose type wasn't worked out was
guessed by a word the receiver shares with a type's name: kubernetes'
`err.Error()` linked to `notRegisteredErr::Error`, prometheus's to
`ParseErr::Error`, etcd's `leaderID.String()` to `MoveLeaderRequest`, and
a receiver declared as a type from outside the project to a project type
of its name (etcd's `var pendingMu sync.Mutex` to its concurrency `Mutex`).
In matchMethodCall, for a standard method name on a plain Go variable:
- The receiver's declaration in scope (goLocalDecl, the scope reader)
decides, over the unscoped pattern scan: a project type's own method,
through the package that declares it; nothing for a predeclared type, a
type from outside the project, a type literal, a value an outside
package hands out, or a declared type that lacks the method.
- Otherwise a guess needs the receiver to name the owner type itself,
case and a trailing number aside (`id` an `ID`, `urlsMap` a `URLsMap`):
in Strategy 3 through receiverNamesOwner, and in Strategy 2, which took
any method of the owner's file whose name contained the type's
(`status` reached `RuntimeStatus::String` beside a `Status` type).
- Only a declaration the scope reader finds rules a call out. The pattern
scan reads back for the name without block scope and can find another
declaration of it, like a test table's `version string` above
`version, _ := parse(...)`; valid Go calls no method but `Error` on a
predeclared type, so such a read says nothing.
A/B on main
|
||
|
|
0d33925aa3 |
fix(ts): a method whose name has a $ resolves through its receiver's type (#2662)
The method-call pattern read the method name as \w+, which has no $, so
a JS/TS call like bitwarden's this.organizationService.organizations$(id)
or vscode's RPC this._proxy.$acceptModelChanged(…) matched no method-call
shape and linked nothing, however well its receiver was typed. For the
JS family the method part now admits $; the receiver part is unchanged,
so RxJS receivers like this.destroy$.next() stay out of name guessing.
A/B on main
|
||
|
|
b4e101387f |
fix(ts): a receiver a dependency-injection call sets up has the class it is given (#2661)
Angular's `private readonly dataService = inject(DataService)` or
`inject<DialogParams>(DIALOG_DATA)`, and vscode's
`this._register(this.instantiationService.createInstance(Widget, …))` or
`const widget = this.instantiationService.createInstance(Widget)`, gave
the field or local no type, so calls through them linked nowhere or by
name alone. The field reader and the TS local patterns now read them:
`inject` only when the file imports it from `@angular/core` (Vue's
`inject(key)` returns whatever was provided under the key), its type
argument first; `createInstance` only when the project declares one that
returns an `InstanceType` of its constructor argument. A local set from a
member of what the injector returns isn't read as the class.
A type the file imports under another name, like bitwarden's
`OrganizationApiServiceAbstraction as OrganizationApiService` or vscode's
`Engine as FileSearchEngine`, is now found through that import rather
than as a class with the local name.
A/B on main
|
||
|
|
93f85d10ce |
fix(go): an explicitly named import owns its name over an unaliased import's last path element (#2660)
fix(go): an explicitly named import owns its name over an unaliased import's last path element
Go binds a name to one import per file. etcd's benchmark tool imports
"github.com/cheggaaa/pb/v3" (package pb) beside
v3 "go.etcd.io/etcd/client/v3", so `v3.Op` is clientv3's. The import
mappings also listed the unaliased import under its last path element,
`v3`, and the first one listed, the progress-bar library outside the
project, took every `v3.` qualifier: `op v3.Op`, `c *v3.Client` and
`[]v3.OpOption` linked to nothing.
extractGoImports no longer lists an unaliased import under a name another
import is explicitly given; it keeps the name goimports assumes for it
(`pb`), as before.
A/B on main
|
||
|
|
383c2cd9e3 |
fix(go): an interface's methods link the types their signatures name (#2659)
A Go interface's methods are `method` nodes, but their signatures named
no types: etcd's `KV.Put(ctx context.Context, key, val string, opts
...OpOption) (*PutResponse, error)` had no edge to `OpOption` or
`PutResponse`. A call through the interface reaches that method, so
impact on those types missed the interface method and the callers that go
through it.
TS extractor and Rust kernel, mirrored:
- extractGoInterfaceMethods / extract_go_interface_methods give each
method a `references` ref per type name in its parameters and results
(goInterfaceMethodTypeNames / interface_method_type_names, on #2655's
collectGoTypeNames).
- Skipped: the predeclared types and, written bare, the interface's own
type parameters (goTypeParameterNames / type_parameter_names, split out
of goDeclaredNames). The interface's own name is a type its method names
(`Children() []Node`), so it is kept.
A/B on main
|
||
|
|
6692e05107 |
fix(go): an unexported name links its own package's declaration, and a local binding shadows the package (#2658)
Go keeps an unexported name inside its package, yet prometheus tsdb's
`[]*memSeries` in head_append.go linked to nothing. Name matching scored
head.go's `memSeries` and `tsdb/agent`'s the same, because directory
proximity counts shared leading directories (and caps at 80), so a child
or deep sibling package ties with the package's own directory. The first
one met, `tsdb/agent`'s, was then vetoed as invisible. Parameters,
results, struct fields, composite literals and calls all failed this way:
prometheus's `memSeries` and `sample{…}` (1,185 literals), and in
kubernetes client-go's per-version informer types and many test helpers.
- matchByExactName drops a Go candidate the reference cannot see before
the ranking (isGoNameVisible, shared with isVisibleAcrossFiles' Go rule),
as it already did for C#, Java, PHP and Scala, so the package's own
declaration wins.
- That lets a name with more namesakes than the ambiguity ceiling resolve
to its package's declaration. It also exposed bare calls to a local
closure named like a package function: client-go's `init := func(…)`
then `init(t)` bound to `func init()`. gateTargetKind now leaves a bare
Go call or function value unresolved when the function around it binds
the name itself (isGoLocallyBound: a parameter, `:=`, `var`), as Go's
scoping says. The same rule drops existing wrong links: a test's
`volumeSpec = volumeSpec(…)` var passed on, or a local `errors` map that
linked to kubectl's `errors` method.
A/B on main
|
||
|
|
c38f2678ac |
fix(ts): a supertype import that names a constant reaches the interface beside it (#2657)
vscode's ILogService is both an interface and its createDecorator constant, and the import of it as ITerminalLogService's or IUserDataSyncLogService's supertype resolved to the constant, so the walk up to ILogger stopped there and their this.logService calls linked nowhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c22acf36c |
fix(ts): a call on a known class goes through that class's own bases (#2656)
A TS/JS call on a field or a typed local whose class the file's import names went, when that class didn't declare the method itself, to any class with the method and a matching name: vscode's `this._storage.dispose` (`Storage extends Disposable`) reached a test fixture's `Disposable`, Copilot's `Emitter extends lsp.Emitter`, whose `fire` is the package's, reached a test fixture's `Emitter::fire`, and `this.logService.trace()` on `ILogService extends ILogger` linked nowhere. Now that class decides: its own method, or the nearest one up its head's `extends` / `implements`, each supertype found the way its subtype's file names it. For a field, a chain that is fully known and has no such method, or leaves the project for a package, links nothing. A typed local, whose type an unscoped scan reads, takes only a method found. A supertype that can't be found, or a same-named function-typed property, leaves the call to the lookups that follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a944ec3579 |
fix(go): a struct links the types its fields are declared with (#2655)
A Go struct referenced nothing through its fields: etcd's `type
PutResponse struct { Header *ResponseHeader }` or gin's `type Context
struct { handlers HandlersChain }` had no `references` edge to those
types, so impact on `ResponseHeader` or `HandlersChain` missed every
struct holding one, and their users. Main had 0 such edges on gin, etcd,
prometheus and kubernetes; only embedded fields were read (`extends`).
TS extractor and Rust kernel, mirrored:
- goStructFieldTypeNames / struct_field_type_names walk each named
field's type and push a `references` ref per type name from the
struct, on the name. Fields mint no nodes: impact never climbs
`contains` upward (#536), so a field node would stop impact short of
the struct; Dart attributes field types to the class too (#2327).
- #2441's alias walk is factored into collectGoTypeNames /
collect_type_names + goDeclaredNames / declared_names, so struct
fields skip what aliases skip: predeclared types and, written bare,
the declaration's own type parameters and its own name (no self-edge,
no failed row for `next *node`).
- An embedded field stays `extends`; an embedded generic's type
arguments are references (client-go's
`listers.ResourceIndexer[*v1.Pod]`: 651 such fields in kubernetes).
Resolver: gateTargetKind's unknown-qualifier rule (#2417) now covers
every Go type declaration (GO_TYPE_KINDS). Without it, a field
`op clientv3.Op` under an unaliased go.etcd.io/etcd/client/v3 import
bound to the same file's own `Op`.
Explore: a `references` edge whose ends are both type declarations no
longer makes either end connected to an entry. With the new edges alone
the #1500 CG-10 gate failed: payroll's 8-line `RunResult` (holding a
`[]Payslip`) became connected and displaced the 51-line
runPayrollCycleAll body. The rule alone changes no rendered symbol on
gin, etcd, prometheus, kubernetes, bloc, bulletproof or refine-examples.
Validation (main
|
||
|
|
c7b50382af |
fix(ts): index abstract methods as methods of their class (#2653)
Neither the TS extractor nor the kernel read `abstract_method_signature`, so an abstract class's `abstract send(path: string): Promise<unknown>;` had no node. An abstract class used as a dependency-injection token, like bitwarden's `abstract class ApiService` in `libs/common/src/abstractions`, had none of its methods in the graph, and a call through a field of that type linked to whichever same-named class declared the method. Abstract methods are now indexed as method signatures are: a method of the class that declares them, never a free function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
de3eb71df6 |
fix(js,ts): an import written from a jsconfig or tsconfig baseUrl links the file it names (#2649)
CRA and Vite admin templates (codedthemes' berry and mantis, uilibrary's
matx) set `"baseUrl": "src"` or `"paths": { "*": ["./src/*"] }` in a
jsconfig and write every cross-file import as `layout/MainLayout`. The
import resolver took each one for an npm package: loadProjectAliases
returned null for a config with a baseUrl and no paths, and
isExternalImport classifies a bare specifier from the root config alone,
before a nested app's config is read. The imports stayed unresolved,
their names linked by bare-name matching (vite components rendering the
remix app's namesakes), and React Router routes loaded through
`lazy(() => import('views/x'))` bound to the local Loadable value.
- path-aliases: a config with only `baseUrl` loads, marked
`declaresBaseUrl`; aliasAndBaseUrlCandidates lists the most specific
`paths` pattern's targets, then the specifier read from a declared
baseUrl (paths alone never reads bare specifiers, as in TS 4.1+).
- import-resolver: a specifier isExternalImport takes for a package is
read through the importing file's own config (nearest, else the root's)
and binds only to a file the index holds (`fileExists` also answers for
folders and node_modules). A package the file's package.json or an
enclosing one declares, or a Node built-in, never binds to a project
module that way, only to a `.d.ts` the project keeps for it (#2437's
`"*": ["./typings/*"]`). A root catch-all `"*"` no longer marks every
specifier local in isExternalImport's default mode either.
- The module ref of a one-segment import (`'config'`) links the file its
specifier resolves to, as a path-shaped one already does (#2528).
- isBoundToOutOfRepoImport keeps the `extends` edge of a class whose base
is imported through baseUrl.
- importBindingTail parks a binding under its module when the app's
config maps the specifier to an indexed file, so a sync that edits the
module relinks `import Layout from 'layout/MainLayout'` and
`{ Foo as Bar }` (#2450's rule for relative imports). An undeclared name
that maps to nothing (`express` under `"baseUrl": "."`) and a declared
package keep their own tail, which root-"*" repos had lost.
- The TypeScript and TSX extension lists probe `/index.jsx` last, as tsc
does: a route resolves its lazy module as TypeScript.
Validated against main
|
||
|
|
d3a778d0b9 |
fix(ts): a class extends the class after its type parameters (#2648)
fix(ts): a class extends the class after its type parameters A TS/JS class head was read up to its first `extends`, which for `class EditorPane<MementoType extends object = object> extends Composite<MementoType>` is a type parameter's constraint: EditorPane read as extending `object` (so nothing), and `MultiWindowParts<T extends IMultiWindowPart, …>` as extending `IMultiWindowPart`. Fields, wrapper methods and static methods inherited through such a head were missed or found on the wrong class. The head is now read from its `class` keyword to its body, over several lines, with type parameter lists and type arguments left out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6071c9e487 |
fix(ts): a field set through a wrapper has the wrapped class's type (#2647)
`private readonly _onDidChange = this._register(new Emitter<void>())` and `this._store.add(new Emitter())` gave the field no type, so its `fire()` calls linked nowhere. The field reader now reads `new X(` handed to `this.<method>(…)` or `this.<holder>.<method>(…)` when that method's own declaration returns its argument (`<T>(o: T): T`, T its own type parameter) and the call ends the statement. The end-of-statement check trims what follows the wrapper's `)`: a CRLF checkout's lines keep their `\r`, which a `$` anchor never matched. The field walk and the wrapper lookup now go ten classes up a class's `extends` chain instead of four: vscode's editors inherit `_register` and `_store` from `Disposable` up to eight classes above them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b210e0eb59 |
fix(ts): a this.<field> call finds a field a base class declares (#2645)
A field the calling class doesn't declare was read off any same-named class elsewhere, never off the classes it extends. It is now looked up on the calling class's base classes, each the class its subclass's file imports or declares, and a same-named class elsewhere is read last, only for a type the call's file imports or declares. A type a file imports from a package outside the project now links to nothing in the project, unless a project file declares the package with `declare module`, Flow libdefs included: microsoft-authentication's `TelemetryReporter` from `@vscode/extension-telemetry` reached another extension's interface of that name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b860b214e5 |
fix(ts): a call on an imported value links to the class its file imports (#2644)
A call on a value another file exports, like `store.add(…)` after
`import { store } from './state'` with `export const store = new Store()`
there, read the type off the value's declaration but broke a tie between
same-named classes by the calling file, which declares neither, and then
by index order: on vscode, `MenuRegistry`, `FileAccess`, `extUri` and
`errorHandler` calls reached copilot's vendored copies or a test fixture.
The value's file now says which class it is, the one it imports or
declares, in Vue, Svelte and Astro components too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
34b17f6b9d |
fix(ts): a local annotated as one of a type's properties has that property's type (#2640)
A local or parameter annotated as one of a type's properties, like `editor: IChatWidget['inputEditor']`, was read as the type it indexes, so its calls reached that type's method of the same name or a guess by the method's name: on vscode, `visibility.isVisible(…)` through `ISessionChatPillVisibilityService['pullRequests']` reached the service's own `isVisible`, and `chatModel.onDidChange(…)` a folding model's. The property's declared type now decides alone, read off the indexed type the call's file imports, with its class the one the declaring file imports or declares, and an array property follows the array rules. A property whose type isn't written there (a getter's, a `typeof`, a type parameter of the class) or a key that isn't a name still reads as the type it indexes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
10e316d9a7 |
fix(ts): a typed call links to the method of the class the file imports (#2641)
When several classes share a name, a TypeScript or JavaScript call on a typed value broke the tie only by the call site's own file, then took the first declaration indexed, and a `this.<field>` call by folder nearness. On vscode, about 25,000 calls on classes vscode imports from its own `vs/base` and `vs/platform` reached the copies the Copilot extension vendors, 907 `Emitter` calls a copilot test fixture's copy, and 952 calls whose first match was a test fixture's class linked to nothing. The file declaring the class the call's file imports under the written name, read through re-exports, `import type`, namespace imports, default imports and `require`, is now passed to resolveMethodOnType, which picks that file's method among several, through the supertype walk too. `this.<field>` calls check the import of the call's own file before folder nearness. Java, Kotlin and every other language are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fb213d0bc5 |
fix(ts): an array method on a local annotated as an array of a class links to nothing, not the class's method (#2639)
`items: Item[]` was read as an `Item`: the annotation's capture stopped at `[`, so `items.find(…)` linked to `Item.find` whenever the class declares a method of that name. On vscode that was 16 wrong edges, like `distinctElements.find(…)` on an `ExplorerItem[]` reaching `ExplorerItem.find` and `ranges.map(…)` on an `OffsetRange[]` reaching `OffsetRange.map`. The annotation pattern now captures a bracket suffix with the type. On an array of a type, a method arrays have links to nothing and isn't matched by its name either. Any other method can't be called on the array, so it is called on an element that shares its name, as in `d.forEach(d => d.dispose())`, and keeps the element's type as before. An indexed type (`Element["groupIds"]`) is still read as the type it indexes. The sync parking that came with the previous change now reads arrays from the type inference gives instead of re-reading the declaration line: an array method on an array keeps its member's tail, and an element's or an indexed type's call waits for that type's module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8ed0202071 |
fix(resolution): workspace globs reach nested members, and a negated glob leaves members out (#2638)
pnpm's `packages/**/*` (folo) and npm's `libs/**/*` (bitwarden) list
member packages at any depth, `packages/plugin-*` and `apps/*/web` name
them by part of a folder name or by a folder below the wildcard, and a
negated glob (`!**/example`, `!examples/with-nx`) leaves some out. The
workspace loader listed only the folders directly under the text before
the first `*` and took a negation for a folder name, so folo's
`@follow/utils`, two levels under `packages/`, was no member and every
import of it counted as an outside package's.
Globs now match folder by folder: `*`, `?` and `{a,b}` within a name,
`**` across folders, breadth-first so a member nearer the root keeps a
name a deeper copy also declares. A wildcard never enters a dot-folder,
`node_modules` or `bower_components`, and a `**` skips `test` and `tests`
folders as pnpm's default ignores do; a folder the glob names literally
is always entered. What a negated glob matches is no member.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
53ad0d48b3 |
fix(sync): a member call on a local typed by an imported class links when the class's module changes (#2637)
`cache.get(k)` after `const cache = new LRUCache(10)` (or an annotated local, a typed parameter, a default or namespace import of the class) is linked by a fresh index through the local's declaration. When the class's module was missing or empty, the failed call was parked under its member's name, `get`, which more failed refs share than the retry's ceiling, so a sync that added, restored or filled in the module never retried it. Such a call now waits under the key of the module the type's binding imports, as a member of the binding itself does since the previous change (2630), and keeps that change's member and receiver triggers. Only calls are parked this way: in TypeScript a typed local's member is only ever a call, and a method of `this` passed as a value is looked up in the enclosing class. A local declared as an array of the class (`results: URI[]`) keeps its member's tail. Resolution reads the element type there too, but `results.push` is the array's method, which no edit of the class's module links. On vscode those calls were 60% of what the parking added and nearly doubled what an edit of `uri.ts` retried. Inference now reports the line it read the declaration from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
986de95c35 |
fix(vue): an import through @/ or ~/ in a Vue project links the module's file (#2633)
In a project with a `vue` dependency or a `.vue` file, a script's module import through `@/` or `~/` went through the Vue resolver's own mapping to `src/`, which linked the first node of the file it found: a constant the module declares, or a component. A tsconfig alias that starts like a Nuxt virtual module (`#app/where`) linked the importing file itself. Without Vue the import resolver links the module's file node, following tsconfig paths. So the walks a sync makes from a module or barrel that appears or goes away to the files importing it (#2512, #2560) never found these importers. Which node the edge hit also depended on the order a file's nodes were stored, so a sync and a fresh index could disagree. The Vue resolver now leaves a module import to the import resolver whenever that finds the file, and only adds what a Vue app's bundler finds from a plain script: a component named without `.vue`, or a folder's `index.vue` (`import Layout from '@/layout'`), linked to the file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
baf190678f |
fix(sync): a member reference through an import binding links its module when the module changes (#2630)
`NODE_PATH_EXCLUDELIST.includes(p)` after `import { NODE_PATH_EXCLUDELIST }
from './constants'`, `store.get(k)` through a default import or
`api.get()` through a namespace import is linked by a fresh index through
the import. When the module was missing or lacked the export, the failed
reference was parked under its member's name (`includes`, `get`), which the
module seldom declares and which the retry skips past its 500 ceiling. A
sync that gave the module the export never relinked it, nor one that added
the module where no import edge reached its file.
A direct member reference through an import binding of a project module is
now parked under the module's key, as whole names through a binding are
since #2450, and keeps two more triggers. Its member's name, through a new
expression index on the tail its name gives (schema v16), under the same
ceiling as the references still parked under that name: a namespace
import's barrel forwards the member from another file, and a guess by name
links a receiver whose module never resolves. Its receiver's name, through
a prefix range on the module-name index of #2450: a barrel's import of the
declaring file that no walk finds. The router navigation retry and the
dead-code check read module-parked calls by their member through the new
index. A chain or a longer path keeps its member's tail: on vscode,
parking them too tripled the refs an edit of a hot module retries, and
none of them resolved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
3be906fcf4 |
test: stop index-daemon-rebuild leaving a suspended daemon on Windows (#2629)
A full suite run on Windows left a `serve --mcp --path %TEMP%\cg-rebuild-…` process behind. Its parent was gone, and its one thread was suspended with 0 CPU used. It would never exit, and it held the checkout's dist. libuv creates every detached child suspended on Windows and resumes it only at the end of `uv_spawn`. A launcher killed inside that call never resumes its daemon. The daemon never runs, so it writes no daemon.pid, and nothing can find or stop it. The rebuild test's teardown killed its launcher at once. The test stops the daemon on purpose, so by then the launcher is serving calls in-process with a retry timer running. When the retry comes due during the last call, the launcher spawns its daemon as soon as that call answers, just as the teardown begins. Under load the two coincide. The teardown now: - closes each launcher's stdin and waits for it to exit. A graceful exit can't land inside the synchronous spawn. - records the daemons each launcher spawns (#1773's recorder) and stops them until all have exited. One spawned as its launcher exited may still be starting, and stopDaemonAt leaves a daemon running until it answers. The recorder's comment now says that a launcher killed mid-spawn records nothing. CODEGRAPH_DAEMON_RETRY_MS=2500 puts the retry inside the last call on a quiet box. Under it, main's test left a daemon in 6 of 12 runs: 2 suspended ones, plus 4 live ones whose fixture then failed to delete. This test left nothing in 12 runs. Of the mutants, killing launchers again, a single stopDaemonAt and no spawn recorder each leaked. The other daemon test files left nothing in 2 runs each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e2b0cf5eea |
fix(cpp): a call on an inherited member reached through a chain, like options.statistics->getTickerCount(…) on a RocksDB Options, links to the member's class (#2628)
Step 3 of member chains, after #2626 and #2627. The type of `x.member` was
read from x's class alone, so a member that class inherits was never found
and the call fell back to its bare name.
A temporary diagnostic on main measured why chain calls still fell through:
2,801 of 4,884 were a member declared in a base class. Nearly all were
rocksdb's `Options`, which declares no members of its own and inherits
`statistics`, `table_factory`, `listeners`, `prefix_extractor` and the rest
from `DBOptions` and `ColumnFamilyOptions`. The other causes are left for
later:
- a chain whose base can't be typed: 875;
- a class the index lacks, e.g. protobuf's generated `_impl_`: 626;
- a member found nowhere: 337.
cppExpressionType's member branch now uses cppInheritedDataMember, the
lookup the calling class's own members already use (#2533):
- the class's own declaration first;
- then the classes it derives from, through their base edges, to depth 4;
- a base that depends on the class's template parameters is skipped
unless a using-declaration brings the name in.
Kit A/B against main
|
||
|
|
e8ec7fd847 |
fix(cpp): a call on a member reached through other members, like thread->rand.Next() on a Random64, links to the method of the member's class (#2627)
Step 2 of member chains; #2626 was step 1. matchCppMemberChainCall (formerly
matchCppOutsideMemberChainCall) now also resolves a chain whose member is
of a project class, in the same steps matchCppUnspelledReceiverCall takes
for `it->second.Flush()`:
- the class C++ lookup finds for the member's type, where that type is
written;
- then its method, or one it inherits (cppMethodOf, with overloads picked
by argument types);
- or no edge when the class surely lacks it (cppLacksMethod).
Before, such a call went by its bare name to whichever class had a method
of that name. Inside a method it could also be taken for an implicit
`this` call on the method's own class.
A member is now read from the class node C++ lookup found (cppDataMemberIn),
not from the first class of that qualified name that declares it
(cppDataMemberType, removed). rocksdb's db_bench_tool.cc and
db_stress_shared_state.h each declare a ROCKSDB_NAMESPACE::ThreadState,
whose `rand` is a Random64 in one and a Random in the other.
Kit A/B against main
|
||
|
|
6e884d7bce |
fix(cpp): a call on a member reached through other members, like options.cf_paths.emplace_back(…) on a std::vector, no longer links to a project method of that name (#2626)
The extractor records a call on a member chain (`a.b.m()`, `a->b->m()`,
`this->p_.m()`) by its bare method name, so it went to whichever project
method shared the name. A census of the cgstd kit found most such calls
linked by name alone, and most of those links wrong. Above all, they were
on members of standard-library types, whose methods (`size`, `reset`,
`push_back`, `get`, `empty`) a large codebase has namesakes of.
matchCppOutsideMemberChainCall reads the chain where the call is written
(cppMemberChainReceiver) and types it with the existing cppMemberObject:
- a data member is typed by its declared type in its class
(cppDataMemberType);
- `->` goes through a pointer, smart pointer, optional or iterator.
It gives no edge when the object the method is called on is a library type
(isCppLibraryType) or a type under a namespace the project declares nothing
in (isCppOutsideQualifiedType). These are the call-chain resolver's rules.
Unlike a call chain, the operator is read as written, so `.` on a
`std::vector<T>` or a `std::unique_ptr<T>` is that type's own member,
whatever `T` is.
Anything else, a member of a project class included, goes to the name
strategies as before.
Kit A/B against main
|
||
|
|
a25706950f |
test: wait for what holds an MCP test's dir before removing it (#2624)
Two MCP test files sometimes failed under full-suite load on Windows with EBUSY in their afterEach, and then left their temp dir behind: - mcp-status-freshness: every case leaves an index-freshness measurement worker still ending, and the deadline case's worker is terminated in the middle of its measurement. Until it exits, the worker can hold the index or the project dir, and the removal raced that exit (EBUSY on codegraph.db or on the dir, ENOTEMPTY on .codegraph). The afterEach now awaits endFreshnessMeasurements(), the server's own shutdown wait, before it closes the index and removes the dir. - mcp-initialize: after the killed server's 'close', the dir itself can stay busy a moment longer (EBUSY rmdir). It freed within about 80 ms, with no process left under the server. A bounded `maxRetries` doesn't help: on Node 22, fs.rmSync never retries EBUSY on a directory and throws at once. So removeWhenReleased() retries the removal itself, for up to 5 s, and a Windows-only test holds a dir as a child's cwd to prove the helper waits. Under CPU load (14 of 16 cores busy, two runs at once), the status teardown failed 20 times in 240 without the wait and 0 times in 240 with it. Alternating real runs of both files failed in 5 of 18 on main and 0 of 10 with the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2344ce0722 |
test: stop test runs leaving temp dirs behind (#2622)
Every full test run left about 40 directories in the temp dir, even when it passed. On the Windows dev machine about 16,000 had piled up by mid-October. A full run with TEMP pointed at an empty folder showed where each one came from: - function-ref (20) and ts-field-classification (3): each case's `finally` cleared `tmpDir` right after `cg.destroy()`, so the `afterEach` that removes the dir found nothing to remove. - npm-sdk (3) and liveness-watchdog (3) never removed the dirs they made. - extraction's #1502 suite (3) made a dir in `beforeEach` and had no `afterEach`; pr19-improvements' schema-version case (1) made one inline. - setup-home-sandbox (6 on Windows): Vitest runs no hooks for a file whose every test is skipped, so each platform-gated file left its home sandbox. Each sandbox now records itself in a per-run registry, and a global teardown removes whatever is still listed and names any dir still in use. With the fix, the same full run leaves nothing behind but Node's own reused compile cache. test-home-sandbox gains two tests: a file's home is listed in the run's registry, and the teardown removes every listed home, then the registry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1981354476 |
fix(cpp): of two overloads alike but for a const, like protobuf's DynamicCastMessage(const MessageLite*) and DynamicCastMessage(MessageLite*), a call reaches the one C++ picks (#2618)
Overload fit by argument type (cppArgumentTypeChoice) scored a `const T*`
parameter and its `T*` twin alike, and the one declared first won: protobuf's
`DynamicCastMessage<T>(ptr.get())` on a `std::shared_ptr<Message>` took the
`const MessageLite*` overload.
- cppConstTwinChoice: of the best-fitting overload's twins, alike but for
the `const` of a pointer or reference parameter, the one that adds `const`
to the fewest arguments.
- cppWrittenTypeFit: a pointer or reference to a non-const type never takes
a const object, nor a reference a value a call returns (cppNamesObject
tells a named object from a call's result); an object is no pointer, nor a
pointer to characters a pointer to another type (or the reverse). None of
that for an argument of a template parameter's type, and the pointer and
`const` rules not for a parameter the function's own template parameter
types (it deduces the `const`).
- The argument's type keeps a `const` its declaration writes in front
(`const Buffer* b`, `constexpr char key[]`), which the declarator regex
leaves out; an array is a pointer to its element; `&ref` is a pointer to
the referee; cppLibraryElementOf keeps a `const` in a holder's template
argument (`std::shared_ptr<const Message>`).
- Template headers are read 8 lines up, not 3 (fmt's `join`).
A/B on the cgstd kit against main
|
||
|
|
0893606ed5 |
fix(cpp): a call on what a container's front()/back()/at() or a holder's get()/value() returns, like rocksdb's edits.back()->MarkAtomicGroup(…), calls the element's method (#2616)
A call on what a library container hands out linked to nothing: the
standard library's `front()` returns nothing the index can read, and the
library-receiver gate keeps the name strategies off a `std::vector`.
cppLibraryElementOf reads what such a member returns, where the
container's type is written:
- `front()`, `back()` and `at(i)` of a sequence (std/absl, and the project
sequences cppRangeElementOf already treats as such) → its element;
- a library map's `at(key)` → the mapped value;
- `std::stack`/`std::priority_queue` `top()`, `std::queue` `front()`/`back()`
→ the element;
- `get()` of `std::unique_ptr`/`std::shared_ptr`/`absl::NoDestructor` → a
pointer to the held type, `value()`/`value_or()` of an optional or an
`absl::StatusOr` → the held type.
It is asked in cppChainInnerType (a chain `recv.front().m()`) and in
cppExpressionType (an `auto` local, a range, an argument), next to
cppLibraryIteratorOf.
A/B on the cgstd kit against main
|
||
|
|
98127e54d0 |
fix(c): a header's definition for a flag nothing sets, like lua-cjson's #ifdef USE_INTERNAL_FPCONV fpconv_init, gives way to the source file the build compiles (#2615)
lua-cjson's fpconv.h defines `static inline void fpconv_init()` only under
`#ifdef USE_INTERNAL_FPCONV` and otherwise declares it `extern` for fpconv.c
to define. Nothing in redis's tree sets the flag, so its build calls
fpconv.c's, but #2613's rule (the file's own or an included header's
definition first) took the header's.
c-feature-variants.ts decides whether a line is compiled only by a build
that sets a flag nothing sets. It reads the conditionals around the line
(cpp-macro-visibility's directiveLines, now exported): `#ifdef`/`#ifndef`,
and `#if` with defined, !, &&, ||, parentheses, integers and bare flags,
then `#elif` and `#else`. A flag is set nowhere when the line's own file
doesn't define it, no build file from the project root down to the
definition or the call names it, and no C/C++ file of their components
(the tree under the nearest directory with a build file) defines it.
Reserved and platform names stay unknown.
cppFunctionOverload's C branch then takes a non-static, non-variant
definition outside the file's tier when one exists, when every definition
the file sees is such a variant, and when the call itself is compiled by
the default build. A branch around both the definition and the call decides
nothing: redis's quicklist.c tests call their own `static mstime` inside
`#ifdef REDIS_TEST`.
A/B against main
|
||
|
|
dc4ccf6b14 |
fix(c): a C call, like redis's mstime(), reaches the function its own file can see, past another file's static copy (#2613)
A C call now links to the function of its name the call's own file can see:
its own, or one a header it includes defines, else one with external
linkage, the best placed of them (cppFunctionOverload's C branch). Before,
the name strategies took the function nearest the call; when that was
another source file's `static`, isVisibleAcrossFiles dropped it and the
call was left unlinked, so no call reached redis's `server.c` `mstime()`
or `ustime()`.
- A source file's `static` function is visible to a file that `#include`s
that source file: hiredis's async.c includes dict.c, redis's ae.c its
event-loop backend.
- The coordinator's `resolveImport` hook falls back to matchByFilePath for
a C/C++ `#include` the import resolver can't place, as resolveOneInner
already does, so cppIncluders sees includes only the end of their path
names: protobuf's `google/protobuf/descriptor.upb.h`, which two generated
copies end in, and jemalloc's `jemalloc/internal/*.h`. Standard headers
are left out: otherwise `#include <math.h>` makes jemalloc's
test/math.h visible to its own include through isIncludedCppTestSuite.
A/B on the cgstd kit against main
|
||
|
|
4a591aeee8 |
fix(cpp): a call to an overloaded free function, like protobuf's ParseWithLimit(limit, data, result), reaches the overload its arguments fit (#2612)
A C++ call to a function outside a class took whichever same-named
function the name strategies ranked first, by file and line proximity,
never by its arguments. Methods already picked their overload by argument
count, string literals and written argument types (cppOverloadFor);
functions never did. protobuf's `ParseWithLimit(limit, data, result)` on an
`int32_t result` reached the `int64_t&` overload beside the call, and
rocksdb's `NewLRUCache(cache_opts)` the overload taking a `size_t
capacity`.
Exact, qualified (global, exact, partial) and macro-namespace matching now
keep their choice of function, then let the call's arguments pick among its
overloads, the functions of its qualified name the call's file can see
(cppFunctionOverload, with cppOverloadFor's count-then-type fit; the
macro-namespace scorer breaks its ties by type). The choice stays on a tie.
- An overload only another source file sees (another `.cc` file's
`static`, an unnamed namespace's) never competes. When the name strategy
had picked such a function, which was then rejected, the call now links
to the overload its file sees: leveldb's `GetLengthPrefixedSlice(&input,
&key)` reached memtable.cc's static and linked nothing.
- A function hides a class of its name, so a call to a name both have
reaches the function.
- Only C++ calls: protobuf's Rust FFI calls lost their `extern` targets to
the C definitions when every language went through it.
Argument types read better too: a parameter named like the function it
belongs to, protobuf's `std::string u64(uint64_t u64)`, was read as the
function's own declarator, so `u64` looked like a `std::string`.
Validated on the cgstd kit against main (
|
||
|
|
627018eb25 |
fix(cpp): an explicit specialization, like protobuf's WireFormatLite::ReadPrimitive<int32_t, WireFormatLite::TYPE_INT32>, is named after its template (#2609)
An explicit specialization of a function or member function template kept
its template arguments in its name: protobuf's `template <> bool
WireFormatLite::ReadPrimitive<int32_t, WireFormatLite::TYPE_INT32>(…)` was
named `TYPE_INT32>` and qualified `…::ReadPrimitive::TYPE_INT32>`, the
qualified id split at the `::` inside its arguments; rocksdb's `template <>
int64_t Deserialize<int64_t>(…)` was `Deserialize<int64_t>`. A call writes
the bare name, so nothing linked to them.
Both extractors (wasm and the native kernel) now name a specialization
after its template, from the template-id's own name, and take its receiver
from the text before the declarator's name. That text must end in a real
`::`: tree-sitter recovers Metal's `vertex VertexOut vertexShader(…)` with
a missing, zero-width one.
A specialization now meets its template among a name's candidates, so the
resolver chooses between them wherever it picks among C++ candidates (a
class's methods, the implicit-this walk and its bases, exact, qualified,
global, fuzzy and macro-namespace matching):
- a call that writes template arguments reaches the specialization
written with the same ones (spacing aside, or qualified further on one
side), else the template itself or an ordinary overload, else nothing:
fmt's `num_bits<unsigned short>()` stays on `num_bits<T>()` instead of
the nearer `num_bits<uint128>()`; protobuf's `Arena::Create<T>` keeps
calling `DefaultConstruct<T>`, not the tests' `DefaultConstruct<
DispatcherTestProto>` hook; a dependent `ReadPrimitive<CType,
DeclaredType>(…)` links nowhere rather than to the first specialization.
- a call that writes none, or `<>`, reaches a specialization only when
nothing else has the name.
- a class's own member template, specialized but none for these
arguments, ends the lookup there instead of falling to a base's
namesake.
The `cpp-override` pass leaves specializations out on both sides: a member
template is never virtual.
Validated on the cgstd kit against main (
|
||
|
|
f7951a13db |
fix(cpp): a method a macro of its file defines, like protobuf's PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor, number, int), is indexed where the macro is invoked (#2605)
protobuf declares its descriptor accessors in their classes and defines
them below with macros, like `PROTOBUF_DEFINE_ACCESSOR(FieldDescriptor,
number, int)`; its reflection pastes `Reflection::Get##TYPENAME` in
another. The parser never sees those definitions (the preParse blanks the
invocations) and a declaration mints no node, so the methods had none and
every call to them linked to nothing.
- After either extractor (the kernel or the wasm walker), a step over the
original source expands the file's own function-like macros whose body
defines `Class::method(...) ... {`, at each invocation at namespace
scope, and mints a method node per definition: named and qualified like
an out-of-line definition, contained by the file and the same-file
class, with the expanded head as its signature. Constructors,
destructors and variadic macros are left out.
- The parse worker leaves the raw kernel fast path only for a file that
may define one (a cheap text test), so the step sees it.
- The C++ resolver reads such a method's return type and parameters from
its signature, and never takes it for a deleted overload.
A/B vs main on the kernel path, every changed site triaged: leveldb and
redis identical; fmt +13 nodes and rocksdb +11 (gtest's TestEventRepeater
overrides); protobuf +257 nodes, 6,203 calls and 2 references added, 252
calls retargeted, 11 metadata-only, none removed. Kernel and wasm dumps
are byte-identical, protobuf's one pre-existing Swift line aside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
4727a068e4 |
fix(cpp): a call on an element read by subscript, statuses[i].IsNotFound(), calls the element's method (#2602)
The extractor records a call on a subscripted receiver, like `statuses[i].IsNotFound()`, `buffers[key].Flush()` or `iters[i]->Valid()`, by its bare name, which went to whichever class had a method of that name, and a local an element initializes (`auto& s = statuses[i]`) had no type. - C++ expression typing reads a subscript: an array's element, what a pointer points at, a library map's mapped value, a sequence's element, or what a class's `operator[]` returns. - Declarations record their array dimensions (locals, parameters and data members, also through an alias), so a subscript, `*arr`, `arr->` and a range-for over an array read the type the array is declared with. - The bare-name receiver reader takes subscripts after a name or `(*name)`, reading the line with its literals blanked. - Overload arguments use the same subscript typing: a map's `values[key]` is its value type, no longer a `std::pair`. A/B vs main, every changed site triaged: leveldb 12 removed, 14 added, 1 retargeted, 11 metadata-only; fmt 6/4/10/2; redis identical; rocksdb 513/292/843/807; protobuf 58/5/34/66. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f951e597b |
fix(cpp): a call on the member of the pair a map's iterator points at, it->second.Flush(), calls the value's method (#2601)
The extractor records `it->second.Flush()`, `kv.second.Flush()` and `(*it).first.Print()` by their bare names, which the name strategies gave to whichever class had a method of that name — 45 `std::string::c_str()` calls went to googletest's `FilePath::c_str` — and a local such a member initializes (`auto& v = it->second`) had no type. `x.first` / `x.second` (and `->`) on a `std::pair` now type as the pair's elements in C++ expression typing (cppPairMember), so a map's iterator, range-for element or declared pair reaches its key and value, and #2598's bare-name receiver reader also takes `name->second`, `name.first` and `(*name).second`. A member typed by another class template's parameter (`CoreLocalArray<T>::AccessElementAndIndex()`'s `std::pair<T*, size_t>`) goes on as before: only the calling function's own template parameter names no particular class. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
18efc8c95f |
fix(cpp): a call on a dereferenced iterator or pointer, (*it)->Visit(), calls the method of what it points at (#2598)
The extractor records `(*it)->Visit()` and `(*node).Weight()` by their bare names, which the name strategies then gave to whichever class had a method of that name (0.4-0.7 guesses), and a local a dereference sets, `auto first = *nodes.begin()`, had no type. `*x` and `x->` in C++ expression typing now dereference a container's iterator to its element as well as pointers and holders (cppDereferenced, used by cppExpressionType, cppMemberObject and cppChainInnerType), and a bare-name call written on `(*name)` (read at the call's column, where the receiver starts) is resolved on the dereferenced value's class by C++ lookup before the name strategies run: the method, or no project method for a library type, a template parameter's, or a class that lacks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c29caf2ae8 |
fix(cpp): an auto local holding a library container's iterator calls the element's method through -> (#2579)
`auto it = logs_.begin(); it->IsSyncing()` had no type for `it`: a call on a library container (`std::deque<LogWriterNumber>`) returned nothing the auto-local reader could use, so `->` calls on it were matched by the method's name. A standard or abseil container's `begin`/`end`/`find`/ `lower_bound`/`rbegin`/… now types as `<container>::iterator`, and a standard search (`std::lower_bound`, `std::find_if`, `std::next`, …) as the iterator it is given first; #2576's cppArrowTarget then reaches the container's element. The element is looked up where the container's type is written (the `auto` local's declared site, a data member's class), not where the call is: an element nested in another class (`vs->obsolete_files_.begin()`) finds that class's type, not a namesake visible at the call. A holder's type stays looked up at the call: from a pimpl member's header the lookup finds a namesake (protobuf's other `DescriptorIndex`) or nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8fe3156992 |
fix(cpp): a -> call on a declared container iterator calls the element's method (#2576)
`std::deque<Slice>::const_iterator it; it->data()` was matched by the method's name alone, so it could reach any class with a `data`, and `itr->c_str()` on a `std::vector<std::string>::const_iterator` reached gtest's `FilePath::c_str`. A `->` on a value of an iterator type now reaches the container's element, as `->` on a smart pointer reaches what it holds: the element is read from the container's template arguments (cppRangeElementOf), before the container's own `iterator` alias would lose them (protobuf's `RepeatedPtrField<T>::iterator`), and looked up as C++ does. A library element (`std::string`) or the caller's template parameter calls no project method. An element only a class template's own `begin()`/`operator*` writes with its parameter (googletest's `ParamGenerator<T>::iterator`) goes on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c71a3d0373 |
fix(cpp): a receiver declared as a class template's member type, like SkipList<Key, Comparator>::Iterator, is that template's member (#2573)
The declarator regex read a type as a name plus one template argument list, so `SkipList<Key, Comparator>::Iterator iter(&list);` was read from its last name alone and looked up from the calling function: leveldb's skip-list tests linked `iter.Valid()` to `leveldb::Iterator::Valid`, rocksdb's to `IteratorBase::Valid`, and rocksdb's SkipListRep iterators linked their `iter_` member's calls (`InlineSkipList<…>::Iterator iter_;`) to their own methods. The type is now read whole, `Outer<Args>::Member`, and C++ lookup finds the member in the class template. A library template's member read this way is a library type only as an iterator (`std::vector<int>::iterator`, never a project class named `iterator`). Others name another type that may be the project's (`std::vector<Slice>::value_type`, gmock's `typename std::tuple_element<N - 1, MatcherTuple>::type matcher`), so their calls go on as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8f1f842b5b |
fix(cpp): a receiver type written from the global scope is looked up there, and a far lowercase outside type reaches no namesake (#2569)
The two follow-ups #2567 and #2568 left open. A receiver declared with a leading `::` lost it: the declarator regex started a type at a letter, so `::upb::Arena& a` inside `namespace hpb` was looked up from `hpb` and found a namespace-local `hpb::upb::Arena`. The declared type now keeps a leading `::` where one can stand (not after a name, a template's arguments or a `)`, so `SkipList<Key, Comparator>::Iterator iter` is not read as `::Iterator`), and resolveCppAliasedType no longer takes a function-local `using Foo = Bar;` for `::Foo`. A lowercase outside type declared outside the calling function still used the last-name typed match: #2413's library rule skips such declarations, and #2567's namesake gate took capitalized types only. So a far `absl::flat_hash_set<int> seen` linked `seen.insert(v)` to the project's `util::flat_hash_set::insert`, and a far `std::string text` linked `text.clear()` to a global project `string` class. The gate now covers lowercase types, which are decided as when no class shares their name, and cppFindsOnlyNamesakes reads a `std::` name exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
759acbbccb |
fix(cpp): an alias of a type written from the global scope names that global type (#2568)
protobuf's hpb declares `class Arena { … backend::Arena arena_; }`, where
`namespace backend = hpb::internal::backend::upb;` and that namespace says
`using Arena = ::upb::Arena;`. cppTypeName dropped the leading `::`, so the
alias's target was looked up as `upb::Arena` from the alias's own scope,
whose nearest `upb` is that very namespace: the alias named itself, the
lookup ended on no class, and `arena_.Fuse(other.arena_)` fell back to the
type's last name, linking `hpb::Arena::Fuse` (and `IsFused`, `RefArena`) to
itself instead of `upb::Arena::Fuse`.
TypeName now records a leading `::` (`global`), and a global name is looked
up from the global scope alone: when an alias's target is followed (follow,
and the scope expand looks the target up from and hands on), in a class's
base clause (baseClassesOf), and in cppAliasedTypeWritten, which #2532's
`auto` locals and chains walk alias by alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
00d7430ebb |
fix(cpp): a qualified receiver type never reaches a class that only shares its last name, and rocksdb::DB is RocksDB's own (#2567)
A C++ receiver declared with a namespace-qualified type is from outside the project only when no class of the project can be what that name names. Three cases read that wrong on main: - A namespace a macro stands for. RocksDB declares its classes in `namespace ROCKSDB_NAMESPACE` under `#define ROCKSDB_NAMESPACE rocksdb`, so the index names them `ROCKSDB_NAMESPACE::DB`, and #2532's outside rule (cppProjectScopeNames) never saw `rocksdb`: `rocksdb::DB* db; db->Put(…)` got no edge. cppProjectScopeNames now adds the name a single-name macro stands for, in both directions (cppNameMacros, the map cppNamespaceMacros already builds). - A lowercase outside type beside a project namesake. isCppLibraryType (#2413) took a non-std lowercase type as the library's only when no project type had its name at all, so `absl::flat_hash_set<int> seen; seen.insert(…)` reached a project's own `util::flat_hash_set::insert`. It is now also the library's when every project type of that name is a namesake (cppFindsOnlyNamesakes). - A capitalized type whose last name finds only namesakes, on the paths #2493's call-site rules leave to the last-name match: a declaration read outside the calling function (protobuf's `absl::Status _status` in its RETURN_IF_ERROR macro body reached `upb::Status::ok`; `auto bytes = ::hpb::Serialize(…); bytes.value()` reached `hpb::StatusOr::value`), and a namespace the project uses (its generated `DescriptorProto::ExtensionRange`, which the index doesn't hold, reached `Descriptor::ExtensionRange::options`). That call now gets no edge, as isUndeclaredTypeName rules where no class shares the name; `->` on a value still goes to the name strategies, and a template parameter's member is left alone. cppFindsOnlyNamesakes: every type of the last name, and every owner of a method of the called name (the index can hold a class's methods without the class), has a qualified name that ends unlike the written one, neither the other's tail; namespace aliases are expanded and macro-named namespaces read both ways. A name a project type qualifies, an unqualified name and an unbalanced `<>` are never judged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
eec9288a56 |
fix(react): a tag written in a comment or a string renders no JSX child (#2564)
The jsx-render pass read tag names off each parent's raw lines, so text
that only mentions a tag rendered it: react-router's hooks rendered the
`Router`, `Routes` and `Route` their JSDoc, comments and invariant
messages name ("You cannot render a <Router> inside another <Router>"),
shadcn's `useCarousel` the `Carousel` its error names, and layouts the
markup commented out of them (`{/* <Progress /> */}`).
The pass now scans a code-only view of each file (`jsxCode`, new in
src/resolution/jsx-code.ts): comments and the contents of string,
template and regex literals and of JSX attribute strings are blanked,
offsets and line breaks kept. The lexer follows JSX elements, so a quote,
slash or backtick in JSX text (`Don't`, `https://…`) stays text, and it
re-reads a `<` that opens a type parameter list (`<T,>(x: T) =>`) as an
operator. A file it can't read as one program (an unbalanced brace, a
string open at a line's end, a mismatched closing tag) is scanned as it
is, as before. TypeScript `.ts` files are skipped outright: they can't
hold JSX. The lexer's state lives in a class whose methods every file
shares: closures made per file sent V8 back to recompile it for file
after file.
Checked against TypeScript's parser on 36 React repos (7,649 files,
62,377 `<Name` matches): no real tag blanked, no file given up on, 614
non-JSX matches removed. Graph A/B on 12 repos: -36 jsx-render edges,
+0, nothing else changed; the TypeScript oracle grades all 36 as tags in
comments or strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
3f714bcc48 |
fix(jsx): a .jsx component links to the file-scope values it reads, as in .js and .tsx (#2561)
Value-reference edges (a reader symbol -> the file-scope const/var it reads, which impact follows) ran for typescript, tsx and javascript but not jsx. A .jsx file is its own language, parsed with the JavaScript grammar, so the same component had no edge to the lazy page it renders (`<LoginPage />` under `const LoginPage = Loadable(lazy(...))`), the route table or the config it maps over as .jsx, and all of them as .js or .tsx. Every other JS-family gate in the extractor lists jsx, and nothing in the history says this one was left out on purpose: the kernel mirrored the gap with an explicit "NOT jsx" check for parity. Add jsx to VALUE_REF_LANGS and drop the kernel's jsx exclusion. On mantis, matx, berry, mastodon and bigbluebutton-html5 the index only gains edges (35, 56, 15, 28 and 266): no node, ref or file changes and no edge removed. Every added edge is a same-file read of a file-scope value that is the only binding of its name in the file. A repo with no .jsx files indexes byte-identically, and kernel-parity.mjs shows 0 diffs over those repos with the same deferrals as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8206c8c271 |
fix(react): a JSX tag imported from the project renders what its module exports (#2563)
The JSX render pass matched a tag by name and used the file's imports only
to break ties, so an index that forwards another module's component
declared no candidate and the tag took a namesake elsewhere: outline's
Sidebar/index.ts is `import Sidebar from "./App"; export default Sidebar`,
and <Sidebar> rendered Aside.tsx's styled Sidebar, not the AppSidebar that
App.tsx exports as observer(AppSidebar).
A tag imported from a module of the project now renders what that module
exports under the imported name, read by exportedComponent, the reader
lazy routes use: through an index or barrel, past a wrapper call. Two rules
join that reader, for routes too: a const holding another binding is that
binding (`const Avatar = AvatarWithHoverCard; export { Avatar }`; a let may
be reassigned, so it stays), and a memo wrapper's bodyless component node is
the component it wraps (`const MemoizedBlurhash = memo(Blurhash)`). A module
whose export is no component node (`export default () => ...`) links nothing
rather than a namesake. Sync records every file a tag's import is read
through as a synthesis input, so rewiring only the index redraws the tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d0bb9be51e |
fix(react): a component a file declares itself as a value is what its JSX renders (#2420)
A PascalCase name that a .tsx/.jsx file declares as a module-level value is
that declaration wherever the file uses it: a JSX tag, a route's element, a
layout or a TSX type. Its initializer decides what it renders, in order:
- the component of the module its lazy loader loads, or the export the
loader picks, in either branch of a conditional;
- the function it wraps (`observer(function X() {…})`,
`forwardRef(Inner) as {…}`);
- else the declaration itself.
Before, the lookup by kind skipped the value and took a lone same-named
component anywhere, often in another app of the repository.
- Pattern 1 asks declaredComponent about every PascalCase reference, not
only route references. layoutComponent and the jsx-render synthesizer's
jsxChild ask it too.
- valueLoader reads a value's loader with loaderValue (#2480).
- defaultExportedName reads `export default observer(Login)`.
- lazyValueModules records the modules a file's lazy values load as
synthesis inputs, so a sync that changes one of them redraws the tag.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
9b237093a3 |
fix(sync): a lazy route relinks a page that appears below its barrel (#2560)
A lazy React Router route whose module is a barrel, and whose page sits in a folder below the barrel's, stayed unlinked after `codegraph sync` added the page or gave it its export, though a fresh index linked it. In DigiShop.Frontend, `@/pages` is `src/pages/index.ts`, which imports `./Home`, `src/pages/Home/index.tsx`, and exports it again. While the page is missing, the route's reference fails and waits under the key of the module it names, `module:pages` (#2422). The retry looks failed references up by the keys of the changed files, and a file in `pages/Home/` gives only `module:Home` and its own stem. The limit, which the `export *` barrels of #2436 share, was recorded with #2558. After the failed-ref retry, which makes the barrel's import edge into an added file, sync now walks up the import edges from each file it added, or gave a definition, to the barrels that pass it on: `export … from`, or an import exported again (`import { X } from './x'; export { X }`, `export default X`), as deep as the resolver chases a name. It re-opens, for the sweep, the failed references waiting under those barrels' keys. Only keys a failed reference waits under are looked up, in one DISTINCT over the failed rows parked under module keys, so a sync with none reads nothing, and a body-only edit seeds nothing. The ImportReader that the added-files reopen (#2512) uses is shared, and gains exportedBindings, behind a regex pre-check, since most importers export nothing. defaultExportBinding moves to alias-binding.ts for this walk and for react.ts's exportsBinding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |