mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-17 05:07:37 +08:00
aa0c73c416
Strategy 1 (a receiver naming a class) looked only in the class's own
file, so an inherited class method fell to the name-word guess: Horse's
THorse.Get('/ping', …) (THorseCore's, three class(…) heads up) went to a
route-group interface's Get 120 times.
When the class itself declares no such method, walk the supertypes its
declaration heads name (Pascal class(…), Python bases, Ruby <, PHP/TS/JS
extends, and the existing Java-family / Dart / Swift / ObjC / Scala
readers), nearest first, up to five levels.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
2.8 KiB
TypeScript
131 lines
2.8 KiB
TypeScript
/**
|
|
* A call through a class name reaches a class method the class inherits,
|
|
* read from its own and its ancestors' declaration heads, before any guess by
|
|
* name: Horse's `THorse.Get('/ping', …)` is THorseCore's (three `class(…)`
|
|
* heads up), not a route-group interface's `Get`; DRF's `MyView.as_view()` is
|
|
* APIView's; netbox's `ColorChoices.values()` is ChoiceSet's.
|
|
*/
|
|
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-inherited-class-method-'));
|
|
const files: Record<string, string> = {
|
|
'src/Horse.Core.pas': `unit Horse.Core;
|
|
|
|
interface
|
|
|
|
type
|
|
THorseCore = class
|
|
public
|
|
class function Get(const APath: string): THorseCore;
|
|
end;
|
|
|
|
implementation
|
|
|
|
class function THorseCore.Get(const APath: string): THorseCore;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
end.
|
|
`,
|
|
'src/Horse.Core.Group.pas': `unit Horse.Core.Group;
|
|
|
|
interface
|
|
|
|
type
|
|
IHorseCoreGroup = interface
|
|
function Get(const APath: string): IHorseCoreGroup;
|
|
end;
|
|
|
|
implementation
|
|
|
|
end.
|
|
`,
|
|
'src/Horse.pas': `unit Horse;
|
|
|
|
interface
|
|
|
|
uses Horse.Core;
|
|
|
|
type
|
|
THorseProvider = class(THorseCore)
|
|
end;
|
|
|
|
THorse = class(THorseProvider)
|
|
end;
|
|
|
|
implementation
|
|
|
|
end.
|
|
`,
|
|
'samples/Console.dpr': `program Console;
|
|
|
|
uses Horse;
|
|
|
|
begin
|
|
THorse.Get('/ping');
|
|
end.
|
|
`,
|
|
'app/views.py': `class APIView:
|
|
@classmethod
|
|
def as_view(cls):
|
|
return cls
|
|
|
|
|
|
class GenericAPIView(APIView):
|
|
pass
|
|
|
|
|
|
class UserList(GenericAPIView):
|
|
pass
|
|
|
|
|
|
class Router:
|
|
def as_view(self):
|
|
return None
|
|
`,
|
|
'app/urls.py': `from app.views import UserList
|
|
|
|
urlpatterns = [UserList.as_view()]
|
|
`,
|
|
};
|
|
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('a class method reached through a subclass name', () => {
|
|
it('Pascal: walks the class(…) heads', () => {
|
|
expect(callsFrom('samples/Console.dpr').filter((q) => q.endsWith('Get'))).toEqual(['THorseCore::Get']);
|
|
});
|
|
|
|
it('Python: walks the class bases', () => {
|
|
expect(callsFrom('app/urls.py')).toEqual(['APIView::as_view']);
|
|
});
|
|
});
|