@component-compass/reference-graph 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/argument-map.d.ts +7 -0
- package/dist/engine/argument-map.js +7 -0
- package/dist/engine/argument-map.js.map +1 -1
- package/dist/engine/component-shape.js +2 -2
- package/dist/engine/component-shape.js.map +1 -1
- package/dist/engine/denotation.d.ts +83 -0
- package/dist/engine/denotation.js +40 -0
- package/dist/engine/denotation.js.map +1 -0
- package/dist/engine/host-element.d.ts +7 -3
- package/dist/engine/host-element.js +7 -3
- package/dist/engine/host-element.js.map +1 -1
- package/dist/engine/index.d.ts +5 -23
- package/dist/engine/index.js +287 -427
- package/dist/engine/index.js.map +1 -1
- package/dist/engine/library-stubs.d.ts +36 -0
- package/dist/engine/library-stubs.js +26 -29
- package/dist/engine/library-stubs.js.map +1 -1
- package/dist/engine/registry.d.ts +13 -2
- package/dist/engine/registry.js +68 -32
- package/dist/engine/registry.js.map +1 -1
- package/dist/engine/resolve-type.d.ts +1 -1
- package/dist/engine/resolve-type.js +3 -2
- package/dist/engine/resolve-type.js.map +1 -1
- package/dist/engine/wrapper-folding.d.ts +3 -13
- package/dist/engine/wrapper-folding.js +283 -54
- package/dist/engine/wrapper-folding.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/engine/index.js
CHANGED
|
@@ -10,8 +10,9 @@ import { resolveOwnerChain } from "./owner-resolution.js";
|
|
|
10
10
|
import { createCycleGuard } from "./cycle-detection.js";
|
|
11
11
|
import { unparsedMemberDefinition, effectiveExportName, residualMemberChain, compoundExportName } from "./member-identity.js";
|
|
12
12
|
import { buildComponentRegistry, excludeFoldedHolders, excludeHostElementNames, entryKey } from "./registry.js";
|
|
13
|
-
import { hasOnlyHostElementNames, isHostElementName } from "./host-element.js";
|
|
13
|
+
import { hasOnlyHostElementNames, isHostElementName, HOOK_NAME } from "./host-element.js";
|
|
14
14
|
import { firstDynamicImport } from "./component-shape.js";
|
|
15
|
+
import { creditedTerminals, renderOutcome } from "./denotation.js";
|
|
15
16
|
/**
|
|
16
17
|
* JSX treats a bare, lowercase-initial element name as a host element
|
|
17
18
|
* (`div`, `svg`, `web-button`) — it never names a binding, so it is not an
|
|
@@ -118,238 +119,176 @@ export function resolve(graph, opts) {
|
|
|
118
119
|
? { fileUsageIdx, kind, parentUsageIdx }
|
|
119
120
|
: { fileUsageIdx, kind };
|
|
120
121
|
}
|
|
122
|
+
return occurrences.length - lengthBefore;
|
|
121
123
|
};
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
stampComposition(jsxLengthBefore, "jsx", usageIdx, jsxDepth, jsxParentUsageIdx);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
const packageName = packageNameFromSpecifier(matchedImport.specifier);
|
|
163
|
-
// Resolve namespace-import members to their export name once, up front —
|
|
164
|
-
// every export-name / re-export lookup below keys on this, not the raw
|
|
165
|
-
// `imported`. `via.import` keeps the raw `imported` (provenance).
|
|
166
|
-
const effectiveImported = effectiveExportName(matchedImport.imported, usage.ref.memberChain);
|
|
167
|
-
// Lookups below key on `effectiveImported` (the ROOT); the
|
|
168
|
-
// identity stamped on a fallback is the root plus the residual chain.
|
|
169
|
-
// A member the walker resolves to its own declaration never reaches a
|
|
170
|
-
// fallback, so this only names members with no declaration of their own.
|
|
171
|
-
const residual = residualMemberChain(matchedImport.imported, usage.ref.memberChain);
|
|
172
|
-
if (!packageName) {
|
|
173
|
-
// Relative / absolute specifier — always a local file reference.
|
|
174
|
-
// Resolve the target path; normalize absolute → graph-relative if possible.
|
|
175
|
-
const resolvedAbsTarget = graph.moduleResolver(filePath, matchedImport.specifier);
|
|
176
|
-
if (!resolvedAbsTarget) {
|
|
177
|
-
// A JSX usage whose import failed to resolve is a dropped local
|
|
178
|
-
// reference — surface it instead of vanishing.
|
|
179
|
-
opts?.collector?.emit({
|
|
180
|
-
code: "unresolved-import",
|
|
181
|
-
severity: "warning",
|
|
182
|
-
filePath,
|
|
183
|
-
line: usage.loc.line,
|
|
184
|
-
column: usage.loc.column,
|
|
185
|
-
specifier: matchedImport.specifier,
|
|
186
|
-
});
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const resolvedTarget = graph.resolveToGraphKey
|
|
190
|
-
? (graph.resolveToGraphKey(resolvedAbsTarget) ?? resolvedAbsTarget)
|
|
191
|
-
: resolvedAbsTarget;
|
|
192
|
-
// Resolve the type to confirm it's a JSX component. Cross-file resolution
|
|
193
|
-
// works when resolveToGraphKey is present (graph keys are repo-relative).
|
|
194
|
-
const refType = resolveReference(graph, fileGraph, usage.ref);
|
|
195
|
-
const folded = walkWithFolding(graph, fileGraph, refType, argMap);
|
|
196
|
-
const jsxTerminals = folded.filter((t) => t.type.kind === "JSX");
|
|
197
|
-
if (jsxTerminals.length === 0) {
|
|
198
|
-
emitLazyImportUnsupported(opts, filePath, usage.loc, refType);
|
|
199
|
-
continue;
|
|
200
|
-
}
|
|
201
|
-
// Determine the canonical source file + local export name. When the
|
|
202
|
-
// target is a re-export barrel (`export { X } from './source'`),
|
|
203
|
-
// follow the chain to the actual definition file so JSX edges
|
|
204
|
-
// attribute to the source rather than the barrel. Without
|
|
205
|
-
// this, two consumers importing the same component through different
|
|
206
|
-
// re-export paths fragment across phantom identities.
|
|
207
|
-
const chained = followReExportChain(graph, resolvedTarget, effectiveImported);
|
|
208
|
-
const canonicalFile = chained?.file ?? resolvedTarget;
|
|
209
|
-
let exportName = chained?.localExport ?? effectiveImported;
|
|
210
|
-
// When the re-export chain didn't follow (no chain present), still
|
|
211
|
-
// look up the local export name at the resolved target to match the
|
|
212
|
-
// local-index seed's hash convention.
|
|
213
|
-
if (!chained) {
|
|
214
|
-
const targetGraph = graph.files.get(resolvedTarget);
|
|
215
|
-
if (targetGraph && effectiveImported === "default") {
|
|
216
|
-
const defaultExp = targetGraph.exports.find((e) => e.kind === "default");
|
|
217
|
-
if (defaultExp && "local" in defaultExp) {
|
|
218
|
-
exportName = defaultExp.local;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
else if (targetGraph) {
|
|
222
|
-
const namedExp = targetGraph.exports.find((e) => e.kind === "named" && "exportedAs" in e && e.exportedAs === effectiveImported && "local" in e);
|
|
223
|
-
if (namedExp && "local" in namedExp) {
|
|
224
|
-
exportName = namedExp.local;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
const outerVia = viaOverride ?? {
|
|
229
|
-
kind: "direct-import",
|
|
230
|
-
specifier: matchedImport.specifier,
|
|
231
|
-
import: matchedImport.imported,
|
|
232
|
-
};
|
|
233
|
-
const fallbackComponentId = {
|
|
234
|
-
kind: componentKindFor(fileGraph.dialect),
|
|
235
|
-
export: compoundExportName(exportName, residual),
|
|
236
|
-
source: { type: "local", filePath: canonicalFile },
|
|
237
|
-
};
|
|
238
|
-
if (viaOverride !== undefined) {
|
|
239
|
-
// Prop-forward: parser has pre-decided the owner — single-occurrence path.
|
|
240
|
-
const rawOwnerComponentId = ownerIdFromRef(graph, ownerRef, fileGraph.filePath);
|
|
241
|
-
for (const ft of jsxTerminals) {
|
|
242
|
-
const rawComponentId = componentIdFromIdentity(ft.identity, fallbackComponentId, fileGraph.dialect, {
|
|
243
|
-
filePath,
|
|
244
|
-
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
245
|
-
});
|
|
246
|
-
const viaChain = [viaOverride];
|
|
247
|
-
const provisionalId = serialiseComponentId(rawComponentId);
|
|
248
|
-
const occurrenceId = computeOccurrenceId(provisionalId, filePath, usage.loc.line, usage.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
249
|
-
occurrences.push({
|
|
250
|
-
rawComponentId,
|
|
251
|
-
filePath,
|
|
252
|
-
line: usage.loc.line,
|
|
253
|
-
column: usage.loc.column,
|
|
254
|
-
via: viaChain[0] ?? outerVia,
|
|
255
|
-
viaChain,
|
|
256
|
-
props: projectPropUsages(usage.props),
|
|
257
|
-
events: usage.events ?? [],
|
|
258
|
-
occurrenceId,
|
|
259
|
-
depth: 0,
|
|
260
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
// Helper-call fanout: resolve owner chain and emit per (terminal × resolution).
|
|
266
|
-
const ownerResolutions = resolveOwnerChain(ownerRef, graph, helperIndex, createCycleGuard());
|
|
267
|
-
for (const ft of jsxTerminals) {
|
|
268
|
-
const rawComponentId = componentIdFromIdentity(ft.identity, fallbackComponentId, fileGraph.dialect, {
|
|
269
|
-
filePath,
|
|
270
|
-
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
271
|
-
});
|
|
272
|
-
for (const res of ownerResolutions) {
|
|
273
|
-
const rawOwnerComponentId = ownerIdFromResolution(graph, res.ownerDecl);
|
|
274
|
-
const viaChain = [...res.viaPrefix, outerVia, ...ft.viaTrail];
|
|
275
|
-
const provisionalId = serialiseComponentId(rawComponentId);
|
|
276
|
-
const occurrenceId = computeOccurrenceId(provisionalId, filePath, usage.loc.line, usage.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
277
|
-
occurrences.push({
|
|
278
|
-
rawComponentId,
|
|
279
|
-
filePath,
|
|
280
|
-
line: usage.loc.line,
|
|
281
|
-
column: usage.loc.column,
|
|
282
|
-
via: viaChain[0] ?? outerVia,
|
|
283
|
-
viaChain,
|
|
284
|
-
props: projectPropUsages(usage.props),
|
|
285
|
-
events: usage.events ?? [],
|
|
286
|
-
occurrenceId,
|
|
287
|
-
depth: 0,
|
|
288
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
stampComposition(jsxLengthBefore, "jsx", usageIdx, jsxDepth, jsxParentUsageIdx);
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
// Check whether the import resolves to a local file that IS in the graph.
|
|
297
|
-
// If it does, resolve properly to avoid false positives (e.g. importing a
|
|
298
|
-
// utility function that happens to be named with a capital letter from a
|
|
299
|
-
// workspace sibling). For imports whose target is outside the graph
|
|
300
|
-
// (external packages in node_modules), trust the JSX call site directly.
|
|
124
|
+
const pushJsxUsageOccurrences = (filePath, fileGraph, usage, ownership) => {
|
|
125
|
+
// Resolve owner symbol ref for this usage (for composition attribution).
|
|
126
|
+
const ownerRef = ownership?.ownerSymbolRef ?? null;
|
|
127
|
+
const viaOverride = ownership?.viaOverride;
|
|
128
|
+
// Find the import record that introduced the root symbol at module scope.
|
|
129
|
+
// We check usage.ref.symbol (the root binding name), not member-chain
|
|
130
|
+
// segments, because ImportRecord.local binds the top-level name.
|
|
131
|
+
const matchedImport = fileGraph.importsByLocal.get(usage.ref.symbol);
|
|
132
|
+
if (!matchedImport) {
|
|
133
|
+
// Symbol is locally declared (no import) — emit local identity.
|
|
134
|
+
const decl = findLocalDeclaration(fileGraph, usage.ref);
|
|
135
|
+
if (!decl)
|
|
136
|
+
return { kind: "unbound" };
|
|
137
|
+
return {
|
|
138
|
+
kind: "evaluated",
|
|
139
|
+
...pushLocalOccurrence(occurrences, filePath, usage, decl, graph, fileGraph, argMap, ownerRef, helperIndex, opts, viaOverride),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const packageName = packageNameFromSpecifier(matchedImport.specifier);
|
|
143
|
+
// Resolve namespace-import members to their export name once, up front —
|
|
144
|
+
// every export-name / re-export lookup below keys on this, not the raw
|
|
145
|
+
// `imported`. `via.import` keeps the raw `imported` (provenance).
|
|
146
|
+
const effectiveImported = effectiveExportName(matchedImport.imported, usage.ref.memberChain);
|
|
147
|
+
// Lookups below key on `effectiveImported` (the ROOT); the
|
|
148
|
+
// identity stamped on a fallback is the root plus the residual chain.
|
|
149
|
+
// A member the walker resolves to its own declaration never reaches a
|
|
150
|
+
// fallback, so this only names members with no declaration of their own.
|
|
151
|
+
const residual = residualMemberChain(matchedImport.imported, usage.ref.memberChain);
|
|
152
|
+
const holderDefinition = (file, exportName) => {
|
|
153
|
+
if (residual.length === 0)
|
|
154
|
+
return undefined;
|
|
155
|
+
const loc = graph.files.get(file)?.declarations.get(`${MODULE_SCOPE}::${exportName}`)?.loc;
|
|
156
|
+
return loc === undefined ? undefined : { line: loc.line, column: loc.column };
|
|
157
|
+
};
|
|
158
|
+
if (!packageName) {
|
|
159
|
+
// Relative / absolute specifier — always a local file reference.
|
|
160
|
+
// Resolve the target path; normalize absolute → graph-relative if possible.
|
|
301
161
|
const resolvedAbsTarget = graph.moduleResolver(filePath, matchedImport.specifier);
|
|
302
|
-
|
|
303
|
-
|
|
162
|
+
if (!resolvedAbsTarget)
|
|
163
|
+
return { kind: "import-failed", specifier: matchedImport.specifier };
|
|
164
|
+
const resolvedTarget = graph.resolveToGraphKey
|
|
304
165
|
? (graph.resolveToGraphKey(resolvedAbsTarget) ?? resolvedAbsTarget)
|
|
305
166
|
: resolvedAbsTarget;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
167
|
+
// Resolve the type to confirm it's a JSX component. Cross-file resolution
|
|
168
|
+
// works when resolveToGraphKey is present (graph keys are repo-relative).
|
|
169
|
+
const refType = resolveReference(graph, fileGraph, usage.ref);
|
|
170
|
+
const evaluation = walkWithFolding(graph, fileGraph, refType, argMap);
|
|
171
|
+
const jsxTerminals = creditedTerminals(evaluation);
|
|
172
|
+
if (jsxTerminals.length === 0)
|
|
173
|
+
return { kind: "evaluated", evaluation, value: refType };
|
|
174
|
+
// Determine the canonical source file + local export name. When the
|
|
175
|
+
// target is a re-export barrel (`export { X } from './source'`),
|
|
176
|
+
// follow the chain to the actual definition file so JSX edges
|
|
177
|
+
// attribute to the source rather than the barrel. Without
|
|
178
|
+
// this, two consumers importing the same component through different
|
|
179
|
+
// re-export paths fragment across phantom identities.
|
|
180
|
+
const chained = followReExportChain(graph, resolvedTarget, effectiveImported);
|
|
181
|
+
const canonicalFile = chained?.file ?? resolvedTarget;
|
|
182
|
+
let exportName = chained?.localExport ?? effectiveImported;
|
|
183
|
+
// When the re-export chain didn't follow (no chain present), still
|
|
184
|
+
// look up the local export name at the resolved target to match the
|
|
185
|
+
// local-index seed's hash convention.
|
|
186
|
+
if (!chained) {
|
|
187
|
+
const targetGraph = graph.files.get(resolvedTarget);
|
|
188
|
+
if (targetGraph && effectiveImported === "default") {
|
|
189
|
+
const defaultExp = targetGraph.exports.find((e) => e.kind === "default");
|
|
190
|
+
if (defaultExp && "local" in defaultExp) {
|
|
191
|
+
exportName = defaultExp.local;
|
|
192
|
+
}
|
|
319
193
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
pushOccurrence(occurrences, filePath, usage, matchedImport, compoundExportName(effectiveImported, residual), packageName, graph, fileGraph, ft, ownerRef, helperIndex, opts, localFallback, viaOverride);
|
|
194
|
+
else if (targetGraph) {
|
|
195
|
+
const namedExp = targetGraph.exports.find((e) => e.kind === "named" && "exportedAs" in e && e.exportedAs === effectiveImported && "local" in e);
|
|
196
|
+
if (namedExp && "local" in namedExp) {
|
|
197
|
+
exportName = namedExp.local;
|
|
198
|
+
}
|
|
326
199
|
}
|
|
327
200
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
else {
|
|
349
|
-
pushOccurrence(occurrences, filePath, usage, matchedImport, compoundExportName(effectiveImported, residual), packageName, graph, fileGraph, sentinelFt, ownerRef, helperIndex, opts, undefined, viaOverride);
|
|
350
|
-
}
|
|
201
|
+
const outerVia = viaOverride ?? {
|
|
202
|
+
kind: "direct-import",
|
|
203
|
+
specifier: matchedImport.specifier,
|
|
204
|
+
import: matchedImport.imported,
|
|
205
|
+
};
|
|
206
|
+
const fallbackComponentId = {
|
|
207
|
+
kind: componentKindFor(fileGraph.dialect),
|
|
208
|
+
export: compoundExportName(exportName, residual),
|
|
209
|
+
source: { type: "local", filePath: canonicalFile },
|
|
210
|
+
};
|
|
211
|
+
const site = usageSite(filePath, usage);
|
|
212
|
+
const owners = usageOwners(graph, helperIndex, ownerRef, viaOverride, fileGraph.filePath);
|
|
213
|
+
const definition = holderDefinition(canonicalFile, exportName);
|
|
214
|
+
for (const ft of jsxTerminals) {
|
|
215
|
+
const rawComponentId = componentIdFromIdentity(ft.identity, fallbackComponentId, fileGraph.dialect, {
|
|
216
|
+
filePath,
|
|
217
|
+
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
218
|
+
});
|
|
219
|
+
emitOccurrence(occurrences, site, rawComponentId, owners, viaOverride !== undefined ? [viaOverride] : [outerVia, ...ft.viaTrail], ft.identity === null ? definition : undefined);
|
|
351
220
|
}
|
|
352
|
-
|
|
221
|
+
return { kind: "evaluated", evaluation, value: refType };
|
|
222
|
+
}
|
|
223
|
+
// Check whether the import resolves to a local file that IS in the graph.
|
|
224
|
+
// If it does, resolve properly to avoid false positives (e.g. importing a
|
|
225
|
+
// utility function that happens to be named with a capital letter from a
|
|
226
|
+
// workspace sibling). For imports whose target is outside the graph
|
|
227
|
+
// (external packages in node_modules), trust the JSX call site directly.
|
|
228
|
+
const resolvedAbsTarget = graph.moduleResolver(filePath, matchedImport.specifier);
|
|
229
|
+
// Normalize absolute path → repo-relative graph key when possible.
|
|
230
|
+
const resolvedNormTarget = resolvedAbsTarget !== null && graph.resolveToGraphKey
|
|
231
|
+
? (graph.resolveToGraphKey(resolvedAbsTarget) ?? resolvedAbsTarget)
|
|
232
|
+
: resolvedAbsTarget;
|
|
233
|
+
const targetInGraph = resolvedNormTarget !== null && graph.files.has(resolvedNormTarget);
|
|
234
|
+
if (targetInGraph) {
|
|
235
|
+
// Target file is in graph — workspace sibling. Resolve to check
|
|
236
|
+
// JSX type, fan out per terminal. The fallback identity is local so
|
|
237
|
+
// null-identity terminals still attribute to the sibling file rather
|
|
238
|
+
// than a phantom external stub. Terminal identities from deriveIdentity
|
|
239
|
+
// are already local when applicable (workspaceSiblingIdentity fires).
|
|
240
|
+
const refType = resolveReference(graph, fileGraph, usage.ref);
|
|
241
|
+
const evaluation = walkWithFolding(graph, fileGraph, refType, argMap);
|
|
242
|
+
const jsxTerminals = creditedTerminals(evaluation);
|
|
243
|
+
if (jsxTerminals.length === 0)
|
|
244
|
+
return { kind: "evaluated", evaluation, value: refType };
|
|
245
|
+
const siblingFallback = workspaceSiblingFallback(graph, resolvedNormTarget, matchedImport, effectiveImported, fileGraph.dialect);
|
|
246
|
+
const localFallback = siblingFallback !== undefined && siblingFallback.kind !== "custom-element"
|
|
247
|
+
? { ...siblingFallback, export: compoundExportName(siblingFallback.export, residual) }
|
|
248
|
+
: siblingFallback;
|
|
249
|
+
const definition = siblingFallback !== undefined && siblingFallback.kind !== "custom-element" && siblingFallback.source.type === "local"
|
|
250
|
+
? holderDefinition(siblingFallback.source.filePath, siblingFallback.export)
|
|
251
|
+
: undefined;
|
|
252
|
+
for (const ft of jsxTerminals) {
|
|
253
|
+
pushOccurrence(occurrences, filePath, usage, matchedImport, compoundExportName(effectiveImported, residual), packageName, graph, fileGraph, ft, ownerRef, helperIndex, opts, localFallback, viaOverride, ft.identity === null ? definition : undefined);
|
|
254
|
+
}
|
|
255
|
+
return { kind: "evaluated", evaluation, value: refType };
|
|
256
|
+
}
|
|
257
|
+
// Target not in graph. Workspace membership: a
|
|
258
|
+
// resolved target owned by a workspace member is first-party — emit
|
|
259
|
+
// LOCAL, pinned to the definition file (bounded re-export resolution)
|
|
260
|
+
// so identity is scope-invariant between whole-repo and single-app
|
|
261
|
+
// scans. Otherwise: external, trusting the JSX call site (external
|
|
262
|
+
// package or unresolvable specifier — identity null →
|
|
263
|
+
// matchedImport-based componentId). See `unparsedMemberDefinition`.
|
|
264
|
+
const member = resolvedAbsTarget !== null
|
|
265
|
+
? unparsedMemberDefinition(graph, resolvedAbsTarget, effectiveImported)
|
|
266
|
+
: null;
|
|
267
|
+
const sentinelFt = { denotation: { kind: "foreign" }, viaTrail: [], identity: null };
|
|
268
|
+
if (member !== null) {
|
|
269
|
+
const localFallback = {
|
|
270
|
+
kind: componentKindFor(fileGraph.dialect),
|
|
271
|
+
export: compoundExportName(member.exportName, residual),
|
|
272
|
+
source: { type: "local", filePath: member.absFile },
|
|
273
|
+
};
|
|
274
|
+
pushOccurrence(occurrences, filePath, usage, matchedImport, compoundExportName(effectiveImported, residual), packageName, graph, fileGraph, sentinelFt, ownerRef, helperIndex, opts, localFallback, viaOverride);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
pushOccurrence(occurrences, filePath, usage, matchedImport, compoundExportName(effectiveImported, residual), packageName, graph, fileGraph, sentinelFt, ownerRef, helperIndex, opts, undefined, viaOverride);
|
|
278
|
+
}
|
|
279
|
+
return { kind: "evaluated", evaluation: [sentinelFt], value: null };
|
|
280
|
+
};
|
|
281
|
+
for (const [filePath, fileGraph] of graph.files) {
|
|
282
|
+
for (let usageIdx = 0; usageIdx < fileGraph.jsxUsages.length; usageIdx++) {
|
|
283
|
+
const usage = fileGraph.jsxUsages[usageIdx];
|
|
284
|
+
if (!usage)
|
|
285
|
+
continue;
|
|
286
|
+
const ownership = fileGraph.ownership[usageIdx];
|
|
287
|
+
const lengthBefore = occurrences.length;
|
|
288
|
+
const outcome = pushJsxUsageOccurrences(filePath, fileGraph, usage, ownership);
|
|
289
|
+
const admitted = stampComposition(lengthBefore, "jsx", usageIdx, ownership?.depth ?? 0, ownership?.parentUsageIdx);
|
|
290
|
+
if (admitted === 0)
|
|
291
|
+
reportDroppedRender(opts, filePath, fileGraph.dialect, usage, outcome);
|
|
353
292
|
}
|
|
354
293
|
// Tag-usage resolution (Vue auto-imports, Lit tag-only references, HTML).
|
|
355
294
|
for (let i = 0; i < fileGraph.tagUsages.length; i++) {
|
|
@@ -433,7 +372,7 @@ export function resolve(graph, opts) {
|
|
|
433
372
|
continue;
|
|
434
373
|
if (!registry.hasLocal(filePath, decl.symbol))
|
|
435
374
|
continue;
|
|
436
|
-
const holderJsx = walkWithFolding(graph, fileGraph, decl.value, argMap)
|
|
375
|
+
const holderJsx = creditedTerminals(walkWithFolding(graph, fileGraph, decl.value, argMap));
|
|
437
376
|
const foldedAway = foldedHolder(holderJsx, filePath);
|
|
438
377
|
if (foldedAway !== null)
|
|
439
378
|
folded.set(entryKey(filePath, decl.symbol), foldedAway);
|
|
@@ -470,10 +409,7 @@ export function resolve(graph, opts) {
|
|
|
470
409
|
loc: { line: 0, column: 0 },
|
|
471
410
|
originFile: filePath,
|
|
472
411
|
};
|
|
473
|
-
const owners = () =>
|
|
474
|
-
rawOwnerComponentId: ownerIdFromResolution(graph, res.ownerDecl),
|
|
475
|
-
viaPrefix: res.viaPrefix,
|
|
476
|
-
}));
|
|
412
|
+
const owners = () => chainOwners(graph, helperIndex, ownerRef);
|
|
477
413
|
const lengthBefore = occurrences.length;
|
|
478
414
|
pushCallArgumentOccurrences(occurrences, filePath, call, owners, graph, fileGraph, argMap, opts);
|
|
479
415
|
compactSlice(lengthBefore);
|
|
@@ -696,29 +632,65 @@ effectiveImported, consumerDialect) {
|
|
|
696
632
|
};
|
|
697
633
|
}
|
|
698
634
|
/**
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
635
|
+
* THE occurrence constructor: one occurrence per owner, `viaChain` being the
|
|
636
|
+
* owner's prefix followed by `chain`. Every emission path calls it; never
|
|
637
|
+
* build an `EngineOccurrence` literal anywhere else. The `occurrenceId` it
|
|
638
|
+
* computes is provisional, keyed on the serialised componentId; scan.ts
|
|
639
|
+
* re-derives it from the hashed componentId.
|
|
704
640
|
*/
|
|
705
|
-
function
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
641
|
+
function emitOccurrence(occurrences, site, rawComponentId, owners, chain, definition) {
|
|
642
|
+
const provisionalId = serialiseComponentId(rawComponentId);
|
|
643
|
+
for (const { rawOwnerComponentId, viaPrefix } of owners) {
|
|
644
|
+
const viaChain = [...viaPrefix, ...chain];
|
|
645
|
+
occurrences.push({
|
|
646
|
+
rawComponentId,
|
|
647
|
+
filePath: site.filePath,
|
|
648
|
+
line: site.line,
|
|
649
|
+
column: site.column,
|
|
650
|
+
via: viaChain[0] ?? chain[0],
|
|
651
|
+
viaChain,
|
|
652
|
+
props: site.props,
|
|
653
|
+
events: site.events,
|
|
654
|
+
occurrenceId: computeOccurrenceId(provisionalId, site.filePath, site.line, site.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined),
|
|
655
|
+
depth: 0,
|
|
656
|
+
...(definition !== undefined ? { definition } : {}),
|
|
657
|
+
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
658
|
+
});
|
|
710
659
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
660
|
+
}
|
|
661
|
+
function usageSite(filePath, usage) {
|
|
662
|
+
return {
|
|
663
|
+
filePath,
|
|
664
|
+
line: usage.loc.line,
|
|
665
|
+
column: usage.loc.column,
|
|
666
|
+
props: projectPropUsages(usage.props),
|
|
667
|
+
events: usage.events ?? [],
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
/** The owner chain of `ownerRef`, one owner per resolution with its `helper-call` prefix. */
|
|
671
|
+
function chainOwners(graph, helperIndex, ownerRef) {
|
|
672
|
+
return resolveOwnerChain(ownerRef, graph, helperIndex, createCycleGuard()).map((res) => ({
|
|
673
|
+
rawOwnerComponentId: ownerIdFromResolution(graph, res.ownerDecl),
|
|
674
|
+
viaPrefix: res.viaPrefix,
|
|
675
|
+
}));
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Owners of a JSX usage. A `viaOverride` means the parser pre-decided the
|
|
679
|
+
* owner (prop-forward): one owner, no prefix. Otherwise the owner chain fans
|
|
680
|
+
* out, each resolution contributing its `helper-call` prefix.
|
|
681
|
+
*/
|
|
682
|
+
function usageOwners(graph, helperIndex, ownerRef, viaOverride, fallbackFile) {
|
|
683
|
+
if (viaOverride !== undefined) {
|
|
684
|
+
return [{ rawOwnerComponentId: ownerIdFromRef(graph, ownerRef, fallbackFile), viaPrefix: [] }];
|
|
685
|
+
}
|
|
686
|
+
return chainOwners(graph, helperIndex, ownerRef);
|
|
715
687
|
}
|
|
716
688
|
function pushOccurrence(occurrences, filePath, usage, matchedImport,
|
|
717
689
|
/** Export name for the fallback identity — the root export resolved via
|
|
718
690
|
* `effectiveExportName` joined with the residual member chain via
|
|
719
691
|
* `compoundExportName`. Distinct from `matchedImport.imported`,
|
|
720
692
|
* which stays raw for `via` provenance. */
|
|
721
|
-
effectiveImported, packageName, graph, fileGraph, ft, ownerRef, helperIndex, opts, fallbackOverride, viaOverride) {
|
|
693
|
+
effectiveImported, packageName, graph, fileGraph, ft, ownerRef, helperIndex, opts, fallbackOverride, viaOverride, definition) {
|
|
722
694
|
const fallback = fallbackOverride ?? {
|
|
723
695
|
kind: componentKindFor(fileGraph.dialect),
|
|
724
696
|
export: effectiveImported,
|
|
@@ -729,82 +701,63 @@ effectiveImported, packageName, graph, fileGraph, ft, ownerRef, helperIndex, opt
|
|
|
729
701
|
fallbackSpecifier: matchedImport.specifier,
|
|
730
702
|
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
731
703
|
});
|
|
732
|
-
// Derive wraps cross-link for external react-component occurrences.
|
|
733
|
-
// wrapCallback only fires for external packages — local react-components are
|
|
734
|
-
// user-written, not wrappers. Does NOT affect rawComponentId or hashing.
|
|
735
|
-
const wraps = computeWraps(rawComponentId, opts?.wrapCallback);
|
|
736
704
|
const outerVia = viaOverride ?? {
|
|
737
705
|
kind: "direct-import",
|
|
738
706
|
specifier: matchedImport.specifier,
|
|
739
707
|
import: matchedImport.imported,
|
|
740
708
|
};
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
709
|
+
emitOccurrence(occurrences, usageSite(filePath, usage), rawComponentId, usageOwners(graph, helperIndex, ownerRef, viaOverride, filePath), viaOverride !== undefined ? [viaOverride] : [outerVia, ...ft.viaTrail], definition);
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* The one reporter for a JSX usage that produced no occurrence. A host-element
|
|
713
|
+
* root reports nothing. A failed import is `unresolved-import`, an unbound root
|
|
714
|
+
* `unresolved-reference`. A walked value holding an `import()` is
|
|
715
|
+
* `lazy-import-unsupported`, naming the file the `import()` is declared in when
|
|
716
|
+
* that is not the render file; the `import()` in the walked value is checked
|
|
717
|
+
* before the evaluation. Everything the evaluation itself decides
|
|
718
|
+
* is `renderOutcome`'s: a value that is not a component reports nothing, a
|
|
719
|
+
* value supplied elsewhere reports `late-bound-render`, and anything else —
|
|
720
|
+
* including a credit whose occurrences admission rejected (#544: one occurrence
|
|
721
|
+
* or one diagnostic) — reports `unresolved-reference`. A non-react dialect
|
|
722
|
+
* reports only the import codes (#621).
|
|
723
|
+
*/
|
|
724
|
+
function reportDroppedRender(opts, filePath, dialect, usage, outcome) {
|
|
725
|
+
const collector = opts?.collector;
|
|
726
|
+
if (collector === undefined)
|
|
727
|
+
return;
|
|
728
|
+
if (isHostElementRoot(usage.ref))
|
|
729
|
+
return;
|
|
730
|
+
const at = { filePath, line: usage.loc.line, column: usage.loc.column };
|
|
731
|
+
const positional = { ...at, symbol: usage.ref.symbol, memberChain: [...usage.ref.memberChain] };
|
|
732
|
+
if (outcome.kind === "import-failed") {
|
|
733
|
+
collector.emit({ code: "unresolved-import", severity: "warning", ...at, specifier: outcome.specifier });
|
|
761
734
|
return;
|
|
762
735
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
filePath
|
|
775
|
-
line: usage.loc.line,
|
|
776
|
-
column: usage.loc.column,
|
|
777
|
-
via: viaChain[0] ?? outerVia,
|
|
778
|
-
viaChain,
|
|
779
|
-
props: projectPropUsages(usage.props),
|
|
780
|
-
events: usage.events ?? [],
|
|
781
|
-
occurrenceId,
|
|
782
|
-
depth: 0,
|
|
783
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
784
|
-
...(wraps !== undefined ? { wraps } : {}),
|
|
736
|
+
if (outcome.kind === "unbound") {
|
|
737
|
+
collector.emit({ code: "unresolved-reference", severity: "info", ...positional });
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const di = outcome.value === null ? null : firstDynamicImport(outcome.value);
|
|
741
|
+
if (di) {
|
|
742
|
+
collector.emit({
|
|
743
|
+
code: "lazy-import-unsupported",
|
|
744
|
+
severity: "warning",
|
|
745
|
+
...at,
|
|
746
|
+
specifier: di.specifier,
|
|
747
|
+
detail: `the import() target could not be resolved to a component${di.originFile === filePath ? "" : ` (declared in ${di.originFile})`}`,
|
|
785
748
|
});
|
|
749
|
+
return;
|
|
786
750
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
* A fold that reached no JSX while an import() sits in the walked value is a
|
|
790
|
-
* lazy shape the pipeline could not resolve to a component: a `.then` callback
|
|
791
|
-
* that does more than pick a property, or a target whose component shape could
|
|
792
|
-
* not be confirmed. Report it at the render site so the miss is visible, and
|
|
793
|
-
* name the file the `import()` is declared in when that is not the render file.
|
|
794
|
-
*/
|
|
795
|
-
function emitLazyImportUnsupported(opts, filePath, loc, value) {
|
|
796
|
-
const di = opts?.collector ? firstDynamicImport(value) : null;
|
|
797
|
-
if (!di)
|
|
751
|
+
const decided = renderOutcome(outcome.evaluation);
|
|
752
|
+
if (decided.kind === "silent")
|
|
798
753
|
return;
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
detail: `the import() target could not be resolved to a component${di.originFile === filePath ? "" : ` (declared in ${di.originFile})`}`,
|
|
807
|
-
});
|
|
754
|
+
if (dialect !== "react")
|
|
755
|
+
return;
|
|
756
|
+
if (decided.kind === "credit") {
|
|
757
|
+
collector.emit({ code: "unresolved-reference", severity: "info", ...positional });
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
collector.emit({ code: decided.code, severity: "info", ...positional });
|
|
808
761
|
}
|
|
809
762
|
/**
|
|
810
763
|
* Push local-scope occurrences for a symbol declared in the same file (no import).
|
|
@@ -817,82 +770,27 @@ function pushLocalOccurrence(occurrences, filePath, usage, decl, graph, fileGrap
|
|
|
817
770
|
// static-MemberOf arm). A bare reference is unchanged:
|
|
818
771
|
// `decl.value` is what `resolveReference` returns for an empty chain.
|
|
819
772
|
const value = usage.ref.memberChain.length > 0 ? resolveReference(graph, fileGraph, usage.ref) : decl.value;
|
|
820
|
-
const
|
|
821
|
-
const jsxTerminals =
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
// unresolvable alias — emits no occurrence.
|
|
825
|
-
if (jsxTerminals.length === 0) {
|
|
826
|
-
emitLazyImportUnsupported(opts, filePath, usage.loc, value);
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
773
|
+
const evaluation = walkWithFolding(graph, fileGraph, value, argMap);
|
|
774
|
+
const jsxTerminals = creditedTerminals(evaluation);
|
|
775
|
+
if (jsxTerminals.length === 0)
|
|
776
|
+
return { evaluation, value };
|
|
829
777
|
const outerVia = viaOverride ?? { kind: "local-component" };
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
};
|
|
839
|
-
const rawComponentId = componentIdFromIdentity(ft.identity, fallback, fileGraph.dialect, {
|
|
840
|
-
filePath,
|
|
841
|
-
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
842
|
-
});
|
|
843
|
-
const viaChain = [viaOverride];
|
|
844
|
-
const provisionalId = serialiseComponentId(rawComponentId);
|
|
845
|
-
const occurrenceId = computeOccurrenceId(provisionalId, filePath, usage.loc.line, usage.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
846
|
-
occurrences.push({
|
|
847
|
-
rawComponentId,
|
|
848
|
-
filePath,
|
|
849
|
-
line: usage.loc.line,
|
|
850
|
-
column: usage.loc.column,
|
|
851
|
-
via: viaChain[0] ?? outerVia,
|
|
852
|
-
viaChain,
|
|
853
|
-
props: projectPropUsages(usage.props),
|
|
854
|
-
events: usage.events ?? [],
|
|
855
|
-
occurrenceId,
|
|
856
|
-
depth: 0,
|
|
857
|
-
definition: { line: decl.loc.line, column: decl.loc.column },
|
|
858
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
// Helper-call fanout: resolve owner chain and emit per (terminal × resolution).
|
|
864
|
-
const ownerResolutions = resolveOwnerChain(ownerRef, graph, helperIndex, createCycleGuard());
|
|
778
|
+
const site = usageSite(filePath, usage);
|
|
779
|
+
const owners = usageOwners(graph, helperIndex, ownerRef, viaOverride, fileGraph.filePath);
|
|
780
|
+
const fallback = {
|
|
781
|
+
kind: componentKindFor(fileGraph.dialect),
|
|
782
|
+
export: compoundExportName(usage.ref.symbol, usage.ref.memberChain),
|
|
783
|
+
source: { type: "local", filePath },
|
|
784
|
+
};
|
|
785
|
+
const definition = { line: decl.loc.line, column: decl.loc.column };
|
|
865
786
|
for (const ft of jsxTerminals) {
|
|
866
|
-
const fallback = {
|
|
867
|
-
kind: componentKindFor(fileGraph.dialect),
|
|
868
|
-
export: compoundExportName(usage.ref.symbol, usage.ref.memberChain),
|
|
869
|
-
source: { type: "local", filePath },
|
|
870
|
-
};
|
|
871
787
|
const rawComponentId = componentIdFromIdentity(ft.identity, fallback, fileGraph.dialect, {
|
|
872
788
|
filePath,
|
|
873
789
|
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
874
790
|
});
|
|
875
|
-
|
|
876
|
-
const rawOwnerComponentId = ownerIdFromResolution(graph, res.ownerDecl);
|
|
877
|
-
const viaChain = [...res.viaPrefix, outerVia, ...ft.viaTrail];
|
|
878
|
-
const provisionalId = serialiseComponentId(rawComponentId);
|
|
879
|
-
const occurrenceId = computeOccurrenceId(provisionalId, filePath, usage.loc.line, usage.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
880
|
-
occurrences.push({
|
|
881
|
-
rawComponentId,
|
|
882
|
-
filePath,
|
|
883
|
-
line: usage.loc.line,
|
|
884
|
-
column: usage.loc.column,
|
|
885
|
-
via: viaChain[0] ?? outerVia,
|
|
886
|
-
viaChain,
|
|
887
|
-
props: projectPropUsages(usage.props),
|
|
888
|
-
events: usage.events ?? [],
|
|
889
|
-
occurrenceId,
|
|
890
|
-
depth: 0,
|
|
891
|
-
definition: { line: decl.loc.line, column: decl.loc.column },
|
|
892
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
893
|
-
});
|
|
894
|
-
}
|
|
791
|
+
emitOccurrence(occurrences, site, rawComponentId, owners, viaOverride !== undefined ? [viaOverride] : [outerVia, ...ft.viaTrail], ft.identity === null ? definition : undefined);
|
|
895
792
|
}
|
|
793
|
+
return { evaluation, value };
|
|
896
794
|
}
|
|
897
795
|
/**
|
|
898
796
|
* A holder is folded away iff its fold reaches JSX and EVERY JSX
|
|
@@ -939,7 +837,7 @@ function pushArgumentSiteOccurrences(occurrences, filePath, holder, call, holder
|
|
|
939
837
|
const folded = foldedArgument(graph, fileGraph, call, holderJsx, argMap);
|
|
940
838
|
if (folded?.arg.kind !== "ReturnTypeOf")
|
|
941
839
|
return;
|
|
942
|
-
const innerJsx = walkWithFolding(graph, fileGraph, folded.arg, argMap)
|
|
840
|
+
const innerJsx = creditedTerminals(walkWithFolding(graph, fileGraph, folded.arg, argMap));
|
|
943
841
|
pushArgumentSiteOccurrences(occurrences, filePath, holder, folded.arg, innerJsx, graph, fileGraph, argMap, opts, depth + 1);
|
|
944
842
|
return;
|
|
945
843
|
}
|
|
@@ -978,8 +876,6 @@ function callFoldShape(jsx) {
|
|
|
978
876
|
return "product";
|
|
979
877
|
return credited === jsx.length ? "wrapper" : null;
|
|
980
878
|
}
|
|
981
|
-
/** React's own hook predicate (`isHookName` in rules-of-hooks and the compiler). */
|
|
982
|
-
const HOOK_NAME = /^use[A-Z0-9]/;
|
|
983
879
|
/**
|
|
984
880
|
* THE argument-site push seam: one `passed-as-argument` occurrence per
|
|
985
881
|
* (identifier argument that resolves to a component) × (owner). The
|
|
@@ -1021,34 +917,15 @@ function pushCallArgumentOccurrences(occurrences, filePath, call, owners, graph,
|
|
|
1021
917
|
// and `buildComponentId` returns the fallback only for a null one. The
|
|
1022
918
|
// relabel seam's signature requires a fallback all the same.
|
|
1023
919
|
const unusedFallback = makeLocalComponentId(filePath, arg.ref.symbol, fileGraph.dialect);
|
|
1024
|
-
for (const ft of walkWithFolding(graph, fileGraph, arg, argMap)) {
|
|
1025
|
-
if (ft.
|
|
920
|
+
for (const ft of creditedTerminals(walkWithFolding(graph, fileGraph, arg, argMap))) {
|
|
921
|
+
if (ft.identity === null)
|
|
1026
922
|
continue;
|
|
1027
923
|
const rawComponentId = componentIdFromIdentity(ft.identity, unusedFallback, fileGraph.dialect, {
|
|
1028
924
|
filePath,
|
|
1029
925
|
resolveExternalLeaf: opts?.resolveExternalLeaf,
|
|
1030
926
|
});
|
|
1031
|
-
const wraps = computeWraps(rawComponentId, opts?.wrapCallback);
|
|
1032
927
|
resolvedOwners ??= owners();
|
|
1033
|
-
|
|
1034
|
-
const viaChain = [...viaPrefix, outerVia, ...hops, ...ft.viaTrail];
|
|
1035
|
-
const occurrenceId = computeOccurrenceId(serialiseComponentId(rawComponentId), filePath, arg.ref.loc.line, arg.ref.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
1036
|
-
occurrences.push({
|
|
1037
|
-
rawComponentId,
|
|
1038
|
-
filePath,
|
|
1039
|
-
line: arg.ref.loc.line,
|
|
1040
|
-
column: arg.ref.loc.column,
|
|
1041
|
-
via: viaChain[0] ?? outerVia,
|
|
1042
|
-
viaChain,
|
|
1043
|
-
props: {},
|
|
1044
|
-
events: [],
|
|
1045
|
-
occurrenceId,
|
|
1046
|
-
depth: 0,
|
|
1047
|
-
...(argDecl !== null ? { definition: { line: argDecl.loc.line, column: argDecl.loc.column } } : {}),
|
|
1048
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
1049
|
-
...(wraps !== undefined ? { wraps } : {}),
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
928
|
+
emitOccurrence(occurrences, { filePath, line: arg.ref.loc.line, column: arg.ref.loc.column, props: {}, events: [] }, rawComponentId, resolvedOwners, [outerVia, ...hops, ...ft.viaTrail], argDecl !== null ? { line: argDecl.loc.line, column: argDecl.loc.column } : undefined);
|
|
1052
929
|
}
|
|
1053
930
|
};
|
|
1054
931
|
const seedArguments = (site, siteDepth) => {
|
|
@@ -1061,7 +938,7 @@ function pushCallArgumentOccurrences(occurrences, filePath, call, owners, graph,
|
|
|
1061
938
|
}
|
|
1062
939
|
if (arg.kind !== "ReturnTypeOf" || callDepth >= MAX_ARGUMENT_CALL_DEPTH)
|
|
1063
940
|
return;
|
|
1064
|
-
const argJsx = walkWithFolding(graph, fileGraph, arg, argMap)
|
|
941
|
+
const argJsx = creditedTerminals(walkWithFolding(graph, fileGraph, arg, argMap));
|
|
1065
942
|
const shape = callFoldShape(argJsx);
|
|
1066
943
|
if (shape === "product") {
|
|
1067
944
|
seedArguments(arg, callDepth + 1);
|
|
@@ -1098,27 +975,10 @@ function findTagOwnership(ownership, usageIdx) {
|
|
|
1098
975
|
*/
|
|
1099
976
|
function pushTagOccurrence(occurrences, filePath, usage, rawComponentId, ownerRef, viaOverride, graph, helperIndex) {
|
|
1100
977
|
const via = viaOverride ?? { kind: "html-tag" };
|
|
1101
|
-
// Owner-chain fanout uses the same machinery as JsxUsage
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
const viaChain = [...res.viaPrefix, via];
|
|
1106
|
-
const provisionalId = serialiseComponentId(rawComponentId);
|
|
1107
|
-
const occurrenceId = computeOccurrenceId(provisionalId, filePath, usage.loc.line, usage.loc.column, rawOwnerComponentId !== undefined ? serialiseComponentId(rawOwnerComponentId) : undefined);
|
|
1108
|
-
occurrences.push({
|
|
1109
|
-
rawComponentId,
|
|
1110
|
-
filePath,
|
|
1111
|
-
line: usage.loc.line,
|
|
1112
|
-
column: usage.loc.column,
|
|
1113
|
-
via: viaChain[0] ?? via,
|
|
1114
|
-
viaChain,
|
|
1115
|
-
props: projectPropUsages(usage.props),
|
|
1116
|
-
events: usage.events ?? [],
|
|
1117
|
-
occurrenceId,
|
|
1118
|
-
depth: 0,
|
|
1119
|
-
...(rawOwnerComponentId !== undefined ? { rawOwnerComponentId } : {}),
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
978
|
+
// Owner-chain fanout uses the same machinery as JsxUsage, a `viaOverride`
|
|
979
|
+
// notwithstanding.
|
|
980
|
+
const owners = chainOwners(graph, helperIndex, ownerRef);
|
|
981
|
+
emitOccurrence(occurrences, usageSite(filePath, usage), rawComponentId, owners, [via]);
|
|
1122
982
|
}
|
|
1123
983
|
/**
|
|
1124
984
|
* Decide the `kind` for a JSX-emitted ComponentId. A concrete
|