fallow-type-aware 3.16.0 → 3.18.0

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/README.md CHANGED
@@ -25,6 +25,13 @@ and SHA-256 declaration guard, but Fallow owns the final decision and fix
25
25
  policy. The sidecar does not emit TypeScript compiler diagnostics as Fallow
26
26
  findings and does not implement generic typed lint rules.
27
27
 
28
+ The raw TypeScript-Go host cannot currently expose Svelte virtual-module named
29
+ exports. If source code imports or re-exports such a name and checker resolution
30
+ has no declaration target, the sidecar returns
31
+ `svelte-virtual-module-exports` instead of claiming complete evidence. Run
32
+ `svelte-check` for framework diagnostics; Fallow stays fail-closed until a
33
+ supported host seam can preserve virtual source identity and mappings.
34
+
28
35
  ## Run locally
29
36
 
30
37
  ```sh
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { assertTypescriptBackendResolvable } from "./src/backend-preflight.mjs";
3
+ import { installWindowsChildProcessPolicy } from "./src/windows-child-process.mjs";
4
+
5
+ installWindowsChildProcessPolicy();
4
6
 
5
7
  try {
8
+ const { assertTypescriptBackendResolvable } = await import("./src/backend-preflight.mjs");
6
9
  assertTypescriptBackendResolvable();
7
10
  const { run } = await import("./src/cli.mjs");
8
11
  await run({ input: process.stdin, output: process.stdout, args: process.argv.slice(2) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fallow-type-aware",
3
- "version": "3.16.0",
3
+ "version": "3.18.0",
4
4
  "description": "Optional TypeScript-Go semantic refinement sidecar for Fallow",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@codspeed/tinybench-plugin": "5.7.1",
35
- "tinybench": "6.1.2"
35
+ "tinybench": "6.1.2",
36
+ "zod": "4.4.3"
36
37
  }
37
38
  }
@@ -1,8 +1,8 @@
1
1
  // Generated from crates/api/type-aware-protocol.json. Do not edit.
2
2
  export const TYPE_AWARE_PROTOCOL = Object.freeze({
3
3
  schema_version: 1,
4
- wire_protocol_version: 6,
5
- semantic_schema_version: 2,
4
+ wire_protocol_version: 7,
5
+ semantic_schema_version: 3,
6
6
  analysis_operation: "semantic-queries",
7
7
  status_operation: "status",
8
8
  query_operations: ["symbol-use", "symbol-trace", "api-surface", "symbol-impact", "type-coupling"],
@@ -2,18 +2,107 @@ import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
 
5
+ import { SymbolFlags } from "typescript/unstable/sync";
6
+ import {
7
+ isExportDeclaration,
8
+ isImportDeclaration,
9
+ isNamedExports,
10
+ isNamedImports,
11
+ } from "typescript/unstable/ast/is";
12
+
5
13
  import { canonicalFileIdentity } from "./file-identity.mjs";
6
- import { relativePath } from "./semantic-identity.mjs";
14
+ import { projectSourceFiles, relativePath } from "./semantic-identity.mjs";
7
15
 
8
16
  const INFERRED_PROJECT = "<inferred>";
9
17
  const compareText = (left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right));
10
18
  const slash = (value) => value.split(path.sep).join("/");
11
19
 
12
- const blockingDiagnosticCount = (project) =>
13
- project.program.getConfigFileParsingDiagnostics().length +
14
- project.program.getProgramDiagnostics().length +
15
- project.program.getSyntacticDiagnostics().length +
16
- project.program.getBindDiagnostics().length;
20
+ const structuralDiagnostics = (project) => [
21
+ ...project.program.getConfigFileParsingDiagnostics(),
22
+ ...project.program.getProgramDiagnostics(),
23
+ ...project.program.getSyntacticDiagnostics(),
24
+ ...project.program.getBindDiagnostics(),
25
+ ];
26
+
27
+ const isProjectLocalDiagnostic = (project, diagnostic) => {
28
+ if (!diagnostic.fileName) return true;
29
+ const sourceFile = project.program.getSourceFile(diagnostic.fileName);
30
+ if (!sourceFile) return true;
31
+ return (
32
+ !project.program.isSourceFileDefaultLibrary(sourceFile) &&
33
+ !project.program.isSourceFileFromExternalLibrary(sourceFile)
34
+ );
35
+ };
36
+
37
+ const isSvelteSpecifier = (node) =>
38
+ typeof node.moduleSpecifier?.text === "string" && node.moduleSpecifier.text.endsWith(".svelte");
39
+
40
+ const isUnknownAlias = (checker, specifier) => {
41
+ const symbol = checker.getSymbolAtLocation(specifier.name);
42
+ if (!symbol) return true;
43
+ return checker.isUnknownSymbol(checker.getAliasedSymbol(symbol));
44
+ };
45
+
46
+ const concreteExportSymbol = (checker, symbol) => {
47
+ const target =
48
+ (symbol.flags & SymbolFlags.Alias) === 0 ? symbol : checker.getAliasedSymbol(symbol);
49
+ return !checker.isUnknownSymbol(target) && (target.declarations?.length ?? 0) > 0;
50
+ };
51
+
52
+ const hasProvableNamedExports = (project, declaration) => {
53
+ const moduleSymbol = project.checker.getSymbolAtLocation(declaration.moduleSpecifier);
54
+ if (!moduleSymbol) return false;
55
+ const hasConcreteModuleDeclaration = moduleSymbol.declarations?.some((moduleDeclaration) => {
56
+ const declarationPath =
57
+ moduleDeclaration.path ??
58
+ moduleDeclaration.fileName ??
59
+ moduleDeclaration.getSourceFile?.().fileName ??
60
+ "";
61
+ return declarationPath.endsWith(".d.svelte") || declarationPath.endsWith(".d.svelte.ts");
62
+ });
63
+ if (!hasConcreteModuleDeclaration) return false;
64
+ const namedExports = project.checker
65
+ .getExportsOfModule(moduleSymbol)
66
+ .filter((symbol) => symbol.name !== "default");
67
+ return namedExports.every((symbol) => concreteExportSymbol(project.checker, symbol));
68
+ };
69
+
70
+ const svelteDeclarationHasGap = (project, node) => {
71
+ if (!isSvelteSpecifier(node)) return false;
72
+ if (isImportDeclaration(node)) {
73
+ const bindings = node.importClause?.namedBindings;
74
+ return (
75
+ bindings !== undefined &&
76
+ isNamedImports(bindings) &&
77
+ bindings.elements.some((item) => isUnknownAlias(project.checker, item))
78
+ );
79
+ }
80
+ if (!isExportDeclaration(node)) return false;
81
+ if (!node.exportClause) return !hasProvableNamedExports(project, node);
82
+ if (!isNamedExports(node.exportClause)) return !hasProvableNamedExports(project, node);
83
+ return node.exportClause.elements.some((item) => isUnknownAlias(project.checker, item));
84
+ };
85
+
86
+ const sourceHasSvelteVirtualModuleGap = (project, sourceFile) => {
87
+ let gap = false;
88
+ const visit = (node) => {
89
+ if (gap) return;
90
+ gap = svelteDeclarationHasGap(project, node);
91
+ if (gap) return;
92
+ node.forEachChild((child) => {
93
+ visit(child);
94
+ return undefined;
95
+ });
96
+ };
97
+ visit(sourceFile);
98
+ return gap;
99
+ };
100
+
101
+ const hasSvelteVirtualModuleGap = (project) =>
102
+ projectSourceFiles(project).some(
103
+ (sourceFile) =>
104
+ sourceFile.text.includes(".svelte") && sourceHasSvelteVirtualModuleGap(project, sourceFile),
105
+ );
17
106
 
18
107
  const configPath = (root, project) => {
19
108
  const normalized = slash(project.configFileName);
@@ -173,15 +262,27 @@ const effectiveProjectConfigHash = (root, project) => {
173
262
  };
174
263
 
175
264
  export const projectState = (root, project, source) => {
176
- const diagnosticCount = blockingDiagnosticCount(project);
265
+ const diagnostics = structuralDiagnostics(project);
266
+ const localDiagnosticCount = diagnostics.filter((diagnostic) =>
267
+ isProjectLocalDiagnostic(project, diagnostic),
268
+ ).length;
269
+ const hasLocalDiagnostics = localDiagnosticCount > 0;
270
+ const svelteVirtualModuleGap = !hasLocalDiagnostics && hasSvelteVirtualModuleGap(project);
271
+ const reasonCode = hasLocalDiagnostics
272
+ ? "blocking-diagnostics"
273
+ : svelteVirtualModuleGap
274
+ ? "svelte-virtual-module-exports"
275
+ : diagnostics.length > 0
276
+ ? "blocking-diagnostics"
277
+ : null;
177
278
  return {
178
279
  project,
179
280
  config: configPath(root, project),
180
281
  effective_config_hash: effectiveProjectConfigHash(root, project),
181
282
  source,
182
- status: diagnosticCount === 0 ? "complete" : "unavailable",
183
- reason_code: diagnosticCount === 0 ? null : "blocking-diagnostics",
184
- blocking_diagnostic_count: diagnosticCount,
283
+ status: reasonCode === null ? "complete" : "unavailable",
284
+ reason_code: reasonCode,
285
+ blocking_diagnostic_count: reasonCode === "blocking-diagnostics" ? diagnostics.length : 0,
185
286
  source_file_count: project.program.getSourceFileNames().length,
186
287
  program_reused: false,
187
288
  candidate_count: 0,
@@ -237,9 +237,15 @@ export const symbolForDeclaration = (project, declaration) =>
237
237
 
238
238
  export const resolveAlias = (checker, symbol) => {
239
239
  if (!symbol) return undefined;
240
- if ((symbol.flags & SymbolFlags.Alias) === 0) return symbol;
241
- const aliased = checker.getAliasedSymbol(symbol);
242
- return checker.isUnknownSymbol(aliased) ? symbol : aliased;
240
+ const seen = new Set();
241
+ let current = symbol;
242
+ while ((current.flags & SymbolFlags.Alias) !== 0 && !seen.has(current)) {
243
+ seen.add(current);
244
+ const aliased = checker.getAliasedSymbol(current);
245
+ if (checker.isUnknownSymbol(aliased) || aliased === current) return current;
246
+ current = aliased;
247
+ }
248
+ return current;
243
249
  };
244
250
 
245
251
  export const declarationsForSymbol = (project, symbol) =>
package/src/semantic.mjs CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  isImportTypeNode,
21
21
  isNamespaceImport,
22
22
  isPropertyAccessExpression,
23
+ isQualifiedName,
23
24
  isPrivateIdentifier,
24
25
  isSetAccessorDeclaration,
25
26
  isStringLiteralLikeNode,
@@ -157,6 +158,10 @@ const REASON_ACTIONS = new Map([
157
158
  "blocking-diagnostics",
158
159
  "Repair structural TypeScript diagnostics in every selected project and retry.",
159
160
  ],
161
+ [
162
+ "svelte-virtual-module-exports",
163
+ "Run svelte-check for framework diagnostics. See https://docs.fallow.tools/analysis/type-aware#svelte-virtual-module-exports for supported Svelte project setup.",
164
+ ],
160
165
  [
161
166
  "unknown-entry-point",
162
167
  "Refresh the package entry points or pass project-relative source entry points.",
@@ -166,21 +171,22 @@ const DEFAULT_REASON_ACTION =
166
171
  "Narrow the query to a specific symbol, entry point, or healthy TypeScript project and retry.";
167
172
  const REASON_PRIORITY = new Map([
168
173
  ["blocking-diagnostics", 0],
169
- ["incomplete-project-coverage", 1],
170
- ["framework-contract-provenance", 2],
171
- ["ambiguous-project", 3],
172
- ["unknown-symbol", 4],
173
- ["unknown-entry-point", 5],
174
- ["decorated-declaration", 6],
175
- ["optional-contract", 7],
176
- ["accessor-pair", 8],
177
- ["overload-set", 9],
178
- ["attached-comment", 10],
179
- ["abstract-declaration", 11],
180
- ["dynamic-member-access", 12],
181
- ["virtual-dispatch", 13],
182
- ["dynamic-behavior", 14],
183
- ["evidence-limit", 15],
174
+ ["svelte-virtual-module-exports", 1],
175
+ ["incomplete-project-coverage", 2],
176
+ ["framework-contract-provenance", 3],
177
+ ["ambiguous-project", 4],
178
+ ["unknown-symbol", 5],
179
+ ["unknown-entry-point", 6],
180
+ ["decorated-declaration", 7],
181
+ ["optional-contract", 8],
182
+ ["accessor-pair", 9],
183
+ ["overload-set", 10],
184
+ ["attached-comment", 11],
185
+ ["abstract-declaration", 12],
186
+ ["dynamic-member-access", 13],
187
+ ["virtual-dispatch", 14],
188
+ ["dynamic-behavior", 15],
189
+ ["evidence-limit", 16],
184
190
  ]);
185
191
 
186
192
  const actionForReason = (reasonCode) => REASON_ACTIONS.get(reasonCode) ?? DEFAULT_REASON_ACTION;
@@ -201,6 +207,22 @@ const combineOmissions = (omissions) => {
201
207
  return [...counts].map(([reason_code, count]) => ({ reason_code, count }));
202
208
  };
203
209
 
210
+ const projectStateReason = (state) => state.reason_code ?? "blocking-diagnostics";
211
+
212
+ const unavailableProjectOmissions = (states) =>
213
+ combineOmissions(
214
+ states
215
+ .filter((state) => state.status !== "complete")
216
+ .map((state) => ({ reason_code: projectStateReason(state), count: 1 })),
217
+ );
218
+
219
+ const unavailableProjectReason = (states) => {
220
+ if (states.length === 0) return "no-project";
221
+ return (
222
+ unavailableProjectOmissions(states).toSorted(compareOmissions)[0]?.reason_code ?? "no-project"
223
+ );
224
+ };
225
+
204
226
  const resultStatus = (partial) => (partial ? "partial" : "complete");
205
227
  const resultReason = (omissions) => (omissions.length > 0 ? omissions[0].reason_code : null);
206
228
  const resultActions = (omissions) =>
@@ -410,30 +432,60 @@ const registerSymbolTargets = (root, state, entry) => {
410
432
  state.exportEntriesByProject.set(project, projectEntries);
411
433
  });
412
434
  }
413
- entry.resolved.ownerContexts.forEach(({ project, declaration }) => {
414
- const symbol = resolveAlias(project.checker, symbolForDeclaration(project, declaration));
415
- declarationsForSymbol(project, symbol).forEach((target) => {
416
- const namespaces =
417
- entry.query.symbol.declarationKind === "export"
418
- ? declarationNamespaces(target)
419
- : new Set([entry.query.symbol.namespace]);
420
- namespaces.forEach((namespace) => addSymbolTarget(root, state, entry, target, namespace));
421
- });
435
+ entry.resolved.ownerContexts.forEach(({ declaration, anchor }) => {
436
+ const namespaces =
437
+ entry.query.symbol.declarationKind === "export"
438
+ ? declarationNamespaces(declaration)
439
+ : new Set([entry.query.symbol.namespace]);
440
+ namespaces.forEach((namespace) => addSymbolTarget(root, state, entry, declaration, namespace));
441
+ if (entry.query.symbol.declarationKind === "export" && anchor) {
442
+ declarationNamespaces(anchor).forEach((namespace) =>
443
+ addSymbolTarget(root, state, entry, anchor, namespace),
444
+ );
445
+ }
422
446
  });
423
447
  state.contractRelationsByQuery.set(entry.query.id, entry.resolved.contractRelations);
424
448
  };
425
449
 
426
450
  const matchingSymbolEntries = (project, symbol, namespace, targetsByKey) => {
451
+ const declarations = declarationsForSymbol(project, symbol);
452
+ const matchingLane = declarations.filter((declaration) =>
453
+ declarationNamespaces(declaration).has(namespace),
454
+ );
455
+ if (matchingLane.length > 0) {
456
+ return new Set(
457
+ matchingLane.flatMap(
458
+ (declaration) => targetsByKey.get(stableDeclarationKey(declaration, namespace)) ?? [],
459
+ ),
460
+ );
461
+ }
427
462
  return new Set(
428
- declarationsForSymbol(project, symbol)
429
- .filter((declaration) => declarationNamespaces(declaration).has(namespace))
430
- .flatMap((declaration) => {
431
- const key = stableDeclarationKey(declaration, namespace);
432
- return targetsByKey.get(key) ?? [];
433
- }),
463
+ declarations.flatMap((declaration) =>
464
+ [...declarationNamespaces(declaration)].flatMap(
465
+ (declaredNamespace) =>
466
+ targetsByKey.get(stableDeclarationKey(declaration, declaredNamespace)) ?? [],
467
+ ),
468
+ ),
434
469
  );
435
470
  };
436
471
 
472
+ const matchingAliasEntries = (project, symbol, namespace, targetsByKey) => {
473
+ const entries = new Set();
474
+ const seen = new Set();
475
+ let current = symbol;
476
+ while (current && !seen.has(current)) {
477
+ seen.add(current);
478
+ matchingSymbolEntries(project, current, namespace, targetsByKey).forEach((entry) =>
479
+ entries.add(entry),
480
+ );
481
+ if ((current.flags & SymbolFlags.Alias) === 0) break;
482
+ const aliased = project.checker.getAliasedSymbol(current);
483
+ if (project.checker.isUnknownSymbol(aliased) || aliased === current) break;
484
+ current = aliased;
485
+ }
486
+ return entries;
487
+ };
488
+
437
489
  const isDeclarationReference = (root, state, entry, node) => {
438
490
  const locations = state.declarationLocationsByQuery.get(entry.query.id);
439
491
  if (locations.has(locationKey(location(root, node)))) return true;
@@ -467,13 +519,37 @@ const importTypeSpecifier = (node) => {
467
519
  const isDynamicImportCall = (node) =>
468
520
  isCallExpression(node) && node.expression.getText(node.getSourceFile()) === "import";
469
521
 
522
+ const MODULE_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
523
+
524
+ const lexicalModuleIdentityForText = (project, sourceFile, specifierText) => {
525
+ if (!specifierText?.startsWith(".")) return undefined;
526
+ const resolved = path.resolve(path.dirname(sourceFile.fileName), specifierText);
527
+ const extension = path.extname(resolved);
528
+ const stem = extension ? resolved.slice(0, -extension.length) : resolved;
529
+ const candidates = [
530
+ resolved,
531
+ ...MODULE_SOURCE_EXTENSIONS.map((candidate) => `${stem}${candidate}`),
532
+ ...MODULE_SOURCE_EXTENSIONS.map((candidate) => path.join(resolved, `index${candidate}`)),
533
+ ];
534
+ for (const candidate of candidates) {
535
+ const matched = project.program.getSourceFile(candidate);
536
+ if (matched) return sourceFileIdentity(matched);
537
+ if (existsSync(candidate)) return canonicalFileIdentity(candidate);
538
+ }
539
+ return undefined;
540
+ };
541
+
542
+ const lexicalModuleIdentity = (project, specifier) =>
543
+ specifier
544
+ ? lexicalModuleIdentityForText(project, specifier.getSourceFile(), specifier.text)
545
+ : undefined;
546
+
470
547
  const moduleIdentityForSpecifier = (project, specifier) => {
471
548
  if (!specifier) return undefined;
549
+ const lexicalIdentity = lexicalModuleIdentity(project, specifier);
550
+ if (lexicalIdentity) return lexicalIdentity;
472
551
  const moduleSymbol = project.checker.getSymbolAtLocation(specifier);
473
- const moduleDeclaration = declarationsForSymbol(
474
- project,
475
- resolveAlias(project.checker, moduleSymbol),
476
- )[0];
552
+ const moduleDeclaration = declarationsForSymbol(project, moduleSymbol)[0];
477
553
  return moduleDeclaration ? sourceFileIdentity(moduleDeclaration.getSourceFile()) : undefined;
478
554
  };
479
555
 
@@ -486,7 +562,9 @@ const namespaceImportDeclaration = (project, node) => {
486
562
  : undefined
487
563
  : isElementAccessExpression(parent) && parent.argumentExpression === node
488
564
  ? parent.expression
489
- : undefined;
565
+ : isQualifiedName(parent) && parent.right === node
566
+ ? parent.left
567
+ : undefined;
490
568
  if (!namespace) return undefined;
491
569
  if (!isIdentifier(namespace)) return undefined;
492
570
  const symbol = project.checker.getSymbolAtLocation(namespace);
@@ -495,9 +573,28 @@ const namespaceImportDeclaration = (project, node) => {
495
573
  .find(isNamespaceImport);
496
574
  };
497
575
 
576
+ const localAliasModuleEdgeDeclaration = (project, node) => {
577
+ const symbol = project.checker.getSymbolAtLocation(node);
578
+ return symbol?.declarations
579
+ ?.map((declaration) => moduleEdgeDeclaration(declaration.resolve(project)))
580
+ .find(Boolean);
581
+ };
582
+
498
583
  const referencedModuleIdentity = (project, node) => {
584
+ const importType = ancestorImportType(node);
585
+ const lexicalImportType = importType
586
+ ?.getText(importType.getSourceFile())
587
+ .match(/import\s*\(\s*["']([^"']+)["']/u)?.[1];
588
+ const lexicalIdentity = lexicalModuleIdentityForText(
589
+ project,
590
+ node.getSourceFile(),
591
+ lexicalImportType,
592
+ );
593
+ if (lexicalIdentity) return lexicalIdentity;
499
594
  const declaration =
500
- moduleEdgeDeclaration(node) ?? moduleEdgeDeclaration(namespaceImportDeclaration(project, node));
595
+ moduleEdgeDeclaration(node) ??
596
+ moduleEdgeDeclaration(namespaceImportDeclaration(project, node)) ??
597
+ localAliasModuleEdgeDeclaration(project, node);
501
598
  const declarationSpecifier = declaration?.moduleSpecifier;
502
599
  const specifier =
503
600
  declarationSpecifier && isStringLiteralLikeNode(declarationSpecifier)
@@ -506,13 +603,22 @@ const referencedModuleIdentity = (project, node) => {
506
603
  return moduleIdentityForSpecifier(project, specifier);
507
604
  };
508
605
 
509
- const isExactExportReference = (project, entry, node) =>
510
- entry.query.symbol.declarationKind !== "export" ||
511
- referencedModuleIdentity(project, node) ===
512
- canonicalFileIdentity(entry.query.symbol.absolutePath);
606
+ const isExactExportReference = (project, entry, node) => {
607
+ if (entry.query.symbol.declarationKind !== "export") return true;
608
+ const referencedModule = referencedModuleIdentity(project, node);
609
+ if (!referencedModule) return false;
610
+ const queryModule = canonicalFileIdentity(entry.query.symbol.absolutePath);
611
+ return (
612
+ referencedModule === queryModule ||
613
+ sourceFileIdentity(entry.resolved.declaration.getSourceFile()) === queryModule
614
+ );
615
+ };
513
616
 
514
617
  const recordSymbolUse = (root, state, project, entry, node, namespace) => {
515
618
  if (isDeclarationReference(root, state, entry, node)) return;
619
+ if (isDeclaration(node.parent) && node.parent.name === node) return;
620
+ const moduleEdge = moduleEdgeDeclaration(node);
621
+ if (moduleEdge) return;
516
622
  if (!isExactExportReference(project, entry, node)) return;
517
623
  const evidence = state.evidenceByQuery.get(entry.query.id);
518
624
  state.totalByQuery.set(entry.query.id, state.totalByQuery.get(entry.query.id) + 1);
@@ -614,6 +720,26 @@ const referenceNamespaces = (project, node, symbol) => {
614
720
  return namespaces.size > 0 ? [...namespaces] : [isTypePosition(node) ? "type" : "value"];
615
721
  };
616
722
 
723
+ const surfaceEntriesForNode = (state, project, node, namespace) => {
724
+ if (moduleEdgeDeclaration(node)) return new Set();
725
+ const moduleIdentity = referencedModuleIdentity(project, node);
726
+ if (!moduleIdentity) return new Set();
727
+ const direct = [...(state.exportEntriesByModule.get(moduleIdentity) ?? [])].filter(
728
+ (entry) =>
729
+ entry.query.symbol.exportedName === node.text && entry.query.symbol.namespace === namespace,
730
+ );
731
+ const entries = new Set(direct);
732
+ for (const entry of direct) {
733
+ for (const { declaration } of entry.resolved.ownerContexts) {
734
+ const symbol = resolveAlias(project.checker, symbolForDeclaration(project, declaration));
735
+ matchingSymbolEntries(project, symbol, namespace, state.targetsByKey).forEach((related) =>
736
+ entries.add(related),
737
+ );
738
+ }
739
+ }
740
+ return entries;
741
+ };
742
+
617
743
  const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImports) => {
618
744
  const projectEntries = classMemberEntrySetForProject(state, project);
619
745
  const nodes = [];
@@ -623,7 +749,7 @@ const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImpor
623
749
  recordStringDispatchedMemberAccess(state, projectEntries, node);
624
750
  recordComputedMemberAccess(state, project, projectEntries, node);
625
751
  }
626
- if (isIdentifierReference(node, state.candidateNames, includeDefaultImports)) {
752
+ if (isIdentifierReference(node, undefined, includeDefaultImports)) {
627
753
  nodes.push(node);
628
754
  return;
629
755
  }
@@ -631,9 +757,13 @@ const scanSymbolUseFile = (root, state, project, sourceFile, includeDefaultImpor
631
757
  });
632
758
  const symbols = project.checker.getSymbolAtLocation(nodes);
633
759
  nodes.forEach((node, index) => {
634
- const symbol = resolveAlias(project.checker, symbols[index]);
760
+ const rawSymbol = symbols[index];
761
+ const symbol = resolveAlias(project.checker, rawSymbol);
635
762
  referenceNamespaces(project, node, symbol).forEach((namespace) => {
636
- const entries = matchingSymbolEntries(project, symbol, namespace, state.targetsByKey);
763
+ const entries = new Set([
764
+ ...matchingAliasEntries(project, rawSymbol, namespace, state.targetsByKey),
765
+ ...surfaceEntriesForNode(state, project, node, namespace),
766
+ ]);
637
767
  entries.forEach((entry) => recordSymbolUse(root, state, project, entry, node, namespace));
638
768
  });
639
769
  });
@@ -681,8 +811,6 @@ const symbolResolutionError = (query, reasonCode, action) => ({
681
811
  });
682
812
 
683
813
  const isCompleteProjectState = (state) => state?.status === "complete";
684
- const selectedProjectName = (state) => state?.config ?? "the selected project";
685
-
686
814
  const owningProjectContexts = (statesByProject, absolutePath) =>
687
815
  [...statesByProject.entries()]
688
816
  .filter(([project]) => project.program.getSourceFile(absolutePath))
@@ -722,13 +850,16 @@ const selectSymbolContext = (
722
850
  }
723
851
  const completeOwners = owners.filter(({ state }) => isCompleteProjectState(state));
724
852
  if (completeOwners.length === 0) {
725
- return {
726
- ...owners[0],
727
- ...symbolResolutionError(
728
- query,
729
- "blocking-diagnostics",
730
- `Repair structural diagnostics in ${selectedProjectName(owners[0].state)} and retry.`,
853
+ const selectedOwner = [...owners].toSorted((left, right) =>
854
+ compareOmissions(
855
+ { reason_code: projectStateReason(left.state) },
856
+ { reason_code: projectStateReason(right.state) },
731
857
  ),
858
+ )[0];
859
+ const reasonCode = projectStateReason(selectedOwner.state);
860
+ return {
861
+ ...selectedOwner,
862
+ ...symbolResolutionError(query, reasonCode, actionForReason(reasonCode)),
732
863
  };
733
864
  }
734
865
  return { owners, completeOwners };
@@ -934,7 +1065,7 @@ const resolvedOwnerContext = (query, owner) => {
934
1065
  const anchor = symbolQueryAnchor(owner.project, query);
935
1066
  const target = resolvedAnchorTarget(owner.project, anchor, query.symbol);
936
1067
  return symbolQueryResolved(owner.project, query.symbol, anchor, target)
937
- ? { ...owner, ...target }
1068
+ ? { ...owner, anchor, ...target }
938
1069
  : undefined;
939
1070
  };
940
1071
 
@@ -1377,6 +1508,13 @@ const signatureTypes = (project, type, anchor) =>
1377
1508
  );
1378
1509
 
1379
1510
  const checkerTypeChildren = (project, type, anchor, hasNamedDeclaration) => {
1511
+ if (type.isTypeParameter()) {
1512
+ const constraint = safeCheckerValue(
1513
+ () => project.checker.getConstraintOfTypeParameter(type),
1514
+ undefined,
1515
+ );
1516
+ return constraint ? [constraint] : [];
1517
+ }
1380
1518
  const structural = [
1381
1519
  ...(type.getTypes() ?? []),
1382
1520
  ...safeCheckerValue(() => type.getAliasTypeArguments(), []),
@@ -1547,12 +1685,9 @@ const graphProjects = (snapshot, explicitProjects) =>
1547
1685
  const readyProjectStates = (query, states) => {
1548
1686
  const ready = states.filter((state) => state.status === "complete");
1549
1687
  if (ready.length > 0) return { ready };
1688
+ const reasonCode = unavailableProjectReason(states);
1550
1689
  return {
1551
- error: unavailable(
1552
- query,
1553
- states.length === 0 ? "no-project" : "blocking-diagnostics",
1554
- "Pass a healthy tsconfig containing the package entry points and retry.",
1555
- ),
1690
+ error: unavailable(query, reasonCode, actionForReason(reasonCode)),
1556
1691
  };
1557
1692
  };
1558
1693
 
@@ -1684,7 +1819,7 @@ const analyzeApiSurface = (root, query, states, evidenceLimit) => {
1684
1819
  reason_code: "evidence-limit",
1685
1820
  count: omissionCount,
1686
1821
  },
1687
- { reason_code: "blocking-diagnostics", count: unavailableProjectCount },
1822
+ ...unavailableProjectOmissions(states),
1688
1823
  { reason_code: "unknown-entry-point", count: missingEntryPointCount },
1689
1824
  ],
1690
1825
  });
@@ -1763,6 +1898,7 @@ const analyzeGraphSemanticQuery = (root, query, graphStates, evidenceLimit) => {
1763
1898
  missingEntryPoints,
1764
1899
  publicApiGraph,
1765
1900
  readyProjectStates,
1901
+ unavailableProjectOmissions,
1766
1902
  uniqueSorted,
1767
1903
  },
1768
1904
  );
@@ -1842,6 +1978,7 @@ const recordAbstainedProjectOutcome = (state, result) => {
1842
1978
  "no-project",
1843
1979
  "ambiguous-project",
1844
1980
  "blocking-diagnostics",
1981
+ "svelte-virtual-module-exports",
1845
1982
  "unknown-symbol",
1846
1983
  "incomplete-project-coverage",
1847
1984
  ]);
@@ -118,7 +118,6 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
118
118
  const highCouplingThreshold = percentile(degrees, 0.9);
119
119
  const topContributors = topCouplingContributors(perFile);
120
120
  const cycles = query.includeCycles ? findCycles(edges) : [];
121
- const unavailableProjectCount = states.length - readiness.ready.length;
122
121
  const missingEntryPointCount = services.missingEntryPoints(query, resolvedEntryPoints);
123
122
  const nestedFileOmissionCount = nestedCouplingOmissionCount(perFile, evidenceLimit);
124
123
  const boundFile = (entry) => boundCoupledFile(entry, evidenceLimit);
@@ -159,7 +158,7 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
159
158
  Math.max(0, cycles.length - evidenceLimit) +
160
159
  nestedFileOmissionCount,
161
160
  },
162
- { reason_code: "blocking-diagnostics", count: unavailableProjectCount },
161
+ ...services.unavailableProjectOmissions(states),
163
162
  { reason_code: "unknown-entry-point", count: missingEntryPointCount },
164
163
  ],
165
164
  });
@@ -0,0 +1,25 @@
1
+ import childProcess from "node:child_process";
2
+ import { syncBuiltinESMExports } from "node:module";
3
+
4
+ const INSTALL_MARKER = Symbol.for("fallow.type-aware.windows-child-process-policy");
5
+
6
+ const hiddenOptions = (options) => ({ ...options, windowsHide: true });
7
+
8
+ /** Keep TypeScript-Go child processes hidden when the sidecar runs on Windows. */
9
+ export const installWindowsChildProcessPolicy = ({
10
+ childProcess: processApi = childProcess,
11
+ platform = process.platform,
12
+ syncBuiltinESMExports: syncExports = syncBuiltinESMExports,
13
+ } = {}) => {
14
+ if (platform !== "win32" || processApi[INSTALL_MARKER] === true) return;
15
+
16
+ const originalSpawn = processApi.spawn;
17
+ processApi.spawn = function spawnHidden(command, args, options) {
18
+ if (Array.isArray(args)) {
19
+ return originalSpawn.call(this, command, args, hiddenOptions(options));
20
+ }
21
+ return originalSpawn.call(this, command, hiddenOptions(args));
22
+ };
23
+ Object.defineProperty(processApi, INSTALL_MARKER, { value: true });
24
+ syncExports();
25
+ };