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:
Colby Mchenry
2026-09-30 12:44:03 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 37bcd5c0ae
commit 05740b55eb
3 changed files with 80 additions and 0 deletions
+1
View File
@@ -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.
+57
View File
@@ -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]);
});
});
+22
View File
@@ -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,