mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +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>
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
/**
|
|
* VB.NET runs on the same .NET base library as C#, so a call such as
|
|
* `x.Contains(…)`, `x.Add(…)` or `x.Dispose()` on a receiver whose type is not
|
|
* known is far more likely the library's method than the one project method
|
|
* that happens to share the name. As in C#, such a guess is kept only when the
|
|
* receiver is named after the method's owner. staxrip's
|
|
* `SupportedInput.Contains(ret)` — a String array — went to `VideoScript`'s
|
|
* `Contains`.
|
|
*/
|
|
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-std-'));
|
|
const files: Record<string, string> = {
|
|
'Video/VideoScript.vb': `Public Class VideoScript
|
|
Public Function Contains(ByVal value As String) As Boolean
|
|
Return False
|
|
End Function
|
|
End Class
|
|
`,
|
|
'Audio/AudioProfile.vb': `Public Class AudioProfile
|
|
Public Function IsInputSupported(ByVal inputs As Object, ByVal ext As String) As Boolean
|
|
For Each supported In inputs
|
|
If supported.Contains(ext) Then Return True
|
|
Next
|
|
Return False
|
|
End Function
|
|
|
|
Public Function HasScript(ByVal scripts As Object, ByVal name As String) As Boolean
|
|
For Each script In scripts
|
|
If script.Contains(name) Then Return True
|
|
Next
|
|
Return False
|
|
End Function
|
|
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 });
|
|
});
|
|
|
|
/** `Owner::member` of every call edge out of one method. */
|
|
function callsOf(qualifiedName: string): string[] {
|
|
const from = cg.getNodesInFile('Audio/AudioProfile.vb').find((n) => n.qualifiedName === qualifiedName)!;
|
|
return cg
|
|
.getOutgoingEdgesFrom([from.id])
|
|
.filter((e) => e.kind === 'calls')
|
|
.map((e) => cg.getNode(e.target)!.qualifiedName)
|
|
.sort();
|
|
}
|
|
|
|
describe('VB.NET standard-library method names', () => {
|
|
it('are not a project method on a receiver that does not name its owner', () => {
|
|
expect(callsOf('AudioProfile::IsInputSupported')).toEqual([]);
|
|
});
|
|
|
|
it('still reach the project method when the receiver names its owner', () => {
|
|
expect(callsOf('AudioProfile::HasScript')).toEqual(['VideoScript::Contains']);
|
|
});
|
|
});
|