GitNexus/gitnexus/test/unit/scope-resolution/name-fallback-visibility.test.ts
Abhinav Pandey 0ede6ae501
fix(rust): respect Cargo target boundaries in name fallback (#3294)
* fix(rust): respect Cargo target boundaries in name fallback

* fix(rust): read Cargo sources through validated file descriptors

* fix(rust): require matching imports for crate-root guesses

* fix(rust): require Cargo root identity for cross-target imports

* fix(rust): enforce Cargo identity across fallback imports

* fix(rust): keep Cargo membership on typical derive/macros (#3294)

Abort only genuine unknown expansion, ignore Cargo artifact layouts instead of every `target` path segment, and require a covering import for unanswered crate-root name fallback so #3253 still holds on ordinary Rust sources.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(rust): gate Cargo target membership in CI (#3253)

Add a parse-dispatch-rounds-style bench so derive/macro abort, target-path globs, and superlinear membership walks fail in CI instead of staying graph-invisible.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(rust): verify Cargo macro and re-export evidence

* style(bench): format Cargo membership benchmark

* fix(rust): require imports for cross-file root guesses

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 11:36:07 +01:00

938 lines
35 KiB
TypeScript

/**
* Unit tests for the per-language `isGlobalNameFallbackPlausible` hooks and the
* shared path arithmetic they are built on.
*
* These hooks decide whether a UNIQUE-NAME GUESS is allowed to become a labeled
* CALLS edge or must be dropped by a language visibility rule. A wrong `false`
* deletes a real edge, so uncertainty alone must not invent a refusal. It also
* must not bypass an independent rule: Rust cross-file bare calls still need
* a visible use path, whether Cargo membership is known or unknown. Tests pin
* both required refusals and permitted guesses under incomplete evidence.
*
* Pure functions over synthetic stubs — no pipeline, no fixtures.
*/
import { describe, it, expect } from 'vitest';
import type { ParsedFile, ParsedImport, SymbolDefinition } from 'gitnexus-shared';
import { goIsGlobalNameFallbackPlausible } from '../../../src/core/ingestion/languages/go/name-fallback-visibility.js';
import { dartIsGlobalNameFallbackPlausible } from '../../../src/core/ingestion/languages/dart/name-fallback-visibility.js';
import { rustIsGlobalNameFallbackPlausible } from '../../../src/core/ingestion/languages/rust/name-fallback-visibility.js';
import { swiftIsGlobalNameFallbackPlausible } from '../../../src/core/ingestion/languages/swift/name-fallback-visibility.js';
import { rubyIsGlobalNameFallbackPlausible } from '../../../src/core/ingestion/languages/ruby/name-fallback-visibility.js';
import {
directoryOf,
modulePathReaches,
moduleSegments,
stripExtension,
} from '../../../src/core/ingestion/scope-resolution/utils/name-fallback-visibility.js';
const namedImport = (targetRaw: string, localName = 'x'): ParsedImport => ({
kind: 'named',
localName,
importedName: localName,
targetRaw,
});
const mkCaller = (
filePath: string,
imports: readonly ParsedImport[] = [],
referenceSites: ParsedFile['referenceSites'] = [],
localDefs: readonly SymbolDefinition[] = [],
): ParsedFile =>
({
filePath,
parsedImports: imports,
referenceSites,
localDefs,
}) as unknown as ParsedFile;
/** A bare (unqualified) call site — the shape the name-guess tier exists for. */
const BARE_SITE = { name: 'unique_helper_xyz' } as const;
const mkCandidate = (filePath: string, qualifiedName: string, ownerId?: string): SymbolDefinition =>
({
nodeId: `def:${filePath}:${qualifiedName}`,
filePath,
type: 'Function',
qualifiedName,
ownerId,
}) as unknown as SymbolDefinition;
describe('shared path arithmetic', () => {
it('splits module paths on /, :: and .', () => {
expect(moduleSegments('a/b/c')).toEqual(['a', 'b', 'c']);
expect(moduleSegments('crate::a::b')).toEqual(['crate', 'a', 'b']);
expect(moduleSegments('com.example.Thing')).toEqual(['com', 'example', 'Thing']);
});
it('does not split a path-bearing specifier on its extension dot', () => {
expect(moduleSegments('./util/parse.js')).toEqual(['util', 'parse.js']);
});
it('matches a written module prefix against a repo-relative directory', () => {
// The written import carries a module prefix that is not a directory.
expect(modulePathReaches('github.com/org/svc/internal/models', 'internal/models')).toBe(true);
// ...and the reverse, when the module manifest sits in a subdirectory.
expect(modulePathReaches('mod/internal/models', 'svc/internal/models')).toBe(true);
});
it('does not match unrelated paths that merely share a middle segment', () => {
expect(modulePathReaches('github.com/org/svc/internal/models', 'internal/handlers')).toBe(
false,
);
expect(modulePathReaches('a/b', 'c/d')).toBe(false);
});
it('reports no match for an empty path on either side', () => {
expect(modulePathReaches('', 'a/b')).toBe(false);
expect(modulePathReaches('a/b', '')).toBe(false);
});
it('derives directories and strips extensions', () => {
expect(directoryOf('a/b/c.go')).toBe('a/b');
expect(directoryOf('main.go')).toBe('');
expect(stripExtension('a/b.rs')).toBe('a/b');
expect(stripExtension('a/b')).toBe('a/b');
});
});
describe('Go: isGlobalNameFallbackPlausible', () => {
it('allows a qualified pkg.Export() that reached the guess tier', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [namedImport('example.com/mod/a')]),
candidate: mkCandidate('a/helper.go', 'Export'),
site: { rawQualifiedName: 'a.Export' },
}),
).toBe(true);
});
it('still REFUSES a qualified call to an unexported or test-only declaration', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go'),
candidate: mkCandidate('a/helper.go', 'private'),
site: { rawQualifiedName: 'a.private' },
}),
).toBe(false);
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go'),
candidate: mkCandidate('a/helper_test.go', 'ExportedTestHelper'),
site: { rawQualifiedName: 'a.ExportedTestHelper' },
}),
).toBe(false);
});
it('allows an external test package to dot-import exported production functions', () => {
const callerParsed = mkCaller('foo/caller_test.go', [
{ kind: 'wildcard', targetRaw: 'example.com/mod/foo' },
]);
const sourceTextOf = (file: string) =>
file.endsWith('_test.go') ? 'package foo_test' : 'package foo';
expect(
goIsGlobalNameFallbackPlausible({
callerParsed,
sourceTextOf,
candidate: mkCandidate('foo/helper.go', 'Helper'),
}),
).toBe(true);
expect(
goIsGlobalNameFallbackPlausible({
callerParsed,
sourceTextOf,
candidate: mkCandidate('foo/helper.go', 'helper'),
}),
).toBe(false);
});
it('REFUSES an unexported identifier from another package', () => {
// The headline case: `a.uniqueHelperXyz` is invisible to package `b`, and no
// import can make it visible, so the guess is impossible rather than weak.
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go'),
candidate: mkCandidate('a/helper.go', 'uniqueHelperXyz'),
}),
).toBe(false);
});
it('REFUSES an unexported identifier even when the package IS imported', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [namedImport('example.com/mod/a')]),
candidate: mkCandidate('a/helper.go', 'uniqueHelperXyz'),
}),
).toBe(false);
});
it('allows an unexported identifier inside the SAME package directory', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('a/caller.go'),
candidate: mkCandidate('a/helper.go', 'uniqueHelperXyz'),
}),
).toBe(true);
});
it('REFUSES an exported identifier when the caller never imports its package', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [namedImport('example.com/mod/unrelated')]),
candidate: mkCandidate('a/helper.go', 'UniqueHelperXyz'),
}),
).toBe(false);
});
it('allows an exported identifier whose package the caller dot-imports', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [
{ kind: 'wildcard', targetRaw: 'example.com/mod/a' },
]),
candidate: mkCandidate('a/helper.go', 'UniqueHelperXyz'),
}),
).toBe(true);
});
it('REFUSES methods as bare calls even with a dot import', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go'),
candidate: mkCandidate('a/helper.go', 'Host.doThing'),
}),
).toBe(false);
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [{ kind: 'wildcard', targetRaw: 'mod/a' }]),
candidate: mkCandidate('a/helper.go', 'Host.DoThing'),
}),
).toBe(false);
});
it.each(['a', 'renamed', '_'])(
'REFUSES an ordinary/alias/blank import (%s) for a bare call',
(localName) => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go', [
{ kind: 'namespace', localName, importedName: 'a', targetRaw: 'mod/a' },
]),
candidate: mkCandidate('a/helper.go', 'UniqueHelperXyz'),
}),
).toBe(false);
},
);
it('keeps internal tests of a production package named foo_test in the same package', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('a/caller_test.go'),
candidate: mkCandidate('a/helper.go', 'uniqueHelperXyz'),
sourceTextOf: () => 'package foo_test\n',
}),
).toBe(true);
});
it('REFUSES same-directory declarations when the known package clauses differ', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('a/caller_test.go'),
candidate: mkCandidate('a/helper.go', 'UniqueHelperXyz'),
sourceTextOf: (path) => (path.endsWith('_test.go') ? 'package a_test\n' : 'package a\n'),
}),
).toBe(false);
});
it('REFUSES a Method definition even in its own package with no qualified name', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('a/caller.go'),
candidate: { ...mkCandidate('a/helper.go', 'DoThing'), type: 'Method' },
}),
).toBe(false);
});
it('does not refuse when there is no identifier to judge', () => {
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('b/caller.go'),
candidate: mkCandidate('a/helper.go', ''),
}),
).toBe(true);
});
it("REFUSES an exported helper declared in another package's `_test.go`, module root included", () => {
// A `_test.go` file is compiled only into its own package's test binary;
// no other package can see it. The module-root exception used to run first
// and accept `root_helper_test.go`'s exports for every subdirectory caller.
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('internal/svc/caller.go', [namedImport('github.com/org/mod')]),
candidate: mkCandidate('helpers_test.go', 'ExportedTestHelper'),
}),
).toBe(false);
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('internal/svc/caller.go', [
namedImport('github.com/org/mod/internal/models'),
]),
candidate: mkCandidate('internal/models/fixtures_test.go', 'NewFixture'),
}),
).toBe(false);
// ...even from another package's own test file.
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('internal/svc/caller_test.go', [
namedImport('github.com/org/mod/internal/models'),
]),
candidate: mkCandidate('internal/models/fixtures_test.go', 'NewFixture'),
}),
).toBe(false);
});
it('requires a dot import even for the module ROOT package', () => {
// The root package is imported by the module path alone, which the
// repo-relative layout cannot align against — undecidable, so allowed.
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('internal/svc/caller.go', [
{ kind: 'wildcard', targetRaw: 'github.com/org/mod' },
]),
candidate: mkCandidate('root.go', 'ExportedHelper'),
}),
).toBe(true);
expect(
goIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('internal/svc/caller.go'),
candidate: mkCandidate('root.go', 'ExportedHelper'),
}),
).toBe(false);
});
});
describe('Dart: isGlobalNameFallbackPlausible', () => {
it('does NOT refuse a library-private name from another directory — a `part` URI may cross it', () => {
// `part '../shared/gen.dart';` is legal Dart, and `part` directives are not
// extracted yet, so "different directory" is undecidable, not impossible.
// The edge stays a labeled guess rather than being deleted.
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/widgets/b.dart'),
candidate: mkCandidate('lib/models/a.dart', '_privateHelper'),
}),
).toBe(true);
});
it('allows a library-private name in a SIBLING file (possible `part`)', () => {
// `part` directives are not extracted yet, and parts are siblings of their
// library file — refusing here would delete the Flutter `foo.g.dart` edge.
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/b.dart'),
candidate: mkCandidate('lib/a.dart', '_privateHelper'),
}),
).toBe(true);
});
it('allows the generated-part idiom: `_$FooFromJson` in `foo.g.dart` beside `foo.dart`', () => {
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/models/foo.dart'),
candidate: mkCandidate('lib/models/foo.g.dart', '_$FooFromJson'),
}),
).toBe(true);
});
it('allows a library-private name in the same file', () => {
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/a.dart'),
candidate: mkCandidate('lib/a.dart', '_privateHelper'),
}),
).toBe(true);
});
it('allows a library-private name across directories when the caller names the file', () => {
// The `part` / `part of` direction, once the extractor surfaces it as an
// import target: an explicit directive against the candidate's file wins.
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/widgets/b.dart', [namedImport('lib/models/a.dart')]),
candidate: mkCandidate('lib/models/a.dart', '_privateHelper'),
}),
).toBe(true);
});
it('allows a public name across files', () => {
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/b.dart'),
candidate: mkCandidate('lib/a.dart', 'publicHelper'),
}),
).toBe(true);
});
it('judges privacy on the member, not its owner', () => {
// `_Foo.bar` is a public member of a private class — the member name is what
// a bare call would name, so it is not refused on the owner's underscore.
expect(
dartIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/b.dart'),
candidate: mkCandidate('lib/a.dart', '_Foo.bar'),
}),
).toBe(true);
});
});
describe('Rust: isGlobalNameFallbackPlausible', () => {
it.each([
['src/a/b.rs', 'super::unique_helper_xyz', 'src/a.rs'],
['src/a/b/c.rs', 'super::super::unique_helper_xyz', 'src/a/mod.rs'],
['src/a/b.rs', 'self::child::unique_helper_xyz', 'src/a/b/child.rs'],
])('resolves relative imports from %s', (caller, target, candidate) => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller(caller, [namedImport(target, BARE_SITE.name)]),
candidate: mkCandidate(candidate, BARE_SITE.name),
}),
).toBe(true);
});
it('REFUSES a cross-module item with no covering `use`', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [namedImport('crate::unrelated')]),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(false);
});
it('allows a same-file item', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/a.rs'),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('REFUSES a bare item when only its module was imported', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [namedImport('crate::a')]),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(false);
});
it('treats `mod.rs` as its parent directory module', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [{ kind: 'wildcard', targetRaw: 'crate::net::http' }]),
candidate: mkCandidate('src/net/http/mod.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('REFUSES a crate-root candidate without cargo identity or a covering use', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs'),
candidate: mkCandidate('lib.rs', 'unique_helper_xyz'),
}),
).toBe(false);
});
it('allows a crate-root candidate when a covering use names it', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [{ kind: 'wildcard', targetRaw: 'crate' }]),
candidate: mkCandidate('lib.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('REFUSES a crate:: import that names the same module path in another workspace crate', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: { name: 'helper' },
callerParsed: mkCaller('crates/a/src/caller.rs', [
namedImport('crate::tools::helper', 'helper'),
]),
candidate: mkCandidate('crates/b/src/tools.rs', 'helper'),
}),
).toBe(false);
expect(
rustIsGlobalNameFallbackPlausible({
site: { name: 'helper' },
callerParsed: mkCaller('crates/a/src/caller.rs', [
namedImport('crate::tools::helper', 'helper'),
]),
candidate: mkCandidate('crates/a/src/tools.rs', 'helper'),
}),
).toBe(true);
});
it('allows an item whose `use` names the ITEM rather than only its module', () => {
// A bare call exercises the import matcher, not the qualified-site bypass.
const candidate = mkCandidate('src/user.rs', 'build_user');
expect(
rustIsGlobalNameFallbackPlausible({
site: { name: 'build_user' },
callerParsed: mkCaller('src/main.rs', [
namedImport('crate::user::build_user', 'build_user'),
]),
candidate,
}),
).toBe(true);
expect(
rustIsGlobalNameFallbackPlausible({
site: { name: 'build_user' },
callerParsed: mkCaller('src/main.rs'),
candidate,
}),
).toBe(false);
});
it('REFUSES when the only `use` of the module names a DIFFERENT item', () => {
// `use crate::a::other;` brings `other` into scope, not `helper`. The
// parent-path match used to accept every item of `a` on its strength.
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [namedImport('crate::a::other', 'other')]),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(false);
});
it('accepts an aliased import only under its local spelling', () => {
const callerParsed = mkCaller('src/b.rs', [
{
kind: 'named',
localName: 'renamed',
importedName: 'unique_helper_xyz',
targetRaw: 'crate::a::unique_helper_xyz',
},
]);
const candidate = mkCandidate('src/a.rs', 'unique_helper_xyz');
expect(rustIsGlobalNameFallbackPlausible({ callerParsed, candidate, site: BARE_SITE })).toBe(
false,
);
expect(
rustIsGlobalNameFallbackPlausible({ callerParsed, candidate, site: { name: 'renamed' } }),
).toBe(true);
});
it('allows a glob `use` of the module — every item is in scope', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [{ kind: 'wildcard', targetRaw: 'crate::a' }]),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('allows a `use` that names the candidate itself, with the item on the path', () => {
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [
namedImport('crate::a::unique_helper_xyz', 'unique_helper_xyz'),
]),
candidate: mkCandidate('src/a.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('keeps a nested src/ directory as a module segment', () => {
// `src/src/helper.rs` is crate::src::helper. A glob of an unrelated
// `foo::helper` used to match after the crate-root while-strip reduced the
// file to just `helper`.
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [{ kind: 'wildcard', targetRaw: 'crate::foo::helper' }]),
candidate: mkCandidate('src/src/helper.rs', 'unique_helper_xyz'),
}),
).toBe(false);
expect(
rustIsGlobalNameFallbackPlausible({
site: BARE_SITE,
callerParsed: mkCaller('src/b.rs', [{ kind: 'wildcard', targetRaw: 'crate::src::helper' }]),
candidate: mkCandidate('src/src/helper.rs', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('does not judge a PATH-QUALIFIED call site', () => {
// `User::new(...)` names its path in source. Refusing it for lacking a
// `use` of the module would delete an edge the code spells out — the
// regression this carve-out exists for (rust-scope.test.ts).
expect(
rustIsGlobalNameFallbackPlausible({
site: { name: 'new', rawQualifiedName: 'User::new' },
callerParsed: mkCaller('src/main.rs'),
candidate: mkCandidate('src/user.rs', 'User.new'),
}),
).toBe(true);
});
});
describe('Swift: isGlobalNameFallbackPlausible', () => {
it.each(['sources', 'tests', 'Tests'])(
'does not invent target modules under unconfigured %s folders',
(root) => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller(`${root}/App/Caller.swift`),
candidate: mkCandidate(`${root}/Core/Helper.swift`, 'helper'),
}),
).toBe(true);
},
);
it('does not invent module boundaries between arbitrary Xcode directories', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('App/Caller.swift'),
candidate: mkCandidate('Shared/Helper.swift', 'helper'),
}),
).toBe(true);
});
it('recognizes distinct src targets and requires a matching import', () => {
const candidate = mkCandidate('src/Core/Helper.swift', 'helper');
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('src/App/Caller.swift'),
candidate,
}),
).toBe(false);
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('src/App/Caller.swift', [namedImport('Core')]),
candidate,
}),
).toBe(true);
});
it('allows a cross-file candidate in the same target (whole-module internal)', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Sources/Core/Caller.swift'),
candidate: mkCandidate('Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(true);
});
it('REFUSES a candidate in another target the caller never imports', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Sources/App/Caller.swift'),
candidate: mkCandidate('Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(false);
});
it('does not treat same-named targets in different packages as one module', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Sources/Core/Caller.swift'),
candidate: mkCandidate('Package/Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(false);
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Sources/Core/Caller.swift', [namedImport('Core')]),
candidate: mkCandidate('Package/Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(true);
});
it('allows a candidate in another target the caller imports', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Sources/App/Caller.swift', [namedImport('Core')]),
candidate: mkCandidate('Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(true);
});
it('does not refuse when the layout heuristic cannot place a file', () => {
expect(
swiftIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('Caller.swift'),
candidate: mkCandidate('Sources/Core/Helper.swift', 'uniqueHelperXyz'),
}),
).toBe(true);
});
});
describe('Ruby: isGlobalNameFallbackPlausible', () => {
/** A candidate file whose `localDefs` carry the owner with the given label. */
const ownerFile =
(ownerId: string, label: 'Class' | 'Trait') =>
(filePath: string): ParsedFile | undefined =>
({
filePath,
parsedImports: [],
referenceSites: [],
localDefs: [{ nodeId: ownerId, filePath, type: label, qualifiedName: 'Billing' }],
}) as unknown as ParsedFile;
it('allows a TOP-LEVEL method across files (the autoload shape)', () => {
// Ruby keeps this guess on purpose: with zeitwerk a file really can call a
// method whose defining file it never requires.
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'unique_helper_xyz'),
}),
).toBe(true);
});
it('REFUSES a CLASS-owned method whose class the caller never names', () => {
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [namedImport('app/unrelated')]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'unique_helper_xyz()',
}),
).toBe(false);
});
it('allows a MODULE-owned method with no mention — Rails mixes modules in for you', () => {
// `module ApplicationHelper` is included into every view by the framework;
// a bare `format_money()` there is legal with no include/require/constant.
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/views/show.html.erb'),
candidate: mkCandidate('app/helpers/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Trait'),
sourceTextOf: () => 'unique_helper_xyz()',
}),
).toBe(true);
});
it('allows a class-owned method when the caller INHERITS from anything (transitive chains)', () => {
// `class UsersController < AdminController` reaches ApplicationController's
// methods while naming only AdminController — undecidable from one file.
const inherits = {
kind: 'inherits',
name: 'AdminController',
} as unknown as ParsedFile['referenceSites'][number];
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/controllers/users_controller.rb', [], [inherits]),
candidate: mkCandidate(
'app/controllers/application_controller.rb',
'ApplicationController#unique_helper_xyz',
'def:ApplicationController',
),
parsedFileOf: ownerFile('def:ApplicationController', 'Class'),
sourceTextOf: () => 'class UsersController < AdminController\n unique_helper_xyz()\nend\n',
}),
).toBe(true);
});
it('allows a class-owned method when the caller mixes anything in (include/extend marker)', () => {
const mixin = {
kind: 'namespace',
localName: 'Auditable',
importedName: 'Auditable',
targetRaw: '__heritage__:include:Auditable:Report',
} as unknown as ParsedImport;
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/models/report.rb', [mixin]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'include Auditable; unique_helper_xyz()',
}),
).toBe(true);
});
it('allows a class-owned method when the caller file DEFINES a module (a mixin body)', () => {
// `module PostGuardian; def can_see?(post); is_staff? ...` — the module's
// methods run inside `class Guardian`, which includes it; the module file
// never names Guardian.
const moduleDef = {
nodeId: 'def:PostGuardian',
filePath: 'lib/guardian/post_guardian.rb',
type: 'Trait',
qualifiedName: 'PostGuardian',
} as unknown as SymbolDefinition;
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('lib/guardian/post_guardian.rb', [], [], [moduleDef]),
candidate: mkCandidate('lib/guardian.rb', 'Guardian#is_staff?', 'def:Guardian'),
parsedFileOf: ownerFile('def:Guardian', 'Class'),
sourceTextOf: () => 'module PostGuardian; def can_see?(post); is_staff?; end; end\n',
}),
).toBe(true);
});
it('does not refuse when the owner cannot be typed (no file lookup)', () => {
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
sourceTextOf: () => 'unique_helper_xyz()',
}),
).toBe(true);
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: () => undefined,
sourceTextOf: () => 'unique_helper_xyz()',
}),
).toBe(true);
});
it('allows a class-owned method when the caller requires its namespace (snake_case path)', () => {
// `require 'billing/invoice_service'` names `Billing::InvoiceService` — the
// path is snake_case and the constant CamelCase, so the match normalizes.
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [namedImport('billing/invoice_service')]),
candidate: mkCandidate(
'app/a.rb',
'Billing::InvoiceService#unique_helper_xyz',
'def:InvoiceService',
),
parsedFileOf: ownerFile('def:InvoiceService', 'Class'),
sourceTextOf: () => "require 'billing/invoice_service'; unique_helper_xyz()",
}),
).toBe(true);
});
it('allows a class-owned method when the caller includes the constant by name', () => {
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [namedImport('Billing', 'Billing')]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'include Billing; unique_helper_xyz()',
}),
).toBe(true);
});
it('allows a class-owned method when the caller REOPENS the class', () => {
const klass = {
type: 'Class',
qualifiedName: 'Invoice',
nodeId: 'def:Invoice',
} as unknown as SymbolDefinition;
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/models/invoice_reporting.rb', [], [], [klass]),
candidate: mkCandidate('app/models/invoice.rb', 'Invoice#total', 'def:Invoice'),
parsedFileOf: ownerFile('def:Invoice', 'Class'),
sourceTextOf: () => 'class Invoice\n def report; total; end\nend\n',
}),
).toBe(true);
});
it('allows a class-owned method when the caller mentions the constant', () => {
const site = { name: 'Billing' } as unknown as ParsedFile['referenceSites'][number];
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [], [site]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'Billing; unique_helper_xyz()',
}),
).toBe(true);
});
it('REFUSES when the caller only mentions a LONGER constant containing the name as a substring', () => {
// `BillingService.build` is not a mention of `Billing`; `includes()` said it was.
const site = {
name: 'build',
rawQualifiedName: 'BillingService.build',
} as unknown as ParsedFile['referenceSites'][number];
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [], [site]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'BillingService.build; unique_helper_xyz()',
}),
).toBe(false);
});
it('allows a qualified mention whose SEGMENT is the constant (`Acme::Billing.new`)', () => {
const site = {
name: 'new',
rawQualifiedName: 'Acme::Billing.new',
} as unknown as ParsedFile['referenceSites'][number];
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb', [], [site]),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
}),
).toBe(true);
});
it('keeps the LABELED guess when the caller file rebinds `self` (`instance_eval` DSL blocks) (magyargergo)', () => {
// `service.instance_eval do unique_helper_xyz() end` dispatches the bare
// call on `service`, so the class never being named here proves nothing.
const src =
'def caller(service)\n service.instance_eval do\n unique_helper_xyz()\n end\nend\n';
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => src,
}),
).toBe(true);
// ...and still REFUSES when the source has no such block.
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => 'def caller\n unique_helper_xyz()\nend\n',
}),
).toBe(false);
// A missing source text is an unanswered question, not a refusal.
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
sourceTextOf: () => undefined,
}),
).toBe(true);
});
it('keeps the labeled guess when the source lookup itself is absent', () => {
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'Billing.unique_helper_xyz', 'def:Billing'),
parsedFileOf: ownerFile('def:Billing', 'Class'),
}),
).toBe(true);
});
it('does not refuse an owned method with no nameable namespace', () => {
expect(
rubyIsGlobalNameFallbackPlausible({
callerParsed: mkCaller('app/b.rb'),
candidate: mkCandidate('app/a.rb', 'unique_helper_xyz', 'def:anon'),
parsedFileOf: ownerFile('def:anon', 'Class'),
}),
).toBe(true);
});
});