mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-25 05:53:44 +00:00
Address PR review feedback (#3632)
- Destructuring defaults (`{ fetch = globalThis.fetch } = {}`, renamed,
array and local forms) are alias evidence for their leaf binding and
no longer count a second initialization, which also invalidated
bindings like `[send = other] = values`.
- `export default (request)` records the default export: the value is
unwrapped before the identifier check.
- The HTTP fallback records and the collapsed-environment test row now
say what they stand for.
Regression rows cover each form in JS, TS and TSX and fail on the
previous source; two fixture consumers cover imported wrappers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0ce9235a64
commit
c263c4f720
10 changed files with 86 additions and 4 deletions
|
|
@ -106,6 +106,7 @@ const INSPECTED_TYPES: ReadonlySet<string> = new Set([
|
|||
'required_parameter',
|
||||
'optional_parameter',
|
||||
'assignment_pattern',
|
||||
'object_assignment_pattern',
|
||||
'catch_clause',
|
||||
'assignment_expression',
|
||||
'augmented_assignment_expression',
|
||||
|
|
@ -305,6 +306,17 @@ export function createFetchWrapperContext(
|
|||
if (pattern?.type === 'identifier' && namesFetch(value)) aliases.add(id);
|
||||
}
|
||||
};
|
||||
// A destructuring default supplies a value for its leaf binding only; the
|
||||
// enclosing declaration or parameter has already counted the initialization.
|
||||
const destructuringDefault = (target: Node | null, value: Node | null) => {
|
||||
if (
|
||||
(target?.type === 'identifier' || target?.type === 'shorthand_property_identifier_pattern') &&
|
||||
namesFetch(value)
|
||||
) {
|
||||
const id = stableBinding(target, false);
|
||||
if (id) aliases.add(id);
|
||||
}
|
||||
};
|
||||
const exported = (local: Node, name: string) => {
|
||||
const id = stableBinding(local, false);
|
||||
if (!id) return;
|
||||
|
|
@ -503,8 +515,10 @@ export function createFetchWrapperContext(
|
|||
initialize(name, node.childForFieldName('value'));
|
||||
} else if (type === 'required_parameter' || type === 'optional_parameter')
|
||||
initialize(parameterPattern(node), node.childForFieldName('value'), true);
|
||||
else if (type === 'assignment_pattern')
|
||||
else if (type === 'assignment_pattern' && parent.type === 'formal_parameters')
|
||||
initialize(node.childForFieldName('left'), node.childForFieldName('right'), true);
|
||||
else if (type === 'assignment_pattern' || type === 'object_assignment_pattern')
|
||||
destructuringDefault(node.childForFieldName('left'), node.childForFieldName('right'));
|
||||
else if (type === 'catch_clause') markPattern(node.childForFieldName('parameter'));
|
||||
else if (type === 'assignment_expression') {
|
||||
invalidate(node.childForFieldName('left'));
|
||||
|
|
@ -529,7 +543,11 @@ export function createFetchWrapperContext(
|
|||
for (const local of patternBindingNames(declarator.childForFieldName('name')))
|
||||
exported(local, isDefault ? 'default' : local.text);
|
||||
}
|
||||
} else if (isDefault && value?.type === 'identifier') exported(value, 'default');
|
||||
} else if (isDefault) {
|
||||
// `export default (request)` exports the binding the parentheses wrap.
|
||||
const target = unwrap(value);
|
||||
if (target?.type === 'identifier') exported(target, 'default');
|
||||
}
|
||||
} else if (
|
||||
type === 'export_specifier' &&
|
||||
// `export { x } from './y'` declares no local export.
|
||||
|
|
|
|||
|
|
@ -3272,8 +3272,9 @@ const processFileGroup = (
|
|||
}
|
||||
if (parsedFile && !httpFailed) for (const def of wrapperDefs) result.fetchWrapperDefs.push(def);
|
||||
else
|
||||
// Without this file's facts its functions still name exports, so a
|
||||
// name-only match elsewhere declines instead of turning unique.
|
||||
// These records stand for unknown facts. With `seed` false and nothing
|
||||
// forwarded they never prove a wrapper; they only make a name-only
|
||||
// match, a barrel or an import that may reach this file decline.
|
||||
for (const { name, node } of wrapperFunctions)
|
||||
result.fetchWrapperDefs.push({
|
||||
filePath: file.path,
|
||||
|
|
|
|||
6
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/default-paren.ts
vendored
Normal file
6
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/default-paren.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
function parenRequest(url: string) {
|
||||
return fetch(url);
|
||||
}
|
||||
// The parentheses are the case under test.
|
||||
// prettier-ignore
|
||||
export default (parenRequest);
|
||||
3
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/destructured.ts
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/lib/destructured.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function destructuredRequest(url: string, { fetch = globalThis.fetch } = {}) {
|
||||
return fetch(url);
|
||||
}
|
||||
|
|
@ -26,3 +26,5 @@ app.get('/api/diamond', (_req, res) => res.json({}));
|
|||
app.get('/api/diamond-distinct', (_req, res) => res.json({}));
|
||||
app.get('/api/cjs-member', (_req, res) => res.json({}));
|
||||
app.get('/api/cjs-arrow', (_req, res) => res.json({}));
|
||||
app.get('/api/destructured', (_req, res) => res.json({}));
|
||||
app.get('/api/default-paren', (_req, res) => res.json({}));
|
||||
|
|
|
|||
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/default-paren.ts
vendored
Normal file
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/default-paren.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import parenRequest from '../lib/default-paren';
|
||||
export const loadDefaultParen = () => parenRequest('/api/default-paren');
|
||||
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/destructured.ts
vendored
Normal file
2
gitnexus/test/fixtures/lang-resolution/fetch-wrapper-target-identity/web/destructured.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import { destructuredRequest } from '../lib/destructured';
|
||||
export const loadDestructured = () => destructuredRequest('/api/destructured');
|
||||
|
|
@ -90,6 +90,8 @@ describe('Fetch wrapper lexical and project target identity', () => {
|
|||
'web/cjs-member.js -> /api/cjs-arrow',
|
||||
'web/cjs-member.js -> /api/cjs-member',
|
||||
'web/cjs.js -> /api/cjs',
|
||||
'web/default-paren.ts -> /api/default-paren',
|
||||
'web/destructured.ts -> /api/destructured',
|
||||
'web/diamond.ts -> /api/diamond',
|
||||
'web/hoc.ts -> /api/hoc',
|
||||
'web/multi.ts -> /api/multi',
|
||||
|
|
|
|||
|
|
@ -39,6 +39,37 @@ for (const [language, grammar] of [
|
|||
true,
|
||||
true,
|
||||
],
|
||||
// A destructuring default is alias evidence for its leaf binding, counted once.
|
||||
[
|
||||
'destructured platform default',
|
||||
'function load(url, { fetch = globalThis.fetch } = {}) { fetch(url); }',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
[
|
||||
'renamed destructured platform default',
|
||||
'function load(url, { fetch: send = globalThis.fetch } = {}) { send(url); }',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
[
|
||||
'array destructured platform default',
|
||||
'function load(url, [send = globalThis.fetch] = []) { send(url); }',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
[
|
||||
'destructured local default',
|
||||
'function load(url, options) { const { fetch = globalThis.fetch } = options; fetch(url); }',
|
||||
true,
|
||||
true,
|
||||
],
|
||||
[
|
||||
'destructured unknown default',
|
||||
'function load(url, { fetch = other } = {}) { fetch(url); }',
|
||||
false,
|
||||
false,
|
||||
],
|
||||
['self default', 'function load(url, fetch = fetch) { fetch(url); }', false, false],
|
||||
[
|
||||
'shadowed default root',
|
||||
|
|
@ -494,6 +525,18 @@ for (const [language, grammar] of [
|
|||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['a plain', 'function request(url) { return fetch(url); }\nexport default request;'],
|
||||
[
|
||||
'a parenthesized',
|
||||
'function request(url) { return fetch(url); }\nexport default (request);',
|
||||
],
|
||||
])('records %s default export of a declaration', (_shape, source) => {
|
||||
const { tree, context } = analyze(source);
|
||||
const request = tree.rootNode.descendantsOfType('function_declaration')[0];
|
||||
expect(context.facts(request)).toMatchObject({ exportedNames: ['default'], seed: true });
|
||||
});
|
||||
|
||||
it('recognizes an anonymous default export', () => {
|
||||
const { tree, context } = analyze('export default function (url) { return fetch(url); }');
|
||||
expect(context.facts(tree.rootNode.firstNamedChild!)).toMatchObject({
|
||||
|
|
|
|||
|
|
@ -285,6 +285,9 @@ function handler() {} function other() {}`);
|
|||
'function inner(app) { app = new Map(); }',
|
||||
['GET /forwarded'],
|
||||
],
|
||||
// A parameter expression gives the body its own variable environment, which
|
||||
// the scope model collapses, so inference declines a parameter that a body
|
||||
// `var` redeclares (the documented boundary); runtime would keep its value.
|
||||
['nested destructuring default', 'app, { value = 1 }', 'var app;', []],
|
||||
['computed pattern key', 'app, { [key]: value }', 'var app;', []],
|
||||
['rest parameter alone', 'app, ...rest', 'var app;', ['GET /forwarded']],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue