/** * 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 = ` WinExe `; 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 = { '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']); }); });