mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-25 05:53:44 +00:00
fix(routes): treat export aliases of one wrapper as one star-export target
Star-export candidates were deduplicated by the resolved export name, so
`export { request, request as send }` reached through two barrels counted
as two targets and blocked the import, dropping the consumer's FETCHES
edge. Candidates are now keyed by the declaration each export name names,
matching ECMAScript ResolveExport's binding comparison; distinct
declarations exported under the same name stay ambiguous.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e85795b78e
commit
4d62bd0451
13 changed files with 67 additions and 4 deletions
|
|
@ -141,10 +141,17 @@ export function resolveFetchWrapperTargets(
|
|||
}
|
||||
return targets;
|
||||
};
|
||||
const declared = new Set<string>();
|
||||
// Each export name maps to the declaration it names; a name two declarations
|
||||
// claim keeps a name-only identity, so it never merges with another export.
|
||||
const declared = new Map<string, string>();
|
||||
for (const definition of definitions)
|
||||
if (definition.moduleScope)
|
||||
for (const name of definition.exportedNames) declared.add(`${definition.filePath}\0${name}`);
|
||||
for (const name of definition.exportedNames) {
|
||||
const key = `${definition.filePath}\0${name}`;
|
||||
const identity = `${definition.filePath}\0${definition.definitionId}`;
|
||||
const existing = declared.get(key);
|
||||
declared.set(key, existing === undefined || existing === identity ? identity : `${key}\0`);
|
||||
}
|
||||
const named = new Map<string, FetchWrapperReexport[]>();
|
||||
const stars = new Map<string, FetchWrapperReexport[]>();
|
||||
for (const reexport of reexports) {
|
||||
|
|
@ -181,8 +188,16 @@ export function resolveFetchWrapperTargets(
|
|||
named.get(key) ?? (name === 'default' ? undefined : stars.get(filePath)) ?? [];
|
||||
return { frame: { filePath, name, candidates, cursor: 0, found: new Map() } };
|
||||
};
|
||||
// Star exports conflict only when they reach different bindings, so two
|
||||
// export names of one declaration are one candidate.
|
||||
const record = (frame: ExportFrame, target: CalleeTarget | undefined) => {
|
||||
if (target) frame.found.set(JSON.stringify(target), target);
|
||||
if (!target) return;
|
||||
const identity =
|
||||
target.kind === 'resolved-import'
|
||||
? declared.get(`${target.filePath}\0${target.importedName}`)
|
||||
: undefined;
|
||||
const key = identity ?? JSON.stringify(target);
|
||||
if (!frame.found.has(key)) frame.found.set(key, target);
|
||||
};
|
||||
const first = open(startFile, startName);
|
||||
if ('value' in first) return first.value;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
export function distinctRequest(url: string) {
|
||||
return fetch(url);
|
||||
}
|
||||
export function distinctSend(url: string) {
|
||||
return fetch(url);
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
export * from './left';
|
||||
export * from './right';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { distinctRequest } from './http';
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { distinctSend as distinctRequest } from './http';
|
||||
4
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/http.ts
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/http.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
function diamondRequest(url: string) {
|
||||
return fetch(url);
|
||||
}
|
||||
export { diamondRequest, diamondRequest as diamondSend };
|
||||
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/index.ts
vendored
Normal file
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/index.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from './left';
|
||||
export * from './right';
|
||||
1
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/left.ts
vendored
Normal file
1
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/left.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { diamondRequest } from './http';
|
||||
1
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/right.ts
vendored
Normal file
1
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/diamond/right.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { diamondSend as diamondRequest } from './http';
|
||||
|
|
@ -22,3 +22,5 @@ app.get('/api/hoc', (_req, res) => res.json({}));
|
|||
app.get('/api/cjs', (_req, res) => res.json({}));
|
||||
app.get('/api/key', (_req, res) => res.json({}));
|
||||
app.get('/api/node', (_req, res) => res.json({}));
|
||||
app.get('/api/diamond', (_req, res) => res.json({}));
|
||||
app.get('/api/diamond-distinct', (_req, res) => res.json({}));
|
||||
|
|
|
|||
4
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/diamond.ts
vendored
Normal file
4
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/diamond.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { diamondRequest } from '../lib/diamond';
|
||||
import { distinctRequest } from '../lib/diamond-distinct';
|
||||
export const loadDiamond = () => diamondRequest('/api/diamond');
|
||||
export const loadDistinct = () => distinctRequest('/api/diamond-distinct');
|
||||
|
|
@ -70,7 +70,7 @@ describe('Base-URL wrapper consumer FETCHES edge extraction', () => {
|
|||
});
|
||||
|
||||
describe('Fetch wrapper lexical and project target identity', () => {
|
||||
it('resolves relative, alias, workspace, barrel, overload, multi-declarator, HOC, CommonJS and package-fetch targets while rejecting shadowed and ambiguous names', async () => {
|
||||
it('resolves relative, alias, workspace, barrel, export-alias diamond, overload, multi-declarator, HOC, CommonJS and package-fetch targets while rejecting shadowed and ambiguous names', async () => {
|
||||
const result = await runPipelineFromRepo(
|
||||
path.join(FIXTURES, 'fetch-wrapper-target-identity'),
|
||||
() => {},
|
||||
|
|
@ -88,6 +88,7 @@ describe('Fetch wrapper lexical and project target identity', () => {
|
|||
'web/barrel.ts -> /api/barrel-imported',
|
||||
'web/barrel.ts -> /api/barrel-star',
|
||||
'web/cjs.js -> /api/cjs',
|
||||
'web/diamond.ts -> /api/diamond',
|
||||
'web/hoc.ts -> /api/hoc',
|
||||
'web/multi.ts -> /api/multi',
|
||||
'web/nested.ts -> /api/nested',
|
||||
|
|
|
|||
|
|
@ -388,6 +388,29 @@ describe('fetch wrapper barrel resolution', () => {
|
|||
expect(resolveIn(reexports)).toEqual(expected);
|
||||
});
|
||||
|
||||
// `export * from './left'; export * from './right'`, where left re-exports
|
||||
// `request` and right re-exports `send as request` from the same module.
|
||||
const diamond = [
|
||||
forward('index.ts', '*', './left', '*'),
|
||||
forward('index.ts', '*', './right', '*'),
|
||||
forward('left.ts', 'request', './http'),
|
||||
forward('right.ts', 'request', './http', 'send'),
|
||||
];
|
||||
it.each([
|
||||
[
|
||||
'two export names of one declaration',
|
||||
[{ ...declares('http.ts', 'request'), exportedNames: ['request', 'send'] }],
|
||||
at('http.ts'),
|
||||
],
|
||||
[
|
||||
'two declarations',
|
||||
[declares('http.ts', 'request'), declares('http.ts', 'send')],
|
||||
{ kind: 'blocked' },
|
||||
],
|
||||
])('resolves a star-export diamond over %s by binding identity', (_shape, defs, expected) => {
|
||||
expect(resolveIn(diamond, 'request', defs)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('never forwards default through a star export', () => {
|
||||
expect(
|
||||
resolveIn([forward('index.ts', '*', './request', '*')], 'default', [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue