mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
c9944147bb
VB.NET's resolver guessed a receiver call's target from the receiver's name whenever no class matched it, without any of the guards C# has. Indexing Structure members (#2345) gave those guesses new targets: 23 SCrawler `xxxFile.Delete()` calls on PersonalUtilities' SFile went to a nested struct's `TempFileConversion::Delete`, and staxrip's main app sent 90 calls to its AutoCrop tool's duplicate `ColorHSL`. - Declared types (new src/resolution/vbnet-receivers.ts): a receiver's `As` type decides the call (local, parameter, field, property, `For Each`, `As New`, ...). The candidates are that type's member, one it inherits, or an extension method written for it. A type from outside the project gets no link. - .NET standard method names (`Add`, `Contains`, `Clear`, `Dispose`, `ToString`, ...) on an untyped receiver need a receiver named after the owner, as in C#. VB.NET matches them case-insensitively. - A bare call, or one on `Me`, reaches the enclosing class, what it inherits, or a Module. A nested type is matched by its bare name only from inside its owner. Types resolve through enclosing namespaces, `Imports` (aliases included) and the caller's project. A tie between equally good guesses goes to the caller's file, then its project, then the nearer directory, or gets no link. - vbReceiverOf reads the name at or after the reference's column, so `Me.Size = New System.Drawing.Size(...)` is read through `System.Drawing`, not `Me`. Before/after on main (nodes unchanged): SCrawler 15,092 -> 14,893 edges, staxrip 28,002 -> 26,647. Of the non-containment edges removed, 855 / 2,802 are the same link with new resolver metadata. The rest: - .NET names on untyped receivers no longer guessed: 196 / 1,378. 731 of staxrip's are StringBuilder `sb.Append` calls that had gone to `LogBuilder::Append`. - Other receiver guesses dropped: 86 / 103, including the SFile.Delete calls. - Bare calls out of scope: 38 / 834. 590 of staxrip's are `New Point(...)` that had gone to a class nested in `ButtonEx`; others are WinForms `Refresh()`, `Focus()` and `Activate()`. - Retargeted: 227 / 532, e.g. 208 bare `Add(...)` calls in encoder classes now reach the inherited `CommandLineParams::Add`. 121 / 958 call sites resolve that did not before, e.g. `cms.Add` -> ContextMenuStripEx and `td.AddButton` -> TaskDialog. Designer -> Size links fall from 5 / 1 to 0; designer -> Add stays 0. Known trade-off, shared with C#'s guard: about 31 staxrip calls like `switches.Join(BR)` that may be the project's own `MiscExtensions` extension methods are no longer linked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
/**
|
|
* VB.NET's extractor keeps only the last name of a member access, so
|
|
* `Me.Panel.Controls.Add(x)` and `New System.Drawing.Size(1, 2)` reach the
|
|
* resolver as bare `Add` / `Size`. A member reached that way must match what
|
|
* the line writes before the dot: `Me` / `MyBase` / `MyClass`, the member's
|
|
* own type or module (`Logger.Log(…)`), or a service locator's type argument
|
|
* (`GetService(Of Notifier).Notify()`). SCrawler's designer code sent
|
|
* `New System.Drawing.Size(…)` to a nested enum's `Size` case 713 times and
|
|
* `Controls.Add(…)` to a collection class's `Add` 547 times.
|
|
*
|
|
* The name read is the one the reference starts at (or after) — in
|
|
* `Me.Size = New System.Drawing.Size(…)` that is `System.Drawing.Size`, not
|
|
* `Me.Size` — and a type is one its written qualifier names: staxrip's
|
|
* designer `New System.Drawing.Point(…)` went to a nested `Point` class 550
|
|
* times. A type nested in a class is named bare only inside it.
|
|
*/
|
|
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
let root = '';
|
|
let cg: CodeGraph;
|
|
|
|
beforeAll(async () => {
|
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-vb-member-'));
|
|
const files: Record<string, string> = {
|
|
'Editors/UsersInfoForm.vb': `Namespace Editors
|
|
Friend Class UsersInfoForm
|
|
Private Enum EComparers
|
|
Name
|
|
Size
|
|
End Enum
|
|
End Class
|
|
End Namespace
|
|
`,
|
|
'Collections/DataColorCollection.vb': `Friend Class DataColorCollection
|
|
Friend Sub Add(ByVal Item As Object)
|
|
End Sub
|
|
End Class
|
|
`,
|
|
'Services/Notifier.vb': `Public Class Notifier
|
|
Public Sub Notify(ByVal Text As String)
|
|
End Sub
|
|
End Class
|
|
`,
|
|
'Logger.vb': `Public Module Logger
|
|
Public Sub Log(ByVal Text As String)
|
|
End Sub
|
|
End Module
|
|
`,
|
|
'MainForm.vb': `Public Class MainForm
|
|
Private Sub InitializeComponent()
|
|
Me.Panel.Controls.Add(Me.Button1)
|
|
Me.Panel.Size = New System.Drawing.Size(184, 25)
|
|
Logger.Log("ready")
|
|
Application.GetService(Of Notifier).Notify("ready")
|
|
Me.Refresh()
|
|
End Sub
|
|
|
|
Private Sub Refresh()
|
|
End Sub
|
|
End Class
|
|
`,
|
|
'UI/ButtonEx.vb': `Public Class ButtonEx
|
|
Public Class SymbolDrawer
|
|
Public Class Point
|
|
End Class
|
|
Public Sub Draw()
|
|
Dim p As New Point()
|
|
End Sub
|
|
End Class
|
|
End Class
|
|
`,
|
|
'Forms/AppsForm.vb': `Public Class AppsForm
|
|
Private Sub InitializeComponent()
|
|
Me.Size = New System.Drawing.Size(315, 205)
|
|
Me.ToolStrip.Location = New System.Drawing.Point(473, 10)
|
|
Me.lDescription.Location = New Point(4, 285)
|
|
End Sub
|
|
End Class
|
|
`,
|
|
};
|
|
for (const [rel, content] of Object.entries(files)) {
|
|
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, rel), content);
|
|
}
|
|
cg = await CodeGraph.init(root, { index: true });
|
|
});
|
|
|
|
afterAll(() => {
|
|
cg?.close();
|
|
if (root) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('VB.NET member access', () => {
|
|
it('reaches a member only through what the line names', () => {
|
|
const ids = cg.getNodesInFile('MainForm.vb').map((n) => n.id);
|
|
const targets = cg
|
|
.getOutgoingEdgesFrom(ids)
|
|
.filter((e) => e.kind === 'calls')
|
|
.map((e) => cg.getNode(e.target)!.qualifiedName)
|
|
.sort();
|
|
expect(targets).not.toContain('Editors::UsersInfoForm::EComparers::Size');
|
|
expect(targets).not.toContain('DataColorCollection::Add');
|
|
expect(targets).toContain('Logger::Log');
|
|
expect(targets).toContain('Notifier::Notify');
|
|
expect(targets).toContain('MainForm::Refresh');
|
|
});
|
|
|
|
it('reads the name the reference starts at, and a type its qualifier names', () => {
|
|
const targetsOf = (file: string) => cg
|
|
.getOutgoingEdgesFrom(cg.getNodesInFile(file).map((n) => n.id))
|
|
.filter((e) => e.kind === 'calls' || e.kind === 'instantiates')
|
|
.map((e) => `${e.kind} ${cg.getNode(e.target)!.qualifiedName}`)
|
|
.sort();
|
|
// `Me.Size = New System.Drawing.Size(…)`, `New System.Drawing.Point(…)`, `New Point(…)` outside ButtonEx.
|
|
expect(targetsOf('Forms/AppsForm.vb')).toEqual([]);
|
|
expect(targetsOf('UI/ButtonEx.vb')).toEqual(['instantiates ButtonEx::SymbolDrawer::Point']);
|
|
});
|
|
});
|