@usefragments/core 2.0.0 → 2.0.2
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/{chunk-BMPYIUZE.js → chunk-QPOKQ5H6.js} +8 -2
- package/dist/chunk-QPOKQ5H6.js.map +1 -0
- package/dist/codes/index.js +1 -1
- package/dist/index.d.ts +18 -4
- package/dist/index.js +158 -66
- package/dist/index.js.map +1 -1
- package/dist/schemas/index.js +1 -1
- package/package.json +1 -1
- package/src/canonical-source.ts +41 -0
- package/src/evaluation/canonical-facts-trust.test.ts +337 -0
- package/src/evaluation/evaluate.ts +67 -10
- package/src/evaluation/evaluation-v2-receipt-v1.test.ts +1 -1
- package/src/evaluation/index.ts +1 -1
- package/src/evaluation/types.ts +5 -3
- package/src/facts/builders.ts +4 -0
- package/src/facts/fact-index.ts +4 -0
- package/src/facts/facts.test.ts +70 -0
- package/src/facts/types.ts +4 -0
- package/src/identity/usage.test.ts +64 -0
- package/src/identity/usage.ts +7 -1
- package/src/index.ts +6 -0
- package/src/rules/components-prefer-library.ts +110 -33
- package/dist/chunk-BMPYIUZE.js.map +0 -1
package/src/facts/facts.test.ts
CHANGED
|
@@ -549,6 +549,76 @@ describe("FactIndex — query layer", () => {
|
|
|
549
549
|
expect(ix.get(first.id)).toEqual(first);
|
|
550
550
|
});
|
|
551
551
|
|
|
552
|
+
it.each(["missing-first", "missing-second", "moved"] as const)(
|
|
553
|
+
"coalesces component definitions when only their evidence region differs (%s)",
|
|
554
|
+
(mode) => {
|
|
555
|
+
const onConflict = vi.fn();
|
|
556
|
+
const onDuplicate = vi.fn();
|
|
557
|
+
const ix = new FactIndex({ onConflict, onDuplicate });
|
|
558
|
+
const definition = {
|
|
559
|
+
file: "src/Button.tsx",
|
|
560
|
+
exportName: "Button",
|
|
561
|
+
exported: true,
|
|
562
|
+
componentKey: "src/Button.tsx#Button",
|
|
563
|
+
renderRoot: { resolution: "intrinsic", tag: "button" } as const,
|
|
564
|
+
propSurface: ["disabled"],
|
|
565
|
+
};
|
|
566
|
+
const first = makeComponentDefinitionFact({
|
|
567
|
+
...definition,
|
|
568
|
+
...(mode !== "missing-first"
|
|
569
|
+
? { location: { file: definition.file, line: 2, column: 0, endLine: 4, endColumn: 1 } }
|
|
570
|
+
: {}),
|
|
571
|
+
});
|
|
572
|
+
const second = makeComponentDefinitionFact({
|
|
573
|
+
...definition,
|
|
574
|
+
...(mode !== "missing-second"
|
|
575
|
+
? { location: { file: definition.file, line: 6, column: 0, endLine: 8, endColumn: 1 } }
|
|
576
|
+
: {}),
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
ix.addMany([first, second]);
|
|
580
|
+
|
|
581
|
+
expect(onConflict).not.toHaveBeenCalled();
|
|
582
|
+
expect(onDuplicate).toHaveBeenCalledTimes(1);
|
|
583
|
+
expect(onDuplicate).toHaveBeenCalledWith({ kept: first, duplicate: second });
|
|
584
|
+
expect(ix.byKind("component_definition")).toEqual([first]);
|
|
585
|
+
}
|
|
586
|
+
);
|
|
587
|
+
|
|
588
|
+
it.each([
|
|
589
|
+
{ renderRoot: { resolution: "intrinsic", tag: "a" } as const },
|
|
590
|
+
{ propSurface: ["disabled", "href"] },
|
|
591
|
+
{ exported: false },
|
|
592
|
+
])("retains component-definition conflicts for material changes: %j", (change) => {
|
|
593
|
+
const onConflict = vi.fn();
|
|
594
|
+
const onDuplicate = vi.fn();
|
|
595
|
+
const ix = new FactIndex({ onConflict, onDuplicate });
|
|
596
|
+
const definition = {
|
|
597
|
+
file: "src/Button.tsx",
|
|
598
|
+
exportName: "Button",
|
|
599
|
+
exported: true,
|
|
600
|
+
componentKey: "src/Button.tsx#Button",
|
|
601
|
+
renderRoot: { resolution: "intrinsic", tag: "button" } as const,
|
|
602
|
+
propSurface: ["disabled"],
|
|
603
|
+
};
|
|
604
|
+
const first = makeComponentDefinitionFact(definition);
|
|
605
|
+
const second = makeComponentDefinitionFact({
|
|
606
|
+
...definition,
|
|
607
|
+
...change,
|
|
608
|
+
location: { file: definition.file, line: 2, column: 0, endLine: 4, endColumn: 1 },
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
ix.addMany([first, second]);
|
|
612
|
+
|
|
613
|
+
expect(onConflict).toHaveBeenCalledTimes(1);
|
|
614
|
+
expect(onConflict).toHaveBeenCalledWith(expect.stringContaining("conflicting facts"), {
|
|
615
|
+
kept: first,
|
|
616
|
+
skipped: second,
|
|
617
|
+
});
|
|
618
|
+
expect(onDuplicate).not.toHaveBeenCalled();
|
|
619
|
+
expect(ix.byKind("component_definition")).toEqual([first]);
|
|
620
|
+
});
|
|
621
|
+
|
|
552
622
|
it("includes token-definition provenance in logical conflict reports", () => {
|
|
553
623
|
const onConflict = vi.fn();
|
|
554
624
|
const ix = new FactIndex({ onConflict });
|
package/src/facts/types.ts
CHANGED
|
@@ -317,6 +317,8 @@ export interface UsageComponentFact extends BaseFact {
|
|
|
317
317
|
kind: "usage_component";
|
|
318
318
|
nodeId: FactId;
|
|
319
319
|
componentId: ComponentId;
|
|
320
|
+
/** Exact local definition resolved from source, separate from catalog/policy identity. */
|
|
321
|
+
definitionKey?: string;
|
|
320
322
|
}
|
|
321
323
|
|
|
322
324
|
export interface UsageImportFact extends BaseFact {
|
|
@@ -688,6 +690,8 @@ export interface ComponentDefinitionFact extends BaseFact {
|
|
|
688
690
|
componentKey: string;
|
|
689
691
|
renderRoot: ComponentDefinitionRenderRoot;
|
|
690
692
|
propSurface: string[];
|
|
693
|
+
/** Implementation region, used to scope exemptions; never part of fact identity. */
|
|
694
|
+
location?: FactLocation;
|
|
691
695
|
stylingChannel?: {
|
|
692
696
|
module: string;
|
|
693
697
|
ownsVisualRole: boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
FactIndex,
|
|
4
|
+
asComponentId,
|
|
5
|
+
makeComponentDefinitionFact,
|
|
6
|
+
makeUsageComponentFact,
|
|
7
|
+
makeUsageNodeFact,
|
|
8
|
+
} from "../index.js";
|
|
9
|
+
import { indexComponentIdentityUsage } from "./usage.js";
|
|
10
|
+
|
|
11
|
+
describe("component identity usage joins", () => {
|
|
12
|
+
const key = "src/Button.tsx#Button";
|
|
13
|
+
const catalogId = asComponentId("cloud:source#source::src/Button.tsx::Button");
|
|
14
|
+
|
|
15
|
+
it("preserves fact identity and counts a node once with exact definition evidence", () => {
|
|
16
|
+
const ix = new FactIndex();
|
|
17
|
+
const node = makeUsageNodeFact({
|
|
18
|
+
file: "src/App.tsx",
|
|
19
|
+
nodePath: "0:0",
|
|
20
|
+
element: "Button",
|
|
21
|
+
location: { file: "src/App.tsx", line: 1, column: 0 },
|
|
22
|
+
});
|
|
23
|
+
const original = makeUsageComponentFact({ nodeId: node.id, componentId: catalogId });
|
|
24
|
+
const resolved = makeUsageComponentFact({
|
|
25
|
+
nodeId: node.id,
|
|
26
|
+
componentId: catalogId,
|
|
27
|
+
definitionKey: key,
|
|
28
|
+
});
|
|
29
|
+
expect(resolved.id).toBe(original.id);
|
|
30
|
+
ix.addMany([
|
|
31
|
+
node,
|
|
32
|
+
resolved,
|
|
33
|
+
makeUsageComponentFact({ nodeId: node.id, componentId: asComponentId(key) }),
|
|
34
|
+
makeComponentDefinitionFact({
|
|
35
|
+
file: "src/Button.tsx",
|
|
36
|
+
exportName: "Button",
|
|
37
|
+
componentKey: key,
|
|
38
|
+
propSurface: [],
|
|
39
|
+
renderRoot: { resolution: "intrinsic", tag: "button" },
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
expect(indexComponentIdentityUsage(ix).get(key)).toMatchObject({
|
|
43
|
+
usageCount: 1,
|
|
44
|
+
blastRadius: 1,
|
|
45
|
+
});
|
|
46
|
+
expect(indexComponentIdentityUsage(ix).has(catalogId)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("does not synthesize a definition from catalog evidence", () => {
|
|
50
|
+
const ix = new FactIndex();
|
|
51
|
+
const node = makeUsageNodeFact({
|
|
52
|
+
file: "src/App.tsx",
|
|
53
|
+
nodePath: "0:0",
|
|
54
|
+
element: "Button",
|
|
55
|
+
location: { file: "src/App.tsx", line: 1, column: 0 },
|
|
56
|
+
});
|
|
57
|
+
ix.addMany([
|
|
58
|
+
node,
|
|
59
|
+
makeUsageComponentFact({ nodeId: node.id, componentId: catalogId, definitionKey: key }),
|
|
60
|
+
]);
|
|
61
|
+
expect(indexComponentIdentityUsage(ix).has(key)).toBe(false);
|
|
62
|
+
expect(indexComponentIdentityUsage(ix).get(catalogId)?.usageCount).toBe(1);
|
|
63
|
+
});
|
|
64
|
+
});
|
package/src/identity/usage.ts
CHANGED
|
@@ -12,12 +12,18 @@ export function indexComponentIdentityUsage(
|
|
|
12
12
|
ix: FactIndex
|
|
13
13
|
): ReadonlyMap<string, ComponentIdentityUsage> {
|
|
14
14
|
const nodesById = new Map(ix.byKind("usage_node").map((node) => [node.id, node]));
|
|
15
|
+
const definitions = new Set(
|
|
16
|
+
ix.byKind("component_definition").map((definition) => definition.componentKey)
|
|
17
|
+
);
|
|
15
18
|
const nodesByComponent = new Map<string, Map<string, UsageNodeFact>>();
|
|
16
19
|
|
|
17
20
|
for (const usage of ix.byKind("usage_component")) {
|
|
18
21
|
const node = nodesById.get(usage.nodeId);
|
|
19
22
|
if (!node) continue;
|
|
20
|
-
const componentKey =
|
|
23
|
+
const componentKey =
|
|
24
|
+
usage.definitionKey && definitions.has(usage.definitionKey)
|
|
25
|
+
? usage.definitionKey
|
|
26
|
+
: String(usage.componentId);
|
|
21
27
|
const nodes = nodesByComponent.get(componentKey) ?? new Map<string, UsageNodeFact>();
|
|
22
28
|
nodes.set(node.id, node);
|
|
23
29
|
nodesByComponent.set(componentKey, nodes);
|
package/src/index.ts
CHANGED
|
@@ -684,6 +684,11 @@ export {
|
|
|
684
684
|
type RuleConfigView,
|
|
685
685
|
} from "./rules/rule-config.js";
|
|
686
686
|
export { indexComponentIdentityUsage, type ComponentIdentityUsage } from "./identity/usage.js";
|
|
687
|
+
export {
|
|
688
|
+
isCanonicalSourceDefinition,
|
|
689
|
+
canonicalSourceContainsFile,
|
|
690
|
+
canonicalSourceIncludesExport,
|
|
691
|
+
} from "./canonical-source.js";
|
|
687
692
|
export { nearestSignedScaleValue } from "./rules/utils.js";
|
|
688
693
|
export {
|
|
689
694
|
buildSpacingTokenLookup,
|
|
@@ -1124,6 +1129,7 @@ export {
|
|
|
1124
1129
|
EVALUATION_RECEIPT_STRING_MAX_BYTES_V1,
|
|
1125
1130
|
EVALUATOR_VERSION,
|
|
1126
1131
|
EVALUATOR_VERSION_V2,
|
|
1132
|
+
LEGACY_EVALUATOR_VERSION_V2,
|
|
1127
1133
|
effectiveEvaluationWaiverV2Schema,
|
|
1128
1134
|
evaluate,
|
|
1129
1135
|
evaluationInputDigestV2,
|
|
@@ -2,6 +2,8 @@ import type { CanonicalSource, GovernanceSeverity } from "../governance.js";
|
|
|
2
2
|
import type {
|
|
3
3
|
FactId,
|
|
4
4
|
FactIndex,
|
|
5
|
+
ComponentDefinitionFact,
|
|
6
|
+
FactLocation,
|
|
5
7
|
UsageImportFact,
|
|
6
8
|
UsageNodeFact,
|
|
7
9
|
UsagePropResolvedFact,
|
|
@@ -15,6 +17,8 @@ import {
|
|
|
15
17
|
type RawHtmlPrecisionTier,
|
|
16
18
|
} from "../raw-html-canonical.js";
|
|
17
19
|
import { ownedImportMatchesRoot, ownedImportsEqual } from "../package-identity-match.js";
|
|
20
|
+
import { indexComponentIdentityUsage } from "../identity/usage.js";
|
|
21
|
+
import { canonicalSourceContainsFile, canonicalSourceIncludesExport } from "../canonical-source.js";
|
|
18
22
|
import {
|
|
19
23
|
canonicalDirectionConflictsTarget,
|
|
20
24
|
projectCanonicalDirectionConflicts,
|
|
@@ -262,6 +266,13 @@ export function ruleComponentsPreferLibrary(ix: FactIndex): Finding[] {
|
|
|
262
266
|
const configuredSources = canonicalSourcesFromPolicy(policy.options?.canonicalSources);
|
|
263
267
|
const conflicts = projectCanonicalDirectionConflicts(ix, configuredSources);
|
|
264
268
|
const sources = configuredSources.map((source) => suppressConflictedExports(source, conflicts));
|
|
269
|
+
const approvedLocalDefinitionKeys = Array.isArray(policy.options?.approvedLocalDefinitionKeys)
|
|
270
|
+
? new Set(
|
|
271
|
+
policy.options.approvedLocalDefinitionKeys.filter(
|
|
272
|
+
(key): key is string => typeof key === "string"
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
: undefined;
|
|
265
276
|
const mappings = canonicalMappingsFromPolicy(policy.options?.canonicalMappings).filter(
|
|
266
277
|
(mapping) => !canonicalDirectionConflictsTarget(conflicts, mapping.importPath, mapping.name)
|
|
267
278
|
);
|
|
@@ -278,16 +289,41 @@ export function ruleComponentsPreferLibrary(ix: FactIndex): Finding[] {
|
|
|
278
289
|
// canonical primitives imported by a relative/`@/` path; a browser-safe string
|
|
279
290
|
// comparison of the raw specifier could never recognize them, so the resolved
|
|
280
291
|
// fact is the identity of record. Never flag them as import impostors.
|
|
281
|
-
const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(
|
|
292
|
+
const canonicalDirectoryResolvedNodes = indexCanonicalDirectoryResolvedNodes(
|
|
293
|
+
ix,
|
|
294
|
+
sources,
|
|
295
|
+
approvedLocalDefinitionKeys
|
|
296
|
+
);
|
|
282
297
|
const shadowRenderRootNodeIds =
|
|
283
298
|
ix.policy.ruleConfig("components/shadow-component")?.enabled === true
|
|
284
299
|
? indexShadowRenderRootNodeIds(ix)
|
|
285
300
|
: new Set<FactId>();
|
|
286
301
|
const findings: Finding[] = [];
|
|
287
302
|
const seenImportFixes = new Set<string>();
|
|
303
|
+
const canonicalIdentities = new Map(
|
|
304
|
+
ix
|
|
305
|
+
.byKind("component_identity")
|
|
306
|
+
.map((identity) => [identity.componentKey, identity.state === "canonical"])
|
|
307
|
+
);
|
|
308
|
+
const definitionsByFile = new Map<string, ComponentDefinitionFact[]>();
|
|
309
|
+
for (const definition of ix.byKind("component_definition")) {
|
|
310
|
+
const file = normalizePath(definition.file);
|
|
311
|
+
const definitions = definitionsByFile.get(file) ?? [];
|
|
312
|
+
definitions.push(definition);
|
|
313
|
+
definitionsByFile.set(file, definitions);
|
|
314
|
+
}
|
|
288
315
|
|
|
289
316
|
for (const node of ix.byKind("usage_node")) {
|
|
290
|
-
if (
|
|
317
|
+
if (
|
|
318
|
+
isCanonicalSourceImplementation(
|
|
319
|
+
node,
|
|
320
|
+
sources,
|
|
321
|
+
definitionsByFile,
|
|
322
|
+
canonicalIdentities,
|
|
323
|
+
approvedLocalDefinitionKeys
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
continue;
|
|
291
327
|
if (node.element.includes(".")) continue;
|
|
292
328
|
if (canonicalDirectoryResolvedNodes.has(node.id)) continue;
|
|
293
329
|
if (shadowRenderRootNodeIds.has(node.id)) continue;
|
|
@@ -299,7 +335,17 @@ export function ruleComponentsPreferLibrary(ix: FactIndex): Finding[] {
|
|
|
299
335
|
node.element === "input" ? readStaticStringProp(props, "type") : undefined;
|
|
300
336
|
const mapped = findMapping(node, nodeInputType, mappings);
|
|
301
337
|
if (mapped && imported && isMappedCanonicalImport(imported, mapped)) continue;
|
|
302
|
-
if (
|
|
338
|
+
if (
|
|
339
|
+
imported &&
|
|
340
|
+
isCanonicalImport(
|
|
341
|
+
imported,
|
|
342
|
+
node.element,
|
|
343
|
+
approvedLocalDefinitionKeys === undefined
|
|
344
|
+
? sources
|
|
345
|
+
: sources.filter((source) => source.kind !== "directory")
|
|
346
|
+
)
|
|
347
|
+
)
|
|
348
|
+
continue;
|
|
303
349
|
|
|
304
350
|
if (mapped) {
|
|
305
351
|
if (!shouldSuggestMapped(node, nodeInputType, imported, mapped)) continue;
|
|
@@ -1154,22 +1200,34 @@ function canonicalImportPath(source: CanonicalSource): string | undefined {
|
|
|
1154
1200
|
*/
|
|
1155
1201
|
function indexCanonicalDirectoryResolvedNodes(
|
|
1156
1202
|
ix: FactIndex,
|
|
1157
|
-
sources: readonly CanonicalSource[]
|
|
1203
|
+
sources: readonly CanonicalSource[],
|
|
1204
|
+
approvedLocalDefinitionKeys?: ReadonlySet<string>
|
|
1158
1205
|
): Set<FactId> {
|
|
1159
1206
|
const out = new Set<FactId>();
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1207
|
+
const directorySources = sources.filter(
|
|
1208
|
+
(source): source is Extract<CanonicalSource, { kind: "directory" }> =>
|
|
1209
|
+
source.kind === "directory"
|
|
1210
|
+
);
|
|
1211
|
+
if (directorySources.length === 0) return out;
|
|
1212
|
+
const identities = new Map(
|
|
1213
|
+
ix.byKind("component_identity").map((identity) => [identity.componentKey, identity])
|
|
1214
|
+
);
|
|
1215
|
+
for (const [componentKey, usage] of indexComponentIdentityUsage(ix)) {
|
|
1216
|
+
if (approvedLocalDefinitionKeys !== undefined && !approvedLocalDefinitionKeys.has(componentKey))
|
|
1217
|
+
continue;
|
|
1218
|
+
const identity = identities.get(componentKey);
|
|
1219
|
+
if (identity && identity.state !== "canonical") continue;
|
|
1220
|
+
const module = componentIdModule(componentKey);
|
|
1169
1221
|
if (module === undefined) continue;
|
|
1170
|
-
const
|
|
1171
|
-
if (
|
|
1172
|
-
|
|
1222
|
+
const exportName = componentKey.slice(componentKey.indexOf("#") + 1);
|
|
1223
|
+
if (
|
|
1224
|
+
directorySources.some(
|
|
1225
|
+
(source) =>
|
|
1226
|
+
canonicalSourceContainsFile(source, module) &&
|
|
1227
|
+
canonicalSourceIncludesExport(source, exportName)
|
|
1228
|
+
)
|
|
1229
|
+
) {
|
|
1230
|
+
for (const node of usage.nodes) out.add(node.id);
|
|
1173
1231
|
}
|
|
1174
1232
|
}
|
|
1175
1233
|
return out;
|
|
@@ -1181,28 +1239,47 @@ function componentIdModule(componentId: string): string | undefined {
|
|
|
1181
1239
|
return hash > 0 ? componentId.slice(0, hash) : undefined;
|
|
1182
1240
|
}
|
|
1183
1241
|
|
|
1184
|
-
function
|
|
1185
|
-
|
|
1186
|
-
sources: readonly CanonicalSource[]
|
|
1242
|
+
function isCanonicalSourceImplementation(
|
|
1243
|
+
node: UsageNodeFact,
|
|
1244
|
+
sources: readonly CanonicalSource[],
|
|
1245
|
+
definitionsByFile: ReadonlyMap<string, readonly ComponentDefinitionFact[]>,
|
|
1246
|
+
canonicalIdentities: ReadonlyMap<string, boolean>,
|
|
1247
|
+
approvedLocalDefinitionKeys?: ReadonlySet<string>
|
|
1187
1248
|
): boolean {
|
|
1188
|
-
const
|
|
1249
|
+
const definitions = definitionsByFile.get(normalizePath(node.file)) ?? [];
|
|
1189
1250
|
return sources.some((source) => {
|
|
1190
|
-
if (source
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1251
|
+
if (!canonicalSourceContainsFile(source, node.file)) return false;
|
|
1252
|
+
// Legacy package/registry implementation roots declare file ownership;
|
|
1253
|
+
// their export filters describe the public API. A frozen local inventory
|
|
1254
|
+
// always narrows that ownership to exact approved definition regions.
|
|
1255
|
+
if (
|
|
1256
|
+
approvedLocalDefinitionKeys === undefined &&
|
|
1257
|
+
(source.kind !== "directory" || (source.include === undefined && !source.exclude?.length))
|
|
1258
|
+
)
|
|
1259
|
+
return true;
|
|
1260
|
+
const owners = definitions.filter(
|
|
1261
|
+
(definition) => definition.location && locationContains(definition.location, node.location)
|
|
1262
|
+
);
|
|
1263
|
+
// Unlocated/ambiguous source cannot exempt arbitrary application code.
|
|
1264
|
+
return (
|
|
1265
|
+
owners.length > 0 &&
|
|
1266
|
+
owners.every(
|
|
1267
|
+
(definition) =>
|
|
1268
|
+
(approvedLocalDefinitionKeys === undefined ||
|
|
1269
|
+
approvedLocalDefinitionKeys.has(definition.componentKey)) &&
|
|
1270
|
+
canonicalIdentities.get(definition.componentKey) !== false &&
|
|
1271
|
+
canonicalSourceIncludesExport(source, definition.exportName)
|
|
1272
|
+
)
|
|
1273
|
+
);
|
|
1200
1274
|
});
|
|
1201
1275
|
}
|
|
1202
1276
|
|
|
1203
|
-
function
|
|
1204
|
-
|
|
1205
|
-
return
|
|
1277
|
+
function locationContains(region: FactLocation, node: FactLocation): boolean {
|
|
1278
|
+
if (region.endLine === undefined || region.endColumn === undefined) return false;
|
|
1279
|
+
return (
|
|
1280
|
+
(node.line > region.line || (node.line === region.line && node.column >= region.column)) &&
|
|
1281
|
+
(node.line < region.endLine || (node.line === region.endLine && node.column < region.endColumn))
|
|
1282
|
+
);
|
|
1206
1283
|
}
|
|
1207
1284
|
|
|
1208
1285
|
function normalizePath(path: string): string {
|