mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 05:41:18 +08:00
489b4749c7
VB.NET's grammar tags each member of a `Structure` as its own `body`
field, so extractAggregate's field lookup saw only the first member.
SCrawler's UserMedia indexed none of its members, and a structure that
opened with a nested enum indexed only the enum. Aggregates now resolve
their body the way classes and enums do (resolveBody, which VB maps to
the declaration itself). Objective-C, the only other struct language
with that hook, is unchanged.
Property bodies were never walked: the propertyTypes branch set
skipChildren after extractProperty, so calls, instantiations and reads
in a VB `Get`/`Set` block, `= initializer` or `As New`, and in a C#
accessor body or `=> expr`, were lost.
- propertyBodies() names the parts of a property that run code (VB: the
whole declaration, as its methods are walked; C#: each accessor's body
and an arrow value), walked with the property on the stack. The
candidates-only fn-ref scan skips the walked subtrees by node id, so a
function value is captured once, from the property. C# `= initializer`s
stay unwalked, as C# field initializers are.
- The C# kernel mirrors it (property_bodies, scan skip list). The parity
suite and a new micro pass; a serilog sweep is 209/214 byte-identical
with 0 diffs (5 deferred for parse errors).
- VB field declarators (`= expr`, `As New T`) and Custom Event
AddHandler/RemoveHandler/RaiseEvent blocks are walked as their field or
event.
Before/after (nodes / edges): SCrawler 9,950 -> 10,427 / 14,336 -> 15,092;
staxrip 13,440 -> 13,644 / 24,120 -> 28,002, most of it its
`Property X As New NumParam With {...}` settings; serilog edges
6,642 -> 6,654. All 23 removed edges are the same call sites re-resolved
by VB's name-guessing resolver now that struct members are candidates:
10 wrong edges dropped, 9 correct ones now guessed wrong, 4 wrong either
way. Designer -> Add/Size edge counts are unchanged.
EXTRACTION_VERSION 27 -> 28.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.6 KiB
TypeScript
92 lines
3.6 KiB
TypeScript
/**
|
|
* C# property accessor bodies belong to the property. `get { … }`,
|
|
* `set { … }`, `get => …` and an expression-bodied `=> …` property used to
|
|
* be skipped by both extractors, so the calls, instantiations and static
|
|
* reads written there were lost and a getter's callee looked unused.
|
|
*
|
|
* Runs against the native kernel (when built) and the wasm extractor, which
|
|
* must agree.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
import type { Node } from '../src/types';
|
|
|
|
const FILES: Record<string, string> = {
|
|
'Helper.cs': `public static class Helper {
|
|
public static int Compute(int n) => n;
|
|
public static void Store(int n) {}
|
|
public static int Arrow() => 1;
|
|
public static int GetA() => 1;
|
|
public static void SetA(int v) {}
|
|
}
|
|
public class Widget {}
|
|
public static class Defaults { public const string Name = "x"; }
|
|
`,
|
|
'Box.cs': `public class Box {
|
|
private int _x;
|
|
public int Value {
|
|
get { return Helper.Compute(_x); }
|
|
set { Helper.Store(value); _x = value; }
|
|
}
|
|
public int Arrow => Helper.Arrow();
|
|
public int Both { get => Helper.GetA(); set => Helper.SetA(value); }
|
|
public Widget Made { get { return new Widget(); } }
|
|
public string Label { get { return Defaults.Name; } }
|
|
private void Handle(int v) { }
|
|
public System.Action<int> Handler { get { return Pick(Handle); } }
|
|
}
|
|
`,
|
|
};
|
|
|
|
describe('C# property accessor bodies', () => {
|
|
let root = '';
|
|
let cg: CodeGraph | undefined;
|
|
let kernel: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cs-accessors-'));
|
|
for (const [rel, content] of Object.entries(FILES)) fs.writeFileSync(path.join(root, rel), content);
|
|
kernel = process.env.CODEGRAPH_KERNEL;
|
|
});
|
|
|
|
afterEach(() => {
|
|
cg?.close();
|
|
cg = undefined;
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
if (kernel === undefined) delete process.env.CODEGRAPH_KERNEL;
|
|
else process.env.CODEGRAPH_KERNEL = kernel;
|
|
});
|
|
|
|
it.each(['default', 'wasm'])('are walked as the property (%s)', async (backend) => {
|
|
if (backend === 'wasm') process.env.CODEGRAPH_KERNEL = '0';
|
|
else delete process.env.CODEGRAPH_KERNEL;
|
|
cg = await CodeGraph.init(root, { index: true });
|
|
const graph = cg;
|
|
const member = (qualifiedName: string): Node => {
|
|
const node = graph.getNodesInFile('Box.cs').find((n) => n.qualifiedName === qualifiedName);
|
|
expect(node, qualifiedName).toBeDefined();
|
|
return node!;
|
|
};
|
|
const targets = (node: Node, kind: string): string[] =>
|
|
graph
|
|
.getOutgoingEdgesFrom([node.id])
|
|
.filter((e) => e.kind === kind)
|
|
.map((e) => graph.getNode(e.target)!.qualifiedName)
|
|
.sort();
|
|
|
|
expect(targets(member('Box::Value'), 'calls')).toEqual(['Helper::Compute', 'Helper::Store']);
|
|
expect(targets(member('Box::Arrow'), 'calls')).toEqual(['Helper::Arrow']);
|
|
expect(targets(member('Box::Both'), 'calls')).toEqual(['Helper::GetA', 'Helper::SetA']);
|
|
expect(targets(member('Box::Made'), 'instantiates')).toEqual(['Widget']);
|
|
expect(targets(member('Box::Label'), 'references')).toContain('Defaults');
|
|
// A method passed as a value inside an accessor is the property's
|
|
// reference, captured once — not the class's as well.
|
|
expect(targets(member('Box::Handler'), 'references')).toContain('Box::Handle');
|
|
expect(targets(member('Box'), 'references')).not.toContain('Box::Handle');
|
|
expect(targets(member('Box'), 'calls')).toEqual([]);
|
|
});
|
|
});
|