mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-29 14:49:36 +08:00
fix(resolution): a call on an outside type is not a project method (#2166)
`Name.method()` where `Name` reads as a type in the language's conventions and nothing in the project (same language family) is called `Name` — JDK / BCL / DOM / Indy types — now leaves the method-call matcher before the capitalized-receiver and name-overlap guesses. Not applied in Go (exported package variables), C / C++ / Rust / CUDA / Metal; in Pascal only Delphi type prefixes (T/E/I…) and `Exception` count, since parameters and locals are capitalized there too. A/B (edges removed): commons-lang -1636 (`Integer.valueOf` → StringUtils ×804, `Calendar.getInstance`, `Instant.now`), horse -89 (`Exception.Create` → EHorseException), retrofit -79 (OkHttp `RequestBody.create` / `HttpUrl.get`), typeorm -74 (`Object.assign` → ObjectUtils), gson -100, zod -63, Newtonsoft.Json -57, jsoup -18; gin and cobra byte-identical. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
37bcd5c0ae
commit
05740b55eb
@@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### Fixes
|
||||
|
||||
- A call on a type from outside the project, like `Integer.valueOf(…)`, `Arrays.asList(…)`, `Object.assign(…)` or Delphi's `Exception.Create(…)`, no longer lands on the project's own method with that name. On commons-lang, `Integer.valueOf` used to land on `StringUtils.valueOf` over 800 times. Go's exported variables and Pascal's capitalized parameters and locals are not mistaken for type names.
|
||||
- Imports through a tsconfig or jsconfig path alias now use the config nearest the importing file, so each app in a monorepo resolves its own `@/…` or `~/…` alias. On bulletproof-react and trpc's examples, those imports used to fail or land in a different app.
|
||||
- React Router data-router routes are named by their full path: nested `children` paths are joined to their parent's, `path: paths.app.root.path` constants are read, and `lazy: () => import('./routes/x')` routes link to the module's component. A guard wrapper like `<ProtectedRoute>` no longer stands in for the page. On bulletproof-react, every route now has its real path and page.
|
||||
- In JavaScript, TypeScript and Vue, a call through a name destructured from a composable or custom hook, like `const { getDefaultActivityRoute } = useDefaultActivity()` or `const { t } = useI18n()`, now reaches the function the hook returns. These calls used to resolve to nothing. mealie, elk, halo and excalidraw each gained dozens to over a hundred such links.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* `Name.method()` where `Name` is a type the project doesn't declare —
|
||||
* `Arrays.asList(…)`, `Object.assign(…)`, Delphi's `Exception.Create(…)` — is
|
||||
* a call into that outside type, never the project's one same-named method.
|
||||
* commons-lang's `Integer.valueOf(…)` went to `StringUtils.valueOf` 804
|
||||
* times; horse's `Exception.Create` to its `EHorseException.Create`. Go's
|
||||
* exported variables (`FormPost.Bind`) and Pascal's capitalized locals and
|
||||
* parameters (`LRequest.GetValue`) are not type names.
|
||||
*/
|
||||
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-external-type-'));
|
||||
const files: Record<string, string> = {
|
||||
'src/main/java/app/StringUtils.java': `package app;
|
||||
public class StringUtils {
|
||||
public static String valueOf(Object o) { return String.valueOf(o); }
|
||||
}
|
||||
`,
|
||||
'src/main/java/app/Use.java': `package app;
|
||||
public class Use {
|
||||
public int run() {
|
||||
Integer.valueOf(1);
|
||||
return StringUtils.valueOf(2).length();
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
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('a call on a type from outside the project', () => {
|
||||
it('is not the project’s same-named method; the project’s own type still is', () => {
|
||||
const run = cg.getNodesInFile('src/main/java/app/Use.java').find((n) => n.name === 'run')!;
|
||||
const valueOf = cg
|
||||
.getOutgoingEdges(run.id)
|
||||
.filter((e) => e.kind === 'calls' && cg.getNode(e.target)!.name === 'valueOf');
|
||||
// Exactly one: `StringUtils.valueOf(2)`, not `Integer.valueOf(1)`.
|
||||
expect(valueOf.map((e) => e.line)).toEqual([5]);
|
||||
});
|
||||
});
|
||||
@@ -4683,6 +4683,15 @@ export function matchMethodCall(
|
||||
return null;
|
||||
}
|
||||
|
||||
// A receiver written as a type name the project doesn't declare
|
||||
// (`Exception.Create(…)` in Delphi, `Collections.sort(…)`) is a type from
|
||||
// outside it: a same-named method of some project type is a guess. horse's
|
||||
// `Exception.Create` went to its own `EHorseException::Create` 44 times.
|
||||
if (namesExternalType(objectOrClass!, ref.language) &&
|
||||
!context.getNodesByName(objectOrClass!).some((n) => sameLanguageFamily(n.language, ref.language))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Strategy 2: Instance variable receiver - try capitalized form to find class
|
||||
// e.g., "permissionEngine" → look for classes containing "PermissionEngine"
|
||||
const capitalizedReceiver = objectOrClass!.charAt(0).toUpperCase() + objectOrClass!.slice(1);
|
||||
@@ -5641,6 +5650,19 @@ function hasParameterBinding(code: string, escapedName: string): boolean {
|
||||
/**
|
||||
* Split a camelCase or PascalCase string into words.
|
||||
*/
|
||||
/**
|
||||
* Whether a receiver written as `Name` is a type in `language`'s conventions.
|
||||
* Not in Go (an exported package variable is `FormPost`), nor C / C++ / Rust
|
||||
* (their type paths use `::`); in Pascal every identifier is capitalized, so
|
||||
* only Delphi's type prefixes (`TFoo`, `EFoo`, `IFoo`) and `Exception` count —
|
||||
* `AWebRequest` / `LRequest` are a parameter and a local.
|
||||
*/
|
||||
function namesExternalType(receiver: string, language: string): boolean {
|
||||
if (!/^[A-Z][A-Za-z0-9_]*$/.test(receiver)) return false;
|
||||
if (language === 'pascal') return /^(?:[TEI][A-Z]\w*|Exception)$/.test(receiver);
|
||||
return !['go', 'c', 'cpp', 'rust', 'cuda', 'metal'].includes(language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Languages whose receivers nothing types, where a unique method name alone
|
||||
* is no evidence: CFML's `server.keyExists()` is the struct member function,
|
||||
|
||||
Reference in New Issue
Block a user