diff --git a/CHANGELOG.md b/CHANGELOG.md
index 037f61f83..c0c959e44 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Fixes
+- Calls into an Expo module now reach the module's native functions even when the JavaScript names the module something else, like expo-camera's `CameraManager` for `requireNativeModule('ExpoCamera')`. Both the iOS and the Android implementations are linked. expo-camera's calls used to link back to the same-named method making the call, so a method looked like it called itself.
+- A JavaScript or TypeScript call on something the file imports, like `fireEvent.click(…)` or `util.omit(…)`, no longer links to a same-named method declared in the calling file itself.
+- TypeScript's `import type { X }` no longer reads as an import of something named `type`, and `import { a, type B }` binds `B`, not `type B`. So a type-only import now links to the module it names, where it used to link to whatever project symbol had that name. A type imported from a package, like `RsbuildConfig` from `@rsbuild/core`, no longer links to a project symbol, and JavaScript and TypeScript names no longer match a symbol whose name differs only in case (`Mock` is not `mock`).
- Angular apps split across Nx libraries, like angular-spotify, now have their routes and navigation. A lazily loaded route written `async () => (await import('@app/home')).HomeModule` is followed through the library's `index.ts` to the module it re-exports, and a `routerLink` in one library now reaches a screen declared in another. Route paths built from a class constant (`RouterUtil.Configuration.Lyrics`, `` `issue/:${ProjectConst.IssueId}` ``) or an enum are now read. A redirect in a routes file that only lazy-loads others now counts, and `router.navigate(['project', 'issue', id])` without `relativeTo` is read from the root, as Angular does. Re-index Angular projects after upgrading.
- React Router apps now show navigation written as React Router v5's `` and through a styled link, like react-boilerplate's `HeaderLink = styled(Link)` used as ``. Apps built this way had routes but no links between their screens.
- SvelteKit routes are now named by the address a browser asks for. A `(group)` folder like `(app)` or `(marketing)` is no longer part of a route's path, and a parameter with a matcher, like `[id=integer]`, is just `:id`. So `goto('/blocks')` and `` now reach a page that lives in `src/routes/(app)/blocks/`, where before they reached nothing and the app's screens showed no navigation. Re-index SvelteKit projects after upgrading.
diff --git a/README.md b/README.md
index 23a046f54..284027063 100644
--- a/README.md
+++ b/README.md
@@ -363,7 +363,7 @@ Real iOS and React Native codebases live across multiple languages — a Swift c
| **React Native legacy bridge** | JS `NativeModules.X.fn(...)` | ObjC `RCT_EXPORT_METHOD` / `RCT_REMAP_METHOD` · Java/Kotlin `@ReactMethod` | Parses macro/annotation declarations to build a JS-name → native-method map |
| **React Native TurboModules** | JS `import M from './NativeM'; M.fn(...)` | Native impl matching the Codegen spec | Treats the `Native.ts` spec interface as ground truth |
| **RN native → JS events** | JS `new NativeEventEmitter(...).addListener('e', cb)` | ObjC `[self sendEventWithName:@"e" body:...]` · Swift `sendEvent(withName: "e", ...)` · Java/Kotlin `.emit("e", ...)` | Synthesized cross-language event channel keyed by literal event name |
-| **Expo Modules** | JS `requireNativeModule('X').fn(...)` | Swift / Kotlin `Module { Name("X"); AsyncFunction("fn") { ... } }` | Parses the Expo DSL literals; synthetic method nodes resolve via existing name-match |
+| **Expo Modules** | JS `requireNativeModule('X').fn(...)`, directly or through a binding (`export default requireNativeModule('X')`) | Swift / Kotlin `Module { Name("X"); AsyncFunction("fn") { ... } }` | Parses the Expo DSL literals into method nodes; a call on a binding resolves to module `X`'s `fn` on both platforms, else to the method on the binding's declared type |
| **Fabric view components** | JSX `` | TS Codegen spec + native impl class | Spec → `component` node; convention-based name+suffix lookup (`View`/`ComponentView`/`Manager`/`ViewManager`) bridges to native |
| **Legacy Paper view managers** | JSX `` | ObjC `RCT_EXPORT_VIEW_PROPERTY` · Java/Kotlin `@ReactProp` | Same as Fabric — Paper-era declarations also produce `component` + `property` nodes |
diff --git a/__tests__/expo-modules.test.ts b/__tests__/expo-modules.test.ts
index eab16c1c3..4df758a2e 100644
--- a/__tests__/expo-modules.test.ts
+++ b/__tests__/expo-modules.test.ts
@@ -205,3 +205,80 @@ class BatteryModule : Module() {
expect(pair.c).toBeGreaterThanOrEqual(2); // swift->kotlin AND kotlin->swift
});
});
+
+describe('Expo Modules — a JS binding named for its role, not its module', () => {
+ let dir: string;
+ beforeEach(() => {
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'expo-modules-binding-'));
+ });
+ afterEach(() => {
+ fs.rmSync(dir, { recursive: true, force: true });
+ });
+
+ it('expo-camera: `CameraManager.fn()` on `export default requireNativeModule("ExpoCamera")` reaches the module’s functions', async () => {
+ const write = (rel: string, content: string) => {
+ fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true });
+ fs.writeFileSync(path.join(dir, rel), content);
+ };
+ write('package.json', '{"dependencies":{"expo-modules-core":"^1.0.0","expo":"*"}}');
+ write('ios/CameraViewModule.swift', `import ExpoModulesCore
+public final class CameraViewModule: Module {
+ public func definition() -> ModuleDefinition {
+ Name("ExpoCamera")
+ AsyncFunction("launchScanner") { (options: ScannerOptions) in }
+ AsyncFunction("dismissScanner") { }
+ }
+}
+`);
+ write('android/src/main/java/expo/modules/camera/CameraViewModule.kt', `package expo.modules.camera
+class CameraViewModule : Module() {
+ override fun definition() = ModuleDefinition {
+ Name("ExpoCamera")
+ AsyncFunction("launchScanner") { options: ScannerOptions -> }
+ AsyncFunction("dismissScanner") { }
+ }
+}
+`);
+ write('src/Camera.types.ts', `import { NativeModule } from 'expo';
+export declare class CameraNativeModule extends NativeModule {
+ readonly isAvailableAsync: () => Promise;
+}
+`);
+ write('src/ExpoCameraManager.ts', `import { requireNativeModule } from 'expo';
+import type { CameraNativeModule } from './Camera.types';
+
+export default requireNativeModule('ExpoCamera');
+`);
+ write('src/CameraView.tsx', `import CameraManager from './ExpoCameraManager';
+
+export default class CameraView {
+ static async isAvailableAsync(): Promise {
+ return CameraManager.isAvailableAsync();
+ }
+ static async launchScanner(options: object): Promise {
+ await CameraManager.launchScanner(options);
+ }
+}
+`);
+ const cg = await CodeGraph.init(dir, { index: true });
+ try {
+ const view = cg.getNodesInFile('src/CameraView.tsx');
+ const from = (name: string) => view.find((n) => n.name === name && n.kind === 'method')!;
+ const callees = (name: string) =>
+ cg
+ .getOutgoingEdges(from(name).id)
+ .filter((e) => e.kind === 'calls')
+ .map((e) => cg.getNode(e.target)!)
+ .map((n) => `${n.language}:${n.qualifiedName.split('::').pop()}`)
+ .sort();
+ // Both platforms' `launchScanner`, not CameraView's own.
+ expect(callees('launchScanner')).toEqual(['kotlin:ExpoCamera.launchScanner', 'swift:ExpoCamera.launchScanner']);
+ // Declared natively nowhere here: the type the binding is given, never the caller itself.
+ expect(callees('isAvailableAsync')).toEqual(['typescript:isAvailableAsync']);
+ const target = cg.getOutgoingEdges(from('isAvailableAsync').id).find((e) => e.kind === 'calls')!;
+ expect(cg.getNode(target.target)!.filePath).toBe('src/Camera.types.ts');
+ } finally {
+ cg.close();
+ }
+ });
+});
diff --git a/__tests__/import-type-modifier.test.ts b/__tests__/import-type-modifier.test.ts
new file mode 100644
index 000000000..a74b43070
--- /dev/null
+++ b/__tests__/import-type-modifier.test.ts
@@ -0,0 +1,75 @@
+/**
+ * TypeScript's `type` modifiers are not bindings.
+ *
+ * `import type { X } from './x'` used to read as a default import named
+ * `type` beside `X` — every type-only import line bound `type` — so zod's
+ * `type.innerType()` (a parameter named `type`) was taken for a call on an
+ * import. An inline `{ util, type objectUtil }` named its binding
+ * `type objectUtil`.
+ */
+import { describe, it, expect, afterAll } from 'vitest';
+import * as fs from 'fs';
+import * as os from 'os';
+import * as path from 'path';
+import { CodeGraph } from '../src';
+import { extractImportMappings } from '../src/resolution/import-resolver';
+
+const bindings = (source: string) =>
+ extractImportMappings('src/a.ts', source, 'typescript').map((m) => `${m.localName}<${m.exportedName}${m.isNamespace ? ' ns' : ''}`);
+
+describe('import mappings and TypeScript type modifiers', () => {
+ it('a type-only import binds its names, never `type`', () => {
+ expect(bindings(`import type { enumUtil } from './helpers/enumUtil.js';\n`)).toEqual(['enumUtil {
+ expect(bindings(`import { util, type objectUtil } from './helpers/util.js';\n`)).toEqual(['util {
+ expect(bindings(`import type from './type';\n`)).toEqual(['type {
+ const roots: string[] = [];
+ afterAll(() => {
+ for (const r of roots.splice(0)) fs.rmSync(r, { recursive: true, force: true });
+ });
+
+ it('halo: `type RsbuildConfig` and `type Command` are not a local `rsbuildConfig` or `command`', async () => {
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-import-type-'));
+ roots.push(root);
+ const files: Record = {
+ 'package.json': JSON.stringify({ name: 'console', dependencies: { '@rsbuild/core': '*', '@tiptap/core': '*' } }),
+ 'src/rsbuild.ts': `import { defineConfig, type RsbuildConfig } from '@rsbuild/core';
+export function rsbuildConfig(): RsbuildConfig { return defineConfig({}); }
+`,
+ 'src/menu.ts': `export function command() { return 1; }
+`,
+ 'src/gap.ts': `import type { Command } from '@tiptap/core';
+export function gapCursor(): Command { return () => true; }
+`,
+ };
+ 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);
+ }
+ const cg = await CodeGraph.init(root, { index: true });
+ try {
+ const targets = (file: string) =>
+ cg
+ .getOutgoingEdgesFrom(cg.getNodesInFile(file).map((n) => n.id), ['references', 'imports', 'type_of', 'returns'])
+ .map((e) => cg.getNode(e.target)!)
+ .filter((n) => n.kind !== 'file' && n.kind !== 'import')
+ .map((n) => n.name);
+ expect(targets('src/rsbuild.ts')).not.toContain('rsbuildConfig');
+ expect(targets('src/gap.ts')).not.toContain('command');
+ } finally {
+ cg.close();
+ }
+ });
+});
diff --git a/src/resolution/frameworks/expo-modules.ts b/src/resolution/frameworks/expo-modules.ts
index a9f60174a..c70d1d5ac 100644
--- a/src/resolution/frameworks/expo-modules.ts
+++ b/src/resolution/frameworks/expo-modules.ts
@@ -42,7 +42,12 @@ import type { Node } from '../../types';
import {
FrameworkExtractionResult,
FrameworkResolver,
+ ResolutionContext,
+ ResolvedRef,
+ UnresolvedRef,
} from '../types';
+import { resolveImportPath } from '../import-resolver';
+import { stripCommentsForRegex } from '../strip-comments';
/**
* Match `Function("name")`, `AsyncFunction("name")`, or `Property("name")`
@@ -196,3 +201,103 @@ export const expoModulesResolver: FrameworkResolver = {
return null;
},
};
+
+// =============================================================================
+// The JS side: `ExpoCamera.scanFromURLAsync(…)` → the module's function
+// =============================================================================
+
+/** `requireNativeModule('ExpoCamera')`, typed or not, optional or not. */
+const REQUIRE_NATIVE = /\brequire(?:Optional)?NativeModule\s*(?:<\s*([A-Za-z_$][\w$]*)[^>]*>)?\s*\(\s*['"]([A-Za-z_$][\w$]*)['"]\s*\)/;
+
+interface NativeBinding {
+ /** The `Name("…")` the module declares. */
+ module: string;
+ /** The declared JS type, from `requireNativeModule(…)`. */
+ type: string | null;
+ /** The file the binding (and its type) is written in. */
+ file: string;
+}
+
+const bindingMemo = new WeakMap>();
+
+/**
+ * What `name` is bound to in `file`: `const X = requireNativeModule('N')`
+ * here, or the import of a module whose default export (or a named export)
+ * is one — expo-camera's `import CameraManager from './ExpoCameraManager'`,
+ * whose whole body is `export default requireNativeModule('ExpoCamera')`.
+ */
+function nativeBinding(name: string, file: string, context: ResolutionContext): NativeBinding | null {
+ let memo = bindingMemo.get(context);
+ if (!memo) bindingMemo.set(context, (memo = new Map()));
+ const key = `${file}\0${name}`;
+ if (memo.has(key)) return memo.get(key)!;
+ let found: NativeBinding | null = null;
+ const own = context.readFile(file);
+ if (own && own.includes('NativeModule')) {
+ const local = new RegExp(String.raw`\b(?:const|let|var)\s+${name}\s*(?::[^=]+)?=\s*` + REQUIRE_NATIVE.source).exec(stripCommentsForRegex(own, 'typescript'));
+ if (local) found = { type: local[1] ?? null, module: local[2]!, file };
+ }
+ if (!found) {
+ const language = /\.tsx$/.test(file) ? 'tsx' : /\.[cm]?ts$/.test(file) ? 'typescript' : /\.jsx$/.test(file) ? 'jsx' : 'javascript';
+ const binding = context.getImportMappings(file, language).find((m) => m.localName === name);
+ const target = binding && binding.source.startsWith('.') ? resolveImportPath(binding.source, file, language, context) : null;
+ const content = target ? context.readFile(target) : null;
+ if (target && content && content.includes('NativeModule')) {
+ const safe = stripCommentsForRegex(content, 'typescript');
+ const pattern = binding!.isDefault
+ ? new RegExp(String.raw`\bexport\s+default\s+` + REQUIRE_NATIVE.source)
+ : new RegExp(String.raw`\bexport\s+(?:const|let)\s+${binding!.exportedName}\s*(?::[^=]+)?=\s*` + REQUIRE_NATIVE.source);
+ const m = pattern.exec(safe);
+ if (m) found = { type: m[1] ?? null, module: m[2]!, file: target };
+ }
+ }
+ memo.set(key, found);
+ return found;
+}
+
+/**
+ * JS calls into an Expo module, bound by the module's NAME, not by the
+ * method name alone: expo-camera calls its module `CameraManager`, so
+ * `CameraManager.isAvailableAsync()` went to `CameraView`'s own static
+ * `isAvailableAsync` in the same file. The native functions the module
+ * declares are the targets — Swift first, Kotlin beside it — and when it
+ * declares none of that name, the method on the type the binding is given
+ * (`requireNativeModule`).
+ */
+export const expoModulesJsResolver: FrameworkResolver = {
+ name: 'expo-modules-js',
+ languages: ['typescript', 'tsx', 'javascript', 'jsx'],
+
+ detect(context) {
+ return expoModulesResolver.detect(context);
+ },
+
+ resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null {
+ if (ref.referenceKind !== 'calls') return null;
+ const m = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(ref.referenceName);
+ if (!m) return null;
+ const binding = nativeBinding(m[1]!, ref.filePath, context);
+ if (!binding) return null;
+ const fn = m[2]!;
+ const declared = context
+ .getNodesByName(fn)
+ .filter((n) => n.id.startsWith('expo-module:') && n.qualifiedName.endsWith(`::${binding.module}.${fn}`))
+ .sort((a, b) => (a.language === 'swift' ? 0 : 1) - (b.language === 'swift' ? 0 : 1));
+ if (declared.length > 0) {
+ return {
+ original: ref,
+ targetNodeId: declared[0]!.id,
+ ...(declared.length > 1 ? { alsoTargets: declared.slice(1).map((n) => ({ targetNodeId: n.id })) } : {}),
+ confidence: 0.95,
+ resolvedBy: 'framework',
+ };
+ }
+ if (binding.type) {
+ const typed = context
+ .getNodesByName(fn)
+ .find((n) => (n.kind === 'method' || n.kind === 'property' || n.kind === 'field') && n.qualifiedName.endsWith(`${binding.type}::${fn}`));
+ if (typed) return { original: ref, targetNodeId: typed.id, confidence: 0.9, resolvedBy: 'framework' };
+ }
+ return null;
+ },
+};
diff --git a/src/resolution/frameworks/index.ts b/src/resolution/frameworks/index.ts
index 88221fdfa..da76403c3 100644
--- a/src/resolution/frameworks/index.ts
+++ b/src/resolution/frameworks/index.ts
@@ -31,7 +31,7 @@ import { aspnetResolver } from './csharp';
import { swiftUIResolver, uikitResolver, vaporResolver } from './swift';
import { swiftObjcBridgeResolver } from './swift-objc';
import { reactNativeBridgeResolver } from './react-native';
-import { expoModulesResolver } from './expo-modules';
+import { expoModulesResolver, expoModulesJsResolver } from './expo-modules';
import { expoRouterResolver } from './expo-router';
import { fabricViewResolver } from './fabric';
import { cicsResolver } from './cics';
@@ -90,6 +90,8 @@ const FRAMEWORK_RESOLVERS: FrameworkResolver[] = [
reactNativeBridgeResolver,
// Expo Modules — Function/AsyncFunction/Property DSL on Swift/Kotlin
expoModulesResolver,
+ // Expo Modules, JS side — `M.fn()` on a `requireNativeModule('N')` binding → module N's `fn`
+ expoModulesJsResolver,
// Expo Router — `app/` screen files → route nodes; `router.push('/x')` → navigates edges
expoRouterResolver,
// React Native Fabric / Codegen view components — TS spec → component nodes
@@ -177,6 +179,6 @@ export { aspnetResolver } from './csharp';
export { swiftUIResolver, uikitResolver, vaporResolver } from './swift';
export { swiftObjcBridgeResolver } from './swift-objc';
export { reactNativeBridgeResolver } from './react-native';
-export { expoModulesResolver } from './expo-modules';
+export { expoModulesResolver, expoModulesJsResolver } from './expo-modules';
export { expoRouterResolver } from './expo-router';
export { fabricViewResolver } from './fabric';
diff --git a/src/resolution/import-resolver.ts b/src/resolution/import-resolver.ts
index 1d2e71630..01f3dc244 100644
--- a/src/resolution/import-resolver.ts
+++ b/src/resolution/import-resolver.ts
@@ -933,8 +933,11 @@ export function extractImportMappings(
function extractJSImports(content: string): ImportMapping[] {
const mappings: ImportMapping[] = [];
- // ES6 imports
- const importRegex = /import\s+(?:(\w+)\s*,?\s*)?(?:\{([^}]+)\})?\s*(?:(\*)\s+as\s+(\w+))?\s*from\s*['"]([^'"]+)['"]/g;
+ // ES6 imports. `import type { X }` / `import type * as ns` is TypeScript's
+ // type-only form, not a default import named `type` — which every such
+ // line used to add, making `type.innerType()` a call on an import.
+ // (`import type from './x'` still binds `type`: backtracking gives it back.)
+ const importRegex = /import\s+(?:type\s+(?=[{*]|(?!from\b)\w))?(?:(\w+)\s*,?\s*)?(?:\{([^}]+)\})?\s*(?:(\*)\s+as\s+(\w+))?\s*from\s*['"]([^'"]+)['"]/g;
let match;
while ((match = importRegex.exec(content)) !== null) {
@@ -953,7 +956,8 @@ function extractJSImports(content: string): ImportMapping[] {
// Named imports
if (namedImports) {
- const names = namedImports.split(',').map((s) => s.trim());
+ // `{ util, type objectUtil }`: an inline `type` modifier is not part of the name.
+ const names = namedImports.split(',').map((s) => s.trim().replace(/^type\s+(?=\w)/, ''));
for (const name of names) {
const aliasMatch = name.match(/(\w+)\s+as\s+(\w+)/);
if (aliasMatch) {
diff --git a/src/resolution/name-matcher.ts b/src/resolution/name-matcher.ts
index ea09c6c01..a35fcf753 100644
--- a/src/resolution/name-matcher.ts
+++ b/src/resolution/name-matcher.ts
@@ -1509,6 +1509,12 @@ export function matchByExactName(
const bareJs = isBareJsCall(ref, context);
const bareGo = isBareGoCall(ref, context);
const barePhp = isBarePhpCall(ref, context);
+ // A type, a value or an import the file binds from a package outside the
+ // repository names nothing in it, whatever kind of reference it is.
+ if (!bareJs && JS_FAMILY.has(ref.language) && ref.referenceKind !== 'calls' &&
+ /^[A-Za-z_$][\w$]*$/.test(ref.referenceName) && isOutOfRepoBinding(ref.referenceName, ref, context)) {
+ return null;
+ }
if (bareJs) {
const storeAction = matchJsStoreBindingCall(ref, context);
if (storeAction) return storeAction;
@@ -3598,10 +3604,22 @@ export function matchMethodCall(
// Filter to same-language candidates first
const sameLanguageMethods = methods.filter(m => m.language === ref.language);
- const targetMethods = sameLanguageMethods.length > 0 ? sameLanguageMethods : methods;
+ let targetMethods = sameLanguageMethods.length > 0 ? sameLanguageMethods : methods;
+ // A receiver the file imports is another module's value: never a method
+ // declared in the calling file. expo-camera's `CameraManager.isAvailableAsync()`
+ // (`import CameraManager from './ExpoCameraManager'`) went to `CameraView`'s
+ // own static `isAvailableAsync` — the method making the call.
+ // Ruling the caller's file out may reject a guess; it must never
+ // manufacture one — the one method left is then no likelier than before.
+ let narrowed = false;
+ if (JS_FAMILY.has(ref.language) && isImportBinding(objectOrClass!, ref, context)) {
+ const kept = targetMethods.filter((m) => m.filePath !== ref.filePath);
+ narrowed = kept.length !== targetMethods.length;
+ targetMethods = kept;
+ }
// If only one same-language method with this name exists, use it
- if (targetMethods.length === 1 && targetMethods[0]!.language === ref.language) {
+ if (targetMethods.length === 1 && !narrowed && targetMethods[0]!.language === ref.language) {
return {
original: ref,
targetNodeId: targetMethods[0]!.id,
@@ -3660,6 +3678,12 @@ function isImportedModuleReceiver(receiver: string, ref: UnresolvedRef, context:
return binding.isNamespace || context.isOutOfRepoImport?.(binding.source, ref.filePath, ref.language) === true;
}
+/** Is the root of a member call's receiver (`CameraManager` in `CameraManager.x`) one of the file's imports? */
+function isImportBinding(receiver: string, ref: UnresolvedRef, context: ResolutionContext): boolean {
+ const root = receiver.split('.')[0]!;
+ return context.getImportMappings?.(ref.filePath, ref.language)?.some((m) => m.localName === root) === true;
+}
+
/** Does the file bind `name` by importing it from outside the repository? */
function isOutOfRepoBinding(name: string, ref: UnresolvedRef, context: ResolutionContext): boolean {
const binding = context.getImportMappings?.(ref.filePath, ref.language)?.find((m) => m.localName === name);
@@ -4558,6 +4582,9 @@ export function matchFuzzy(
const callableCandidates = candidates.filter((n) => callableKinds.has(n.kind) && !(typeRef && !canNameInTypePosition(n)) &&
!(rustBare && (n.name !== ref.referenceName || !isRustNameInScope(n, ref, context))) &&
!(ref.language === 'python' && n.name !== ref.referenceName) &&
+ // JS and TS too: halo's `type RsbuildConfig` (imported from @rsbuild/core)
+ // is not its local `rsbuildConfig`, kit's vitest `Mock` not a `mock`.
+ !(JS_FAMILY.has(ref.language) && n.name !== ref.referenceName) &&
!(pythonShape && !fitsPythonCallShape(n, pythonShape, ref, context)) &&
!(javaBare && n.kind === 'method' && !isJavaMethodInScope(n, ref, context)))
.filter((n) => (ref.referenceKind !== 'references' && ref.referenceKind !== 'function_ref') ||
@@ -4587,8 +4614,9 @@ export function matchFuzzy(
isVisibleAcrossFiles(finalCandidates[0]!, ref, context) &&
isCrossFileReachable(finalCandidates[0]!, ref, context) &&
!(isBareJsCall(ref, context) &&
- (TYPE_MEMBER_KINDS.has(finalCandidates[0]!.kind) || isOutOfRepoBinding(ref.referenceName, ref, context) ||
+ (TYPE_MEMBER_KINDS.has(finalCandidates[0]!.kind) ||
(finalCandidates[0]!.filePath !== ref.filePath && isLocallyBoundJsName(ref.referenceName, ref.filePath, context)))) &&
+ !(JS_FAMILY.has(ref.language) && isOutOfRepoBinding(ref.referenceName, ref, context)) &&
!(finalCandidates[0]!.kind === 'method' && isBareGoCall(ref, context)) &&
// A bare PHP call is a function call (case-insensitive, so fuzzy may find
// one) — never the class `View` for `view(…)`, never a method.