mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-12 23:02:45 +00:00
Merge final Go stack into workspace split
This commit is contained in:
commit
c13e44854b
9 changed files with 142 additions and 25 deletions
|
|
@ -10,7 +10,7 @@
|
|||
* then only if the declaration is `public`.
|
||||
*
|
||||
* A module is approximated by its source directory, the layout every Swift
|
||||
* package manifest produces: `Sources/<Target>/…` and `Tests/<Target>/…`.
|
||||
* package configuration discovers: `Sources/<Target>/…` (also under `Package/`).
|
||||
* `src/<Target>/…` is also recognized by the package configuration loader.
|
||||
* Files outside these layouts have unknown module identity.
|
||||
*
|
||||
|
|
@ -24,14 +24,8 @@
|
|||
import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
|
||||
import { modulePathReaches } from '../../scope-resolution/utils/name-fallback-visibility.js';
|
||||
|
||||
/** Directory names that hold one subdirectory PER TARGET rather than sources. */
|
||||
const SWIFT_TARGET_ROOTS: ReadonlySet<string> = new Set([
|
||||
'Sources',
|
||||
'Tests',
|
||||
'sources',
|
||||
'tests',
|
||||
'src',
|
||||
]);
|
||||
/** Keep aligned with loadSwiftPackageConfig: other layouts are unconfigured. */
|
||||
const SWIFT_TARGET_ROOTS: ReadonlySet<string> = new Set(['Sources', 'src']);
|
||||
|
||||
/**
|
||||
* The module (target) a Swift file belongs to.
|
||||
|
|
|
|||
|
|
@ -195,7 +195,8 @@ export function formatNameFallbackSummary(
|
|||
summary.distinctGuessedPairs !== undefined
|
||||
? ` (${summary.distinctGuessedPairs} distinct caller-file/name pairs)`
|
||||
: '';
|
||||
return `name-fallback resolution: ${summary.totalGuessed} call sites${pairs}, ${summary.totalRefused} refused as impossible (guessed/refused by language: ${languages})${ambiguous}`;
|
||||
const siteUnit = summary.totalGuessed === 1 ? 'call site' : 'call sites';
|
||||
return `name-fallback resolution: ${summary.totalGuessed} ${siteUnit}${pairs}, ${summary.totalRefused} refused as impossible (guessed/refused by language: ${languages})${ambiguous}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ export interface EsmExportEvidence {
|
|||
readonly commonJs: boolean;
|
||||
}
|
||||
|
||||
const CJS_EXPORT_ASSIGNMENT = /^\s*(this\.[A-Za-z_$][\w$]*\s*=)/;
|
||||
|
||||
/** Read binding patterns, never initializer expressions or property keys. */
|
||||
function bindsReceiver(node: SyntaxNode | null, name: string): boolean {
|
||||
if (node === null) return false;
|
||||
|
|
@ -57,18 +55,33 @@ function bindsReceiver(node: SyntaxNode | null, name: string): boolean {
|
|||
if (node.type === 'assignment_pattern')
|
||||
return bindsReceiver(node.childForFieldName('left'), name);
|
||||
if (node.type === 'pair_pattern') return bindsReceiver(node.childForFieldName('value'), name);
|
||||
if (node.type === 'import_specifier') {
|
||||
return bindsReceiver(node.childForFieldName('alias') ?? node.childForFieldName('name'), name);
|
||||
}
|
||||
if (node.type === 'required_parameter' || node.type === 'optional_parameter') {
|
||||
return bindsReceiver(node.childForFieldName('pattern'), name);
|
||||
}
|
||||
return (
|
||||
['formal_parameters', 'object_pattern', 'array_pattern', 'rest_pattern'].includes(node.type) &&
|
||||
node.namedChildren.some((child) => bindsReceiver(child, name))
|
||||
[
|
||||
'formal_parameters',
|
||||
'object_pattern',
|
||||
'array_pattern',
|
||||
'rest_pattern',
|
||||
'import_clause',
|
||||
'named_imports',
|
||||
'namespace_import',
|
||||
].includes(node.type) && node.namedChildren.some((child) => bindsReceiver(child, name))
|
||||
);
|
||||
}
|
||||
|
||||
/** A locally bound `module`/`exports` is not Node's export receiver. */
|
||||
function isExportReceiverShadowed(node: SyntaxNode, name: string): boolean {
|
||||
for (let scope = node.parent; scope !== null; scope = scope.parent) {
|
||||
if (
|
||||
['function_expression', 'generator_function', 'class'].includes(scope.type) &&
|
||||
scope.childForFieldName('name')?.text === name
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
bindsReceiver(scope.childForFieldName('parameters'), name) ||
|
||||
bindsReceiver(scope.childForFieldName('parameter'), name)
|
||||
|
|
@ -81,6 +94,13 @@ function isExportReceiverShadowed(node: SyntaxNode, name: string): boolean {
|
|||
? statement.childForFieldName('declaration')
|
||||
: statement;
|
||||
if (declaration === null) continue;
|
||||
if (
|
||||
declaration.type === 'import_statement' &&
|
||||
declaration.namedChildren.some(
|
||||
(child) => child.type === 'import_clause' && bindsReceiver(child, name),
|
||||
)
|
||||
)
|
||||
return true;
|
||||
if (
|
||||
declaration.type === 'lexical_declaration' ||
|
||||
declaration.type === 'variable_declaration'
|
||||
|
|
@ -197,8 +217,16 @@ export function collectEsmExportEvidence(
|
|||
namedLocals.add(child.text);
|
||||
}
|
||||
}
|
||||
} else if (stmt.type === 'expression_statement' && CJS_EXPORT_ASSIGNMENT.test(stmt.text)) {
|
||||
commonJs = true;
|
||||
} else if (stmt.type === 'expression_statement') {
|
||||
const assignment = stmt.namedChildren[0];
|
||||
const left =
|
||||
assignment?.type === 'assignment_expression' ? assignment.childForFieldName('left') : null;
|
||||
if (
|
||||
left !== null &&
|
||||
(left.type === 'member_expression' || left.type === 'subscript_expression') &&
|
||||
left.childForFieldName('object')?.type === 'this'
|
||||
)
|
||||
commonJs = true;
|
||||
}
|
||||
}
|
||||
return { namedLocals, commonJs };
|
||||
|
|
@ -235,7 +263,9 @@ export function esmExportVerdict(
|
|||
// program without crossing a nesting boundary — one inside a namespace or
|
||||
// ambient-module body is that container's export (see NESTING_BOUNDARIES).
|
||||
let underExport = false;
|
||||
let functionScopedVariable = false;
|
||||
while (current !== null && current.type !== 'program') {
|
||||
if (current.type === 'variable_declaration') functionScopedVariable = true;
|
||||
if (current.type === 'export_statement') {
|
||||
underExport = true;
|
||||
current = current.parent;
|
||||
|
|
@ -248,6 +278,12 @@ export function esmExportVerdict(
|
|||
return evidence.commonJs ? undefined : false;
|
||||
}
|
||||
if (NESTING_BOUNDARIES.has(current.type) && current.id !== nameNode.parent?.id) {
|
||||
// `var` crosses blocks but never a function/namespace boundary. A
|
||||
// module-scoped `var` can therefore be exported by a later clause.
|
||||
if (current.type === 'statement_block' && functionScopedVariable) {
|
||||
current = current.parent;
|
||||
continue;
|
||||
}
|
||||
return evidence.commonJs ? undefined : false;
|
||||
}
|
||||
current = current.parent;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@
|
|||
* import produced, so no consumer could discount it.
|
||||
*
|
||||
* These tests pin both. The Go arm proves the impossible edge is now REFUSED,
|
||||
* with the same-package call kept as the control that shows the refusal is
|
||||
* targeted rather than a blanket disabling of the tier. The Ruby arm proves a
|
||||
* with a same-file call confirming ordinary local resolution is preserved.
|
||||
* That control does not exercise the global-name tier. The Ruby arm proves a
|
||||
* surviving guess is LABELED, since Ruby deliberately keeps the tier for
|
||||
* autoload. The last test is the regression that matters most: no edge from
|
||||
* this tier may ever again carry `import-resolved`.
|
||||
|
|
@ -71,10 +71,11 @@ func CallItRemotely() int {
|
|||
|
||||
afterAll(() => rmRepo(repoDir));
|
||||
|
||||
it('keeps the same-package call (control: the tier still works)', () => {
|
||||
it('keeps ordinary same-file resolution as a control', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const local = calls.find((c) => c.source === 'UseItLocally' && c.target === 'uniqueHelperXyz');
|
||||
expect(local).toBeDefined();
|
||||
expect(local!.rel.reason).not.toBe(GLOBAL_NAME_FALLBACK_REASON);
|
||||
});
|
||||
|
||||
it('emits NO caller edge from the other package', () => {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,32 @@ describe('@declaration.is-exported — Opus review follow-ups', () => {
|
|||
});
|
||||
|
||||
describe('@declaration.is-exported (JavaScript emitter)', () => {
|
||||
it('keeps bracket-form top-level this exports undecided like dot-form exports', () => {
|
||||
for (const [emit, file] of [
|
||||
[emitJsScopeCaptures, 'x.js'],
|
||||
[emitTsScopeCaptures, 'x.ts'],
|
||||
] as const) {
|
||||
const v = verdicts(emit, "function api() {} this['api'] = api;", file);
|
||||
expect(v.api).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('recognizes exported module-scoped var inside a block, but not block let or function-local var', () => {
|
||||
for (const [emit, file] of [
|
||||
[emitJsScopeCaptures, 'x.js'],
|
||||
[emitTsScopeCaptures, 'x.ts'],
|
||||
] as const) {
|
||||
const v = verdicts(
|
||||
emit,
|
||||
'if (ok) { var visible = 1; let hidden = 2; } function wrapper() { var nested = 3; } export { visible, hidden, nested };',
|
||||
file,
|
||||
);
|
||||
expect(v.visible).toBe('true');
|
||||
expect(v.hidden).toBe('false');
|
||||
expect(v.nested).toBe('false');
|
||||
}
|
||||
});
|
||||
|
||||
it('marks synthesized default-export HOC declarations in both emitters', () => {
|
||||
for (const [emit, file] of [
|
||||
[emitJsScopeCaptures, 'Widget.jsx'],
|
||||
|
|
@ -174,6 +200,10 @@ describe('@declaration.is-exported (JavaScript emitter)', () => {
|
|||
'function wrapper() { { module.exports = {}; let module; } }',
|
||||
'function wrapper({ receiver: module }) { module.exports = {}; }',
|
||||
'const module = {}; module.exports = { alpha() {} };',
|
||||
"import module from './other'; module.exports = {};",
|
||||
"import { receiver as exports } from './other'; exports.alpha = 1;",
|
||||
"import * as module from './other'; module.exports = {};",
|
||||
'const wrapper = function module() { module.exports = {}; };',
|
||||
])('ignores a shadowed CommonJS receiver: %s', (source) => {
|
||||
for (const [emit, file] of [
|
||||
[emitJsScopeCaptures, 'x.js'],
|
||||
|
|
@ -198,6 +228,15 @@ describe('@declaration.is-exported (JavaScript emitter)', () => {
|
|||
expect(v.hidden).toBeUndefined();
|
||||
});
|
||||
|
||||
it('an imported source name does not shadow a different local alias', () => {
|
||||
const v = verdicts(
|
||||
emitJsScopeCaptures,
|
||||
"import { module as other } from './other'; module.exports = {}; function hidden() {}",
|
||||
'x.js',
|
||||
);
|
||||
expect(v.hidden).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each(["module['exports']", 'module["exports"]'])(
|
||||
'recognizes %s object methods and properties as exports',
|
||||
(target) => {
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ describe('free-call dedup: the label is decided from every collapsed site, never
|
|||
const summary = summarizeNameFallback(outcomes);
|
||||
expect(summary?.totalGuessed).toBe(1);
|
||||
const line = formatNameFallbackSummary(summary);
|
||||
expect(line).toContain('name-fallback resolution: 1 call sites');
|
||||
expect(line).toContain('name-fallback resolution: 1 call site (');
|
||||
expect(line).not.toContain('CALLS edges');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -224,9 +224,15 @@ describe('formatNameFallbackSummary', () => {
|
|||
const line = formatNameFallbackSummary(
|
||||
summarizeNameFallback([guessed('ruby'), refused('go'), refused('go'), refused('go')]),
|
||||
);
|
||||
expect(line).toContain('1 call sites (1 distinct caller-file/name pairs)');
|
||||
expect(line).toContain('1 call site (1 distinct caller-file/name pairs)');
|
||||
expect(line).toContain('3 refused as impossible');
|
||||
// `go` has more total activity, so it leads.
|
||||
expect(line).toMatch(/go 0\/3.*ruby 1\/0/);
|
||||
});
|
||||
|
||||
it('uses plural call sites for multiple guesses', () => {
|
||||
expect(
|
||||
formatNameFallbackSummary(summarizeNameFallback([guessed('go'), guessed('go')])),
|
||||
).toContain('2 call sites (');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -509,6 +509,17 @@ describe('Rust: isGlobalNameFallbackPlausible', () => {
|
|||
});
|
||||
|
||||
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({
|
||||
|
|
|
|||
|
|
@ -20,17 +20,46 @@ describe('collectResolvedCalleeNames', () => {
|
|||
fn('b', 'b', 'src/b.go');
|
||||
fn('c', 'c', 'src/c.ts');
|
||||
g.addNode({ id: 'file', label: 'File' as NodeLabel, properties: { filePath: 'src/a.go' } });
|
||||
g.addRelationship({ id: 'r1', sourceId: 'a', targetId: 'b', type: 'CALLS', confidence: 0.85 });
|
||||
g.addRelationship({ id: 'r2', sourceId: 'a', targetId: 'c', type: 'CALLS', confidence: 0.5 });
|
||||
g.addRelationship({ id: 'r3', sourceId: 'c', targetId: 'b', type: 'CALLS', confidence: 0.85 });
|
||||
g.addRelationship({
|
||||
id: 'r1',
|
||||
sourceId: 'a',
|
||||
targetId: 'b',
|
||||
type: 'CALLS',
|
||||
confidence: 0.85,
|
||||
reason: '',
|
||||
});
|
||||
g.addRelationship({
|
||||
id: 'r2',
|
||||
sourceId: 'a',
|
||||
targetId: 'c',
|
||||
type: 'CALLS',
|
||||
confidence: 0.5,
|
||||
reason: '',
|
||||
});
|
||||
g.addRelationship({
|
||||
id: 'r3',
|
||||
sourceId: 'c',
|
||||
targetId: 'b',
|
||||
type: 'CALLS',
|
||||
confidence: 0.85,
|
||||
reason: '',
|
||||
});
|
||||
g.addRelationship({
|
||||
id: 'r4',
|
||||
sourceId: 'file',
|
||||
targetId: 'a',
|
||||
type: 'DEFINES',
|
||||
confidence: 1,
|
||||
reason: '',
|
||||
});
|
||||
g.addRelationship({
|
||||
id: 'r5',
|
||||
sourceId: 'a',
|
||||
targetId: 'file',
|
||||
type: 'CALLS',
|
||||
confidence: 1,
|
||||
reason: '',
|
||||
});
|
||||
g.addRelationship({ id: 'r5', sourceId: 'a', targetId: 'file', type: 'CALLS', confidence: 1 });
|
||||
|
||||
const index = collectResolvedCalleeNames(g, g);
|
||||
expect([...index.keys()].sort()).toEqual(['a', 'c']);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue