fix(vbnet): resolve receiver calls by declared type; guard .NET names, bare calls and ties (#2351)

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>
This commit is contained in:
Colby Mchenry
2026-10-05 17:32:53 +00:00
committed by GitHub
parent 355afc70b1
commit c9944147bb
9 changed files with 2235 additions and 6 deletions
+5
View File
@@ -19,6 +19,11 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In VB.NET, a field or property initializer like `= Compute()` or `As New List(Of Order)` now links what it calls and creates, and so do a `Custom Event`'s `AddHandler`, `RemoveHandler` and `RaiseEvent` blocks. Re-index VB.NET and C# projects after upgrading.
- In C#, a field or property initializer like `private readonly ILogger _log = LogManager.GetLogger(typeof(X));` or `public List<Foo> Items { get; } = new();` now links what it calls, creates and reads, including inside a lambda, and a target-typed `new()` there counts as creating the declared type. Before, initializers were skipped, so a method or class used only from one — like a converter created in a static list — looked unused, and a method passed as a value there was credited to the whole class instead of the field or property.
- In C# and VB.NET, a constant no longer stands in for a type with the same name: `new Station { … }` links to class `Station` even when another class declares `private const string Station`, `new Version(…)` no longer links to a `const string Version`, and a field like `static readonly Meter Meter` no longer points at itself. Thanks @EvanYu1980 for the report and @drakeo338. (#2337)
- In VB.NET, a call on a variable, parameter, field or property now reaches a method of the type it is declared with — one that type inherits, or an extension method written for it — and nothing when that type comes from outside your project, instead of any project method that merely shares the name.
- In VB.NET, a call to one of .NET's own methods such as `Add`, `Contains`, `Clear` or `Dispose` on a value whose type isn't known is no longer linked to a project method of that name, unless the value is named after that method's class.
- In VB.NET, a type name is looked up the way VB.NET does it — through the namespaces around it, the file's and project's `Imports` (aliases included), and the caller's own project — so a class declared in several namespaces or projects no longer draws every call to whichever copy was indexed first, and two candidates nothing tells apart get no link at all.
- In VB.NET, a call with no receiver, or on `Me`, now reaches only a member of the class it is written in, of a class it inherits, or of a Module, and no longer a nearby class's member of the same name.
- VB.NET designer code such as `New System.Drawing.Point(…)` or `Me.Size = New System.Drawing.Size(…)` is no longer linked to a project type or member that only shares the name, and a class nested inside another is matched by its bare name only from inside that class or one that inherits it.
- Upgrading CodeGraph while an agent session is open no longer leaves the old version's background server in charge of your project: the first session started from the new install stops it and starts a current one in its place, even while sessions opened before the upgrade are still running. That old server could no longer load the language parsers the upgrade removed, so it saved every file it re-indexed with no symbols, while new sessions could only read the index beside it without keeping it up to date. A background server from a newer install is never stopped, and sessions opened before the upgrade keep the old version until you restart them. Thanks @lipchey for the report. (#2335)
- A file is no longer saved with no symbols when its language parser can't be loaded, which is what happened to every file a background server re-indexed after an upgrade removed its install: the file keeps what it had and is indexed again once the parser loads, and files an earlier version emptied this way are re-indexed by the next sync. A background server also exits on its own once its install is upgraded or removed, so the next session starts one from the current install. Thanks @lipchey for the report. (#2335)
- `codegraph status` no longer says the index is up to date while indexed files are missing their symbols: it now names files the parser couldn't read and files stored without their symbols (which `codegraph sync` repairs), `status --json` counts both, and `codegraph files --json` lists each file's recorded errors. Thanks @lipchey for the report. (#2336)
+100
View File
@@ -0,0 +1,100 @@
/**
* A VB.NET call with no receiver — or with `Me` / `MyClass` / `MyBase`,
* which the extractor drops — is a member of the type it is written in, of a
* type around it, or of one they inherit; a `Module`'s members are reached
* from anywhere. Never another class's same-named member, however near its
* file: SCrawler's `{ToString()}` in the Instagram `UserData` (which
* `Inherits UserDataBase`) went to a nearby structure's `ToString`.
*/
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-bare-'));
const files: Record<string, string> = {
'API/Base/UserDataBase.vb': `Namespace API.Base
Friend MustInherit Class UserDataBase
Public Overrides Function ToString() As String
Return "user"
End Function
Protected Sub Refresh()
End Sub
End Class
End Namespace
`,
'API/Instagram/MediaItem.vb': `Namespace API.Instagram
Friend Class MediaItem
Public Overrides Function ToString() As String
Return "media"
End Function
Friend Sub Refresh()
End Sub
End Class
End Namespace
`,
'API/Instagram/UserData.vb': `Namespace API.Instagram
Friend Class UserData : Inherits API.Base.UserDataBase
Friend Sub SetTagsLimit()
Dim aStr$ = $"Enter the number of posts from user {ToString()}"
Me.Refresh()
Log(aStr)
End Sub
Private Class Counter
Friend Sub Tick()
Report()
End Sub
End Class
Private Shared Sub Report()
End Sub
End Class
End Namespace
`,
'Tools/Logger.vb': `Public Module Logger
Public Sub Log(ByVal Text As String)
End Sub
End Module
`,
};
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(file: string, qualifiedName: string): string[] {
const from = cg.getNodesInFile(file).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 receiver-less calls', () => {
it('reach the members the class inherits, not a nearer class’s, and a module’s', () => {
expect(callsOf('API/Instagram/UserData.vb', 'API.Instagram::UserData::SetTagsLimit')).toEqual([
'API.Base::UserDataBase::Refresh',
'API.Base::UserDataBase::ToString',
'Logger::Log',
]);
});
it('reach a member of the class around a nested one', () => {
expect(callsOf('API/Instagram/UserData.vb', 'API.Instagram::UserData::Counter::Tick')).toEqual([
'API.Instagram::UserData::Report',
]);
});
});
+35
View File
@@ -7,6 +7,12 @@
* (`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';
@@ -56,6 +62,24 @@ End Module
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)) {
@@ -84,4 +108,15 @@ describe('VB.NET member access', () => {
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']);
});
});
+143
View File
@@ -0,0 +1,143 @@
/**
* A VB.NET solution often carries the same type in two projects: staxrip's
* main app and its separate AutoCrop tool (its own AutoCrop.vbproj) each
* declare `ColorHSL`, `FrameServerFactory` and `DirectFrameServer`. A call
* in the main app means its own project's copy — about 90 calls such as
* `_backColor.AddLuminance(0.025)` went to the AutoCrop copy, whichever was
* indexed first. Between equally good guesses, the caller's project decides,
* then the nearer directory; when neither does, there is no guess.
*/
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;
const VBPROJ = `<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
</PropertyGroup>
</Project>
`;
const COLOR_HSL = `Public Class ColorHSL
Public Sub New(ByVal h As Double, ByVal s As Double, ByVal l As Double, ByVal a As Double)
End Sub
Public Function AddLuminance(ByVal offset As Single) As ColorHSL
Return Me
End Function
End Class
`;
const FRAME_SERVER_FACTORY = `Public Class FrameServerFactory
Public Shared Function Create(ByVal path As String) As Object
Return Nothing
End Function
End Class
`;
const HELPER = `Public Class Helper
Public Sub Run()
End Sub
End Class
`;
beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-vb-ties-'));
const files: Record<string, string> = {
'Source/StaxRip.vbproj': VBPROJ,
'Source/Tools/AutoCrop/AutoCrop.vbproj': VBPROJ,
'Source/Tools/AutoCrop/Main.vb': COLOR_HSL + FRAME_SERVER_FACTORY,
'Source/UI/ColorHSL.vb': COLOR_HSL,
'Source/Video/FrameServer.vb': FRAME_SERVER_FACTORY,
'Source/General/ThemeManager.vb': `Public Class ThemeManager
Public Sub Apply(ByVal palette As Object)
Dim _backColor As ColorHSL = New ColorHSL(0, 0.01, 0.1, 1)
Dim _controlBackColor As ColorHSL = _backColor.AddLuminance(0.025)
For Each backgroundColor In palette
backgroundColor.AddLuminance(-0.1)
Next
Dim server = FrameServerFactory.Create("video.mkv")
End Sub
End Class
`,
'Source/UI/TipProvider.vb': `Public Class TipProvider
Public Sub SetTip(ByVal text As String)
End Sub
Public Sub SetTip(ByVal text As String, ByVal title As String)
End Sub
End Class
`,
'Source/General/Tips.vb': `Public Class Tips
Public Sub Apply(ByVal providers As Object)
For Each tipProvider In providers
tipProvider.SetTip("x")
Next
End Sub
End Class
`,
'Plugins/A/A.vbproj': VBPROJ,
'Plugins/A/Helper.vb': HELPER,
'Plugins/B/B.vbproj': VBPROJ,
'Plugins/B/Helper.vb': HELPER,
'App/App.vbproj': VBPROJ,
'App/Main.vb': `Public Class Main
Public Sub Start(ByVal helpers As Object)
For Each runHelper In helpers
runHelper.Run()
Next
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 });
});
/** `Owner::member @ file` of every call / instantiation edge out of a file. */
function targetsFrom(file: string, kind: 'calls' | 'instantiates'): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === kind)
.map((e) => {
const target = cg.getNode(e.target)!;
return `${target.qualifiedName} @ ${target.filePath}`;
})
.sort();
}
describe('VB.NET duplicate types across projects', () => {
it('resolve a call to the caller’s own project’s copy', () => {
expect(targetsFrom('Source/General/ThemeManager.vb', 'calls')).toEqual([
'ColorHSL::AddLuminance @ Source/UI/ColorHSL.vb',
'ColorHSL::AddLuminance @ Source/UI/ColorHSL.vb',
'FrameServerFactory::Create @ Source/Video/FrameServer.vb',
]);
});
it('resolve a construction to the caller’s own project’s copy', () => {
expect(targetsFrom('Source/General/ThemeManager.vb', 'instantiates')).toEqual([
'ColorHSL @ Source/UI/ColorHSL.vb',
]);
});
it('make no guess between copies nothing tells apart', () => {
expect(targetsFrom('App/Main.vb', 'calls')).toEqual([]);
});
it('take one type’s overloads as one guess, not a tie', () => {
expect(targetsFrom('Source/General/Tips.vb', 'calls')).toEqual(['TipProvider::SetTip @ Source/UI/TipProvider.vb']);
});
});
+465
View File
@@ -0,0 +1,465 @@
/**
* A VB.NET call through a local, a parameter, a field or a property is a call
* on the type it is declared with — `Dim x As T`, `ByVal x As T`,
* `x As New T`, `Private File As SFile`, `Property File As SFile`, a type
* character (`Dim name$`), or what `Dim x = obj.GetString(…)` returns —
* never a guess at a same-named method of some project class. Keywords are
* matched without regard to case. A declared type the project does not define
* means nothing of the project's, except an extension method declared for it.
*
* - SCrawler's `ThumbnailFile.Delete(SFO.File, …)` on an `SFile` (from the
* external PersonalUtilities library) went to a nested `TempFileConversion`
* class's `Delete`, `GroupFile.Delete()` to a download group's,
* `UserUpdatedEventHandlers.Add(e)` on a `List(Of …)` to `UserDataBind.Add`
* and `TotalSize.CompareTo(…)` on a `Double` to a plugin's `VSize.CompareTo`;
* - staxrip's `timestampFontColorValue.ToColor(…)`, a String from
* `settings.GetString(…)`, went to a `ColorHSL.ToColor()` instead of the
* String extension `StringExtensions.ToColor`.
*
* The type a name means is the one VB.NET's lookup finds where it is written:
* the namespaces around it (SCrawler declares a `SiteSettings` and an `M3U8`
* per site namespace), a base class's nested types, the file's `Imports`
* aliases, and the type arguments a subclass gives its base. The declared
* type wins over whatever is assigned later, a `For Each` variable is an
* element of what it loops over, and an interface a class implements lends
* it no members to read bare.
*/
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-receiver-'));
const files: Record<string, string> = {
'Download/Groups/DownloadGroup.vb': `Namespace DownloadObjects.Groups
Friend Class DownloadGroup
Friend Sub Delete()
End Sub
End Class
End Namespace
`,
'YouTube/Objects/YouTubeMediaContainerBase.vb': `Namespace API.YouTube.Objects
Public MustInherit Class YouTubeMediaContainerBase
Public ReadOnly Property ThumbnailFile As SFile
Private Class TempFileConversion
Friend Sub Delete(ByVal Mode As Integer)
End Sub
End Class
End Class
End Namespace
`,
'Download/Groups/DownloadGroupCollection.vb': `Namespace DownloadObjects.Groups
Friend Class DownloadGroupCollection
Private ReadOnly GroupFile As SFile = "Settings\\Groups.xml"
Friend Sub Update()
GroupFile.Delete()
End Sub
Friend Sub Reset(byval groupFile as SFile)
groupFile.Delete()
End Sub
End Class
End Namespace
`,
'Hosts/DownloadableMediaHost.vb': `Namespace Hosts
Friend Class DownloadableMediaHost : Inherits API.YouTube.Objects.YouTubeMediaContainerBase
Friend Sub Clean()
ThumbnailFile.Delete(1)
End Sub
End Class
End Namespace
`,
'API/UserDataBind.vb': `Namespace API
Friend Class UserDataBind
Friend Sub Add(ByVal User As Object)
End Sub
End Class
End Namespace
`,
'API/Base/UserDataBase.vb': `Namespace API.Base
Friend MustInherit Class UserDataBase
Private ReadOnly UserUpdatedEventHandlers As List(Of UserUpdatedEventHandler)
Friend Sub AddUpdateHandler(ByVal e As UserUpdatedEventHandler)
UserUpdatedEventHandlers.Add(e)
End Sub
Friend Sub Download()
End Sub
End Class
End Namespace
`,
'API/Instagram/UserData.vb': `Namespace API.Instagram
Friend Class UserData : Inherits API.Base.UserDataBase
End Class
End Namespace
`,
'API/Instagram/Downloader.vb': `Namespace API.Instagram
Friend Class Downloader
Friend Sub Run(ByVal u As UserData)
u.Download()
End Sub
End Class
End Namespace
`,
'Plugin/UserData.vb': `Friend Class VSize
Public Function CompareTo(ByVal Other As VSize) As Integer
Return 0
End Function
End Class
`,
'Editors/UsersInfoForm.vb': `Friend Class UsersInfoForm
Private NotInheritable Class UserOpt
Friend Property TotalSize As Double = 0
Friend Function CompareTo(ByVal Other As UserOpt) As Integer
Return TotalSize.CompareTo(Other.TotalSize) * -1
End Function
End Class
End Class
`,
'UI/ColorHSL.vb': `Public Class ColorHSL
Public Sub New(ByVal h As Double, ByVal s As Double, ByVal l As Double, ByVal a As Double)
End Sub
Public Function AddLuminance(ByVal offset As Single) As ColorHSL
Return Me
End Function
Public Function ToColor() As Integer
Return 0
End Function
End Class
`,
'UI/BackColorAdjuster.vb': `Public Class BackColorAdjuster
Public Function AddLuminance(ByVal offset As Single) As Integer
Return 0
End Function
End Class
`,
'UI/ThemeManager.vb': `Public Class ThemeManager
Public Sub Apply()
Dim _backColor As ColorHSL = New ColorHSL(0, 0.01, 0.1, 1)
Dim _controlBackColor As ColorHSL = _backColor.AddLuminance(0.025)
End Sub
End Class
`,
'UI/Popup.vb': `Public Class Popup
Public Sub Show()
End Sub
End Class
`,
'UI/MainForm.vb': `Public Class MainForm
Inherits Form
Public Sub OpenSettings()
Dim settingsForm As New MainForm
settingsForm.Show()
End Sub
End Class
`,
'General/Extensions.vb': `Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension>
Function ToColor(ByVal str As String, Optional ByVal defaultColor As Integer = 0) As Integer
Return 0
End Function
<Extension>
Function Join(ByVal instance As IEnumerable(Of String), ByVal delimiter As String) As String
Return ""
End Function
<Extension>
Function Sort(Of T)(ByVal instance As IEnumerable(Of T)) As IEnumerable(Of T)
Return instance
End Function
End Module
`,
'General/ObjectStorage.vb': `Public Class ObjectStorage
Public Function GetString(ByVal key As String, Optional ByVal defaultValue As String = Nothing) As String
Return defaultValue
End Function
End Class
`,
'General/Thumbnailer.vb': `Public Class Thumbnailer
Public Sub Run(ByVal settings As ObjectStorage)
Dim timestampFontColorValue = settings.GetString("TimestampFontColor", "#fff")
Dim timestampFontColor = timestampFontColorValue.ToColor(1)
Dim colorText As String = "#000"
Dim outline = colorText.ToColor()
Dim name$ = "x"
Dim named = name.ToColor()
End Sub
End Class
`,
'General/Lists.vb': `Public Class Lists
Public Sub Run()
Dim names As New List(Of String)
names.Sort()
Dim joined = names.Join(", ")
End Sub
End Class
`,
'Base/DownDetector.vb': `Namespace API.Base
Friend NotInheritable Class DownDetector
Friend MustInherit Class Checker(Of T)
Protected ReadOnly Property Source As T
End Class
End Class
End Namespace
`,
'Sites/Bluesky/SiteSettings.vb': `Namespace API.Bluesky
Friend Class SiteSettings
Friend Function IsMyUser(ByVal url As String) As Boolean
Return False
End Function
Friend Function AvailableTrueValue() As Boolean
Return False
End Function
End Class
Friend NotInheritable Class M3U8
Friend Shared Sub Download(ByVal url As String)
End Sub
End Class
End Namespace
`,
'Sites/Reddit/SiteSettings.vb': `Namespace API.Reddit
Friend Class SiteSettings
Friend Function IsMyUser(ByVal url As String) As Boolean
Return True
End Function
Friend Function AvailableTrueValue() As Boolean
Return True
End Function
Private Class MyDownDetector : Inherits API.Base.DownDetector.Checker(Of SiteSettings)
Friend Sub Check()
Source.AvailableTrueValue()
End Sub
End Class
End Class
Friend NotInheritable Class M3U8
Friend Shared Sub Download(ByVal url As String)
End Sub
End Class
End Namespace
`,
'Sites/Reddit/UserData.vb': `Namespace API.Reddit
Friend Class UserData
Private ReadOnly Property MySettings As SiteSettings
Friend Sub Check(ByVal url As String)
MySettings.IsMyUser(url)
M3U8.Download(url)
End Sub
End Class
End Namespace
`,
'Encoding/VideoEncoder.vb': `Public MustInherit Class VideoEncoder
Public Class MenuList
Public Sub Add(ByVal text As String)
End Sub
End Class
End Class
Public Class BatchEncoder
Inherits VideoEncoder
Public Function GetMenu() As Object
Dim ret As New MenuList
ret.Add("Codec Configuration")
Return ret
End Function
End Class
`,
'YouTube/YouTubeFunctions.vb': `Friend Interface IContainer
Sub Parse()
End Interface
Friend Class Channel : Implements IContainer
Friend Sub Parse() Implements IContainer.Parse
End Sub
End Class
Friend Module YouTubeFunctions
Friend Sub Load()
Dim item As IContainer
item = New Channel
item.Parse()
End Sub
End Module
`,
'UI/Labels.vb': `Public Class LabelUI
Public Function AddLabel(ByVal text As String) As Object
Return Nothing
End Function
End Class
Public Class SimpleUI
Public Function AddLabel(ByVal text As String) As Object
Return Nothing
End Function
Public Function AddLabel(ByVal text As String, ByVal width As Integer) As Object
Return Nothing
End Function
End Class
Public Class SimpleSettingsForm
Friend WithEvents SimpleUI As SimpleUI
End Class
`,
'UI/AudioForm.vb': `Public Class AudioForm
Public Sub ShowAdvanced()
Using form As New SimpleSettingsForm()
Dim ui = form.SimpleUI
ui.AddLabel("EBU R128")
End Using
End Sub
End Class
`,
'UI/Theme.vb': `Public Class ButtonLabel
Public Sub ApplyTheme()
End Sub
End Class
Public Class ToggleButtonLabel
Public Sub ApplyTheme()
End Sub
End Class
Public Class ThemeApplier
Public Sub Apply(ByVal controls As Object)
For Each control In controls.OfType(Of ToggleButtonLabel)
control.ApplyTheme()
Next
End Sub
Public Sub ApplyAll(ByVal labels As List(Of ToggleButtonLabel))
For Each label In labels
label.ApplyTheme()
Next
End Sub
End Class
`,
'Download/TDownloader.vb': `Namespace App.Download
Friend Class TDownloader
Friend Class Job
Friend Sub Start()
End Sub
End Class
End Class
Friend Class Worker
Friend Sub Start()
End Sub
End Class
End Namespace
`,
'Download/DownloadProgress.vb': `Imports TDJob = App.Download.TDownloader.Job
Namespace App.Download
Friend Class DownloadProgress
Friend ReadOnly Property Worker As TDJob
Friend Sub Run()
Worker.Start()
End Sub
End Class
End Namespace
`,
'Plugin/IPluginContentProvider.vb': `Namespace Plugin
Public Interface ISiteSettings
End Interface
Public Interface IPluginContentProvider
Property Settings As ISiteSettings
End Interface
End Namespace
`,
'MainMod.vb': `Friend Module MainMod
Friend Settings As SettingsCLS
End Module
`,
'SettingsCLS.vb': `Friend Class SettingsCLS
Friend Sub UpdateUsersList()
End Sub
End Class
`,
'API/Base/UserDataProvider.vb': `Namespace API.Base
Friend MustInherit Class UserDataProvider : Implements Plugin.IPluginContentProvider
Friend Property MySettings As Plugin.ISiteSettings Implements Plugin.IPluginContentProvider.Settings
Friend Sub Delete()
Settings.UpdateUsersList()
End Sub
End Class
End Namespace
`,
};
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 a file. */
function callsFrom(file: string): string[] {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg
.getOutgoingEdgesFrom(ids)
.filter((e) => e.kind === 'calls')
.map((e) => cg.getNode(e.target)!.qualifiedName)
.sort();
}
describe('VB.NET calls through declared receivers', () => {
it('reach nothing of the project on a type it does not define', () => {
expect(callsFrom('Download/Groups/DownloadGroupCollection.vb')).toEqual([]);
expect(callsFrom('Hosts/DownloadableMediaHost.vb')).toEqual([]);
expect(callsFrom('API/Base/UserDataBase.vb')).toEqual([]);
});
it('reach nothing of the project on a VB.NET built-in type', () => {
expect(callsFrom('Editors/UsersInfoForm.vb')).toEqual([]);
});
it('reach the declared type’s own method, or the one it inherits', () => {
expect(callsFrom('UI/ThemeManager.vb')).toEqual(['ColorHSL::AddLuminance']);
expect(callsFrom('API/Instagram/Downloader.vb')).toEqual(['API.Base::UserDataBase::Download']);
});
it('reach nothing of the project for a method the declared type inherits from outside it', () => {
expect(callsFrom('UI/MainForm.vb')).toEqual([]);
});
it('reach an extension method declared for the type, through what a call returns too', () => {
expect(callsFrom('General/Thumbnailer.vb')).toEqual([
'ObjectStorage::GetString',
'StringExtensions::ToColor',
'StringExtensions::ToColor',
'StringExtensions::ToColor',
]);
});
it('reach a .NET type’s own method before an extension of that name, and an extension it lacks', () => {
// `List(Of T)` has its own `Sort()`, but no `Join`.
expect(callsFrom('General/Lists.vb')).toEqual(['StringExtensions::Join']);
});
it('name the type their declaration’s namespace sees, through a base class’s type argument too', () => {
// SCrawler declares a `SiteSettings` and an `M3U8` in each site's namespace.
expect(callsFrom('Sites/Reddit/UserData.vb')).toEqual([
'API.Reddit::M3U8::Download',
'API.Reddit::SiteSettings::IsMyUser',
]);
// `Inherits DownDetector.Checker(Of SiteSettings)` makes its `Source As T` a SiteSettings.
expect(callsFrom('Sites/Reddit/SiteSettings.vb')).toEqual(['API.Reddit::SiteSettings::AvailableTrueValue']);
// A type nested in a base class is named bare in a subclass: staxrip's `Dim ret As New MenuList`.
expect(callsFrom('Encoding/VideoEncoder.vb')).toEqual(['VideoEncoder::MenuList::Add']);
});
it('take the declared type over what is assigned, and an import alias for what it names', () => {
expect(callsFrom('YouTube/YouTubeFunctions.vb')).toEqual(['IContainer::Parse']);
expect(callsFrom('Download/DownloadProgress.vb')).toEqual(['App.Download::TDownloader::Job::Start']);
});
it('read a field through what a call returns, and a loop variable through what it loops over', () => {
expect(callsFrom('UI/AudioForm.vb')).toEqual(['SimpleUI::AddLabel']);
// `For Each control In controls.OfType(Of ToggleButtonLabel)`, `For Each label In labels` over a `List(Of ToggleButtonLabel)`.
expect(callsFrom('UI/Theme.vb')).toEqual(['ToggleButtonLabel::ApplyTheme', 'ToggleButtonLabel::ApplyTheme']);
});
it('see a module’s field, not the property of an interface the class implements', () => {
expect(callsFrom('API/Base/UserDataProvider.vb')).toEqual(['SettingsCLS::UpdateUsersList']);
});
});
+75
View File
@@ -0,0 +1,75 @@
/**
* 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']);
});
});
+2
View File
@@ -25,6 +25,7 @@ import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, is
import { isVisibleCppMacro, clearCppMacroVisibility } from './cpp-macro-visibility';
import { isCppConstructorRef, matchCppConstructor } from './cpp-constructor';
import { gateSwiftTypeTarget, clearSwiftTypeVisibility, swiftExtendedConformances } from './swift-type-visibility';
import { clearVbnetReceiverMemos } from './vbnet-receivers';
import { gateTypeParameter, clearTypeParameterMemos } from './type-parameters';
import { resolveViaImport, resolvePhpImportedStaticCall, resolvePhpQualifiedClassRef, resolveJvmImport, extractImportMappings, extractReExports, loadCppIncludeDirs, isPhpIncludePathRef, isCobolCopybookRef, isNixPathImportRef, isJsPathImportRef, isBoundToOutOfRepoImport, clearImportResolverMemos, resolveImportPath, isExternalImport } from './import-resolver';
import { ResolverPool, minRefsForPool, shouldEngageAdaptively } from './resolver-pool';
@@ -457,6 +458,7 @@ export class ReferenceResolver {
clearNameMatcherMemos(this.context);
clearCppMacroVisibility(this.context);
clearSwiftTypeVisibility(this.context);
clearVbnetReceiverMemos(this.context);
clearTypeParameterMemos(this.context);
}
}
+75 -6
View File
@@ -11,6 +11,7 @@ import { UnresolvedRef, ResolvedRef, ResolutionContext, ImportMapping, isSuperty
import { blankStringContents, stripCommentsForRegex } from './strip-comments';
import { JS_BUILT_INS, JS_BUILTIN_METHODS, TS_PRIMITIVE_TYPES } from './js-builtins';
import { SWIFT_TYPE_PATH_CALL, resolveSwiftTypePathCall } from './swift-type-visibility';
import { breakVbTie, isVbMemberInScope, isVbNestedTypeInScope, isVbTypeQualifiedBy, matchVbTypedCall, preferVbProject, sameVbProject } from './vbnet-receivers';
import { isTestPath } from '../search/query-utils';
import { isMinifiedContent } from '../extraction/generated-detection';
import { getCargoWorkspaceCrateMap } from './frameworks/cargo-workspace';
@@ -3112,6 +3113,9 @@ const CSHARP_STD_METHODS: ReadonlySet<string> = new Set([
'ForEach', 'Sort', 'Reverse', 'Clone', 'Seek', 'SetLength',
]);
/** The same .NET names as VB.NET writes them — in any case. */
const VBNET_STD_METHODS: ReadonlySet<string> = new Set([...CSHARP_STD_METHODS].map((m) => m.toLowerCase()));
/**
* A request handler the web framework dispatches to: a Django / DRF / Flask
* view's `get` / `post` / …, a controller's `index` / `store` / `update` /
@@ -3156,11 +3160,17 @@ function stdMethodNames(language: string): ReadonlySet<string> | null {
case 'rust': return RUST_STD_METHODS;
case 'kotlin': return KOTLIN_STD_METHODS;
case 'csharp': return CSHARP_STD_METHODS;
case 'vbnet': return VBNET_STD_METHODS;
case 'dart': return DART_STD_METHODS;
default: return null;
}
}
/** Whether `name` is one of `language`'s standard-library method names (VB.NET's in any case). */
function isStdMethodName(language: string, name: string): boolean {
return stdMethodNames(language)?.has(language === 'vbnet' ? name.toLowerCase() : name) ?? false;
}
/** Methods of Dart's String, List, Iterable, Map and Set — names a project type rarely carries itself. */
const DART_STD_METHODS: ReadonlySet<string> = new Set([
'endsWith', 'startsWith', 'contains', 'split', 'substring', 'trim', 'trimLeft', 'trimRight', 'toLowerCase',
@@ -4657,7 +4667,13 @@ function vbReceiverOf(ref: UnresolvedRef, context: ResolutionContext): string |
const name = ref.referenceName.toLowerCase();
let start = lower.startsWith(name, ref.column) ? ref.column : -1;
if (start < 0) {
const m = new RegExp(`(?<![\\w])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w])`).exec(lower);
// The reference starts at its receiver or its `New`, so the name is the
// first one there or after: `st.Language = New Language(…)` constructs
// a Language through no receiver, and `Me.Size = New System.Drawing.Size(…)`
// through `System.Drawing`, not `Me`.
const at = new RegExp(`(?<![\\w])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w])`, 'g');
at.lastIndex = Math.max(0, ref.column);
const m = at.exec(lower) ?? (at.lastIndex = 0, at.exec(lower));
start = m ? m.index : -1;
}
if (start < 0) return null;
@@ -4688,6 +4704,22 @@ function isVbMemberReachable(n: Node, receiver: string): boolean {
return last !== '' && last === owner;
}
/**
* Whether a VB.NET call has no receiver, or one the extractor drops (`Me`,
* `MyClass`, `MyBase`) — a call isVbMemberInScope judges. A name its line
* doesn't show (a link of a chain continued from the line above) is not.
*/
function isVbScopedCall(ref: UnresolvedRef, receiver: string | null, context: ResolutionContext): boolean {
if (ref.language !== 'vbnet' || ref.referenceKind !== 'calls' || !/^\w+$/.test(ref.referenceName)) return false;
return receiver === null ? hasNoReceiverOnLine(ref, context) : /^(?:me|mybase|myclass)$/i.test(receiver);
}
/** Whether a VB.NET call or construction names its target with nothing before it (`New Point(4, 285)`). */
function isVbUnqualifiedName(ref: UnresolvedRef, receiver: string | null, context: ResolutionContext): boolean {
if (ref.language !== 'vbnet' || (ref.referenceKind !== 'calls' && ref.referenceKind !== 'instantiates')) return false;
return receiver === null && /^\w+$/.test(ref.referenceName) && hasNoReceiverOnLine(ref, context);
}
const CFML_CHAINS = new WeakMap<ResolutionContext, Map<string, Set<string>>>();
/**
@@ -5536,6 +5568,8 @@ export function matchByExactName(
const cfmlBare = (ref.language === 'cfml' || ref.language === 'cfscript') && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName);
const vbReceiver = ref.language === 'vbnet' && (ref.referenceKind === 'calls' || ref.referenceKind === 'instantiates') && /^\w+$/.test(ref.referenceName)
? vbReceiverOf(ref, context) : null;
const vbScoped = isVbScopedCall(ref, vbReceiver, context);
const vbUnqualified = isVbUnqualifiedName(ref, vbReceiver, context);
const objcShape = ref.language === 'objc' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*:*(?:\w+:)*$/.test(ref.referenceName)
? objcCallShape(ref, context) : null;
const csharpBare = ref.language === 'csharp' && (ref.referenceKind === 'calls' || ref.referenceKind === 'references') && /^[A-Za-z_]\w*$/.test(ref.referenceName);
@@ -5557,6 +5591,9 @@ export function matchByExactName(
!(objcShape === 'self-send' && !isObjcSelfSendTarget(n, ref, context)) &&
!(objcShape === 'super-send' && !isObjcSelfSendTarget(n, ref, context, true)) &&
!(vbReceiver !== null && !isVbMemberReachable(n, vbReceiver)) &&
!(vbReceiver && !/^(?:me|mybase|myclass)$/i.test(vbReceiver) && !isVbTypeQualifiedBy(n, vbReceiver, ref.filePath, context)) &&
!(vbScoped && !isVbMemberInScope(n, ref, context)) &&
!(vbUnqualified && !isVbNestedTypeInScope(n, ref, context)) &&
!(rubyBare && n.kind === 'method' && !isRubyMethodInScope(n, ref, context)) &&
!(cfmlBare && n.kind === 'method' && !isCfmlMethodInScope(n, ref, context)) &&
!(javaBare && n.kind === 'method' && !isJavaMethodInScope(n, ref, context)) &&
@@ -7817,6 +7854,14 @@ export function matchMethodCall(
// shared source-based inferrer. resolveMethodOnType validates the method
// exists on the inferred type, so a mis-inference produces no edge.
if (inferableReceiver) {
// A VB.NET receiver's declared type decides the call, or that there is no
// project method to call: SCrawler's `ThumbnailFile.Delete(…)` on an
// external `SFile` went to a nested class's `Delete` by a shared word.
if (ref.language === 'vbnet' && dotMatch) {
const typed = nmTimedT('mc-vbtyped', ref, () =>
matchVbTypedCall(objectOrClass!, methodName!, ref, context, (name) => isStdMethodName('vbnet', name)));
if (typed !== undefined) return typed;
}
let inferredType = nmTimedT('mc-infer', ref, () =>
ref.language === 'cpp'
? inferCppReceiverType(objectOrClass!, ref, context)
@@ -8024,6 +8069,9 @@ export function matchMethodCall(
const visible = classCandidates.filter((c) => c.language !== 'csharp' || isCsharpTypeVisible(c, typeRef, context));
classCandidates = [...visible, ...classCandidates.filter((c) => !visible.includes(c))];
}
// A VB.NET type declared in two projects is the caller's own project's:
// staxrip's `FrameServerFactory.Create(…)` went to its AutoCrop tool's copy.
if (ref.language === 'vbnet') classCandidates = preferVbProject(classCandidates, ref, context);
for (const classNode of classCandidates) {
// Skip cross-language class matches
@@ -8213,10 +8261,10 @@ export function matchMethodCall(
// `json` of its `MockedResponse`.
!isUnnamedTestDouble(targetMethods[0]!, objectOrClass!, ref, context) &&
!((ref.language === 'lua' || ref.language === 'luau') && isLuaLibraryCall(objectOrClass!, methodName!, ref, targetMethods[0]!)) &&
// Rust / Go / Kotlin / C#: a standard-library method name on an
// untyped receiver (`sym.map(…)`, `w.Header().Get(…)`,
// Rust / Go / Kotlin / C# / VB.NET: a standard-library method name on
// an untyped receiver (`sym.map(…)`, `w.Header().Get(…)`,
// `reader.Value.ToString()`) is the library type's.
!(stdMethodNames(ref.language)?.has(methodName!) &&
!(isStdMethodName(ref.language, methodName!) &&
!/^(?:self|Self|this|base)$/.test(objectOrClass!) && !receiverNamesOwner(receiverLink(objectOrClass!), targetMethods[0]!, context)) &&
!(UNTYPED_RECEIVER_LANGUAGES.has(ref.language) && !/^(?:self|self\.class|this|super|weak_?self|strong_?self)$/i.test(objectOrClass!) &&
!sharesReceiverWord(objectOrClass!, targetMethods[0]!) &&
@@ -8238,11 +8286,12 @@ export function matchMethodCall(
const head = receiverWords[receiverWords.length - 1]?.toLowerCase();
let bestMatch: typeof targetMethods[0] | undefined;
let bestScore = 0;
let tied: typeof targetMethods = [];
// Same-file candidates first, so a score tie (`score > bestScore` keeps
// the first seen) resolves to the call site's own file rather than the
// first-indexed duplicate (#1079).
const std = stdMethodNames(ref.language)?.has(methodName!) && !/^(?:self|Self|this|base)$/.test(objectOrClass!);
const std = isStdMethodName(ref.language, methodName!) && !/^(?:self|Self|this|base)$/.test(objectOrClass!);
for (const method of preferCallSiteFile(targetMethods, ref.filePath)) {
if (std && !receiverNamesOwner(receiverLink(objectOrClass!), method, context)) continue;
// The owner type's own name — not its namespace (`eShop.ClientApp…`
@@ -8265,8 +8314,16 @@ export function matchMethodCall(
if (score > bestScore) {
bestScore = score;
bestMatch = method;
tied = [method];
} else if (score === bestScore) {
tied.push(method);
}
}
// VB.NET: between equally good guesses, the caller's own file, then its
// project, then the nearer directory — and no guess when none of them
// decides. staxrip's main app and its AutoCrop tool each declare a
// `ColorHSL`, and the first indexed took about 90 of the app's calls.
if (ref.language === 'vbnet' && tied.length > 1 && bestScore >= 2) bestMatch = breakVbTie(tied, ref, context) ?? undefined;
// A wrapper handing its call on — BookStack's `FileStorage::delete` doing
// `$storage->delete($path)`, `CommentRepo::delete` doing
@@ -9229,7 +9286,7 @@ function computePathProximity(filePath1: string, filePath2: string): number {
function findBestMatch(
ref: UnresolvedRef,
candidates: Node[],
_context: ResolutionContext
context: ResolutionContext
): Node | null {
// Prioritization rules:
// 1. Same file > different file
@@ -9269,6 +9326,13 @@ function findBestMatch(
// Directory proximity bonus — strongly prefer same module/package
score += pathProximityFromDirs(refDirs, candidate.filePath);
// A VB.NET project compiles its own files: the caller's project weighs as
// much as the nearest a directory can be. staxrip's `New ColorHSL(…)` went
// to its AutoCrop tool's copy.
if (ref.language === 'vbnet' && candidate.language === 'vbnet' && sameVbProject(candidate.filePath, ref.filePath, context)) {
score += 80;
}
// Language matching: strongly prefer same language, penalize cross-language
if (candidate.language === ref.language) {
score += 50;
@@ -9353,6 +9417,8 @@ export function matchFuzzy(
const cfmlBare = (ref.language === 'cfml' || ref.language === 'cfscript') && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName);
const vbReceiver = ref.language === 'vbnet' && (ref.referenceKind === 'calls' || ref.referenceKind === 'instantiates') && /^\w+$/.test(ref.referenceName)
? vbReceiverOf(ref, context) : null;
const vbScoped = isVbScopedCall(ref, vbReceiver, context);
const vbUnqualified = isVbUnqualifiedName(ref, vbReceiver, context);
const objcShape = ref.language === 'objc' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*:*(?:\w+:)*$/.test(ref.referenceName)
? objcCallShape(ref, context) : null;
const csharpBare = ref.language === 'csharp' && (ref.referenceKind === 'calls' || ref.referenceKind === 'references') && /^[A-Za-z_]\w*$/.test(ref.referenceName);
@@ -9388,6 +9454,9 @@ export function matchFuzzy(
!(rubyBare && n.kind === 'method' && !isRubyMethodInScope(n, ref, context)) &&
!(cfmlBare && n.kind === 'method' && !isCfmlMethodInScope(n, ref, context)) &&
!(vbReceiver !== null && !isVbMemberReachable(n, vbReceiver)) &&
!(vbReceiver && !/^(?:me|mybase|myclass)$/i.test(vbReceiver) && !isVbTypeQualifiedBy(n, vbReceiver, ref.filePath, context)) &&
!(vbScoped && !isVbMemberInScope(n, ref, context)) &&
!(vbUnqualified && !isVbNestedTypeInScope(n, ref, context)) &&
!(objcShape === 'c-call' && OBJC_MEMBER_KINDS.has(n.kind)) &&
!(objcShape === 'self-send' && !isObjcSelfSendTarget(n, ref, context)) &&
!(objcShape === 'super-send' && !isObjcSelfSendTarget(n, ref, context, true)) &&
File diff suppressed because it is too large Load Diff