gitnexus 1.6.13-rc.40 → 1.6.13-rc.41

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.
Files changed (28) hide show
  1. package/dist/core/graph/edge-reasons.d.ts +12 -0
  2. package/dist/core/graph/edge-reasons.js +12 -0
  3. package/dist/core/ingestion/import-resolvers/configs/swift.d.ts +4 -3
  4. package/dist/core/ingestion/import-resolvers/configs/swift.js +4 -3
  5. package/dist/core/ingestion/language-config.d.ts +97 -4
  6. package/dist/core/ingestion/language-config.js +186 -32
  7. package/dist/core/ingestion/languages/swift/implicit-imports.d.ts +27 -29
  8. package/dist/core/ingestion/languages/swift/implicit-imports.js +63 -47
  9. package/dist/core/ingestion/languages/swift/import-target.d.ts +12 -4
  10. package/dist/core/ingestion/languages/swift/import-target.js +59 -6
  11. package/dist/core/ingestion/languages/swift/index.d.ts +1 -1
  12. package/dist/core/ingestion/languages/swift/index.js +1 -1
  13. package/dist/core/ingestion/languages/swift/name-fallback-visibility.d.ts +8 -4
  14. package/dist/core/ingestion/languages/swift/name-fallback-visibility.js +35 -5
  15. package/dist/core/ingestion/languages/swift/scope-resolver.d.ts +9 -10
  16. package/dist/core/ingestion/languages/swift/scope-resolver.js +20 -20
  17. package/dist/core/ingestion/languages/swift/sibling-type-bindings.d.ts +12 -13
  18. package/dist/core/ingestion/languages/swift/sibling-type-bindings.js +31 -38
  19. package/dist/core/ingestion/languages/swift/target-grouping.d.ts +56 -37
  20. package/dist/core/ingestion/languages/swift/target-grouping.js +186 -68
  21. package/dist/core/ingestion/languages/swift/target-siblings.d.ts +22 -20
  22. package/dist/core/ingestion/languages/swift/target-siblings.js +66 -40
  23. package/dist/core/ingestion/languages/swift/workspace-config.d.ts +20 -0
  24. package/dist/core/ingestion/languages/swift/workspace-config.js +174 -0
  25. package/dist/core/ingestion/languages/swift/xcode-project.d.ts +52 -0
  26. package/dist/core/ingestion/languages/swift/xcode-project.js +326 -0
  27. package/dist/core/lbug/lbug-adapter.js +61 -35
  28. package/package.json +1 -1
@@ -24,3 +24,15 @@
24
24
  export declare const GLOBAL_NAME_FALLBACK_REASON = "global-name-fallback";
25
25
  /** True when this edge's target was guessed by name rather than resolved. */
26
26
  export declare const isHeuristicEdgeReason: (reason: string) => boolean;
27
+ /**
28
+ * An IMPORTS edge from a File to the `Module` node of the compiler module it
29
+ * belongs to, for languages where every file of a module sees every other
30
+ * file's declarations with no `import` (whole-module visibility).
31
+ *
32
+ * One edge per member file instead of one per ordered file pair: the pairwise
33
+ * form is n·(n−1) edges and exhausted V8's Map limit on large modules (#3355).
34
+ * Consumers that need "files that see this file" follow the hub: two files are
35
+ * co-members when both have a membership edge to the same `Module` node
36
+ * (`queryImportersBatch`).
37
+ */
38
+ export declare const MODULE_MEMBERSHIP_REASON = "module-membership";
@@ -32,3 +32,15 @@ export const GLOBAL_NAME_FALLBACK_REASON = 'global-name-fallback';
32
32
  const HEURISTIC_EDGE_REASONS = new Set([GLOBAL_NAME_FALLBACK_REASON]);
33
33
  /** True when this edge's target was guessed by name rather than resolved. */
34
34
  export const isHeuristicEdgeReason = (reason) => HEURISTIC_EDGE_REASONS.has(reason);
35
+ /**
36
+ * An IMPORTS edge from a File to the `Module` node of the compiler module it
37
+ * belongs to, for languages where every file of a module sees every other
38
+ * file's declarations with no `import` (whole-module visibility).
39
+ *
40
+ * One edge per member file instead of one per ordered file pair: the pairwise
41
+ * form is n·(n−1) edges and exhausted V8's Map limit on large modules (#3355).
42
+ * Consumers that need "files that see this file" follow the hub: two files are
43
+ * co-members when both have a membership edge to the same `Module` node
44
+ * (`queryImportersBatch`).
45
+ */
46
+ export const MODULE_MEMBERSHIP_REASON = 'module-membership';
@@ -22,9 +22,10 @@
22
22
  * `allFileList` entries, and the per-target file ORDER follows
23
23
  * `allFileList`.
24
24
  *
25
- * Import-config fans a file to every matching declared target. Grouping
26
- * (`groupSwiftFilesBySpmTarget`) is first-target-wins. That divergence
27
- * is intentional.
25
+ * Import-config fans a file to every matching declared target. Module
26
+ * grouping (`groupSwiftFilesByModule`) anchors target paths at the repo root
27
+ * and picks the deepest. That divergence is intentional: this legacy
28
+ * strategy only sees the root declaration map.
28
29
  */
29
30
  import type { ImportResolutionConfig, ImportResolverStrategy } from '../types.js';
30
31
  /** Swift Package.swift target map resolution strategy. */
@@ -22,9 +22,10 @@
22
22
  * `allFileList` entries, and the per-target file ORDER follows
23
23
  * `allFileList`.
24
24
  *
25
- * Import-config fans a file to every matching declared target. Grouping
26
- * (`groupSwiftFilesBySpmTarget`) is first-target-wins. That divergence
27
- * is intentional.
25
+ * Import-config fans a file to every matching declared target. Module
26
+ * grouping (`groupSwiftFilesByModule`) anchors target paths at the repo root
27
+ * and picks the deepest. That divergence is intentional: this legacy
28
+ * strategy only sees the root declaration map.
28
29
  */
29
30
  import { SupportedLanguages } from '../../../../_shared/index.js';
30
31
  import { coerceDeclaredSwiftTargets, swiftDeclaredTargetPrefix } from '../../language-config.js';
@@ -86,6 +86,43 @@ export interface SwiftPackageConfig {
86
86
  * binary-only Package.swift does not collapse every file into `__default__`.
87
87
  */
88
88
  declaredTargets?: Map<string, string>;
89
+ /**
90
+ * Every Swift module the workspace loader found (root and nested SwiftPM
91
+ * targets, Xcode native targets). When present it is the authority for
92
+ * module membership and `import` resolution; `targets` stays for callers
93
+ * that only know the SwiftPM map.
94
+ */
95
+ modules?: readonly SwiftModuleSpec[];
96
+ /**
97
+ * True when every manifest and Xcode project was read completely and no
98
+ * module was inferred from folder names, so a name missing from `modules`
99
+ * is an external module (SDK or dependency), not an unread local one.
100
+ */
101
+ moduleNamesComplete?: boolean;
102
+ /** `sources:` / `exclude:` per target name, relative to the target directory. */
103
+ targetFilters?: Map<string, SwiftTargetFilter>;
104
+ }
105
+ /** One Swift module (compiler unit) found in the workspace. */
106
+ export interface SwiftModuleSpec {
107
+ /** Stable key: a repo-relative SwiftPM target directory, or `xcode:<project>:<target>`. */
108
+ readonly key: string;
109
+ /** Module name as written in `import X`. */
110
+ readonly name: string;
111
+ /** SwiftPM: repo-relative target directory ('' is the repo root). */
112
+ readonly dir?: string;
113
+ /** Xcode: repo-relative member files. */
114
+ readonly files?: readonly string[];
115
+ /** Xcode: repo-relative synchronized folders; files below are members. */
116
+ readonly folders?: readonly string[];
117
+ /**
118
+ * Repo-relative paths this module leaves out: Xcode synchronized-folder
119
+ * exceptions, SwiftPM `exclude:`.
120
+ */
121
+ readonly excluded?: readonly string[];
122
+ /** SwiftPM `sources:`: repo-relative files or directories; members must be under one. */
123
+ readonly sources?: readonly string[];
124
+ /** False for SwiftPM plugins: modules, but never `import`-able. */
125
+ readonly importable: boolean;
89
126
  }
90
127
  /**
91
128
  * Declaration view for explicit import resolve. `origin: 'directories'`
@@ -212,12 +249,36 @@ export declare function loadComposerConfig(repoRoot: string): Promise<ComposerCo
212
249
  * windows (see below); directories are still visited breadth-first.
213
250
  */
214
251
  export declare function scanCSharpProject(repoRoot: string): Promise<CSharpProjectScan>;
215
- /** Heuristic Package.swift scan. Never shells out to `swift package dump-package`. */
216
- export declare function parseSwiftPackageManifest(source: string): {
252
+ /** Where SwiftPM looks for a target that declares no `path:`. */
253
+ export type SwiftTargetDirKind = 'source' | 'test' | 'plugin';
254
+ export interface SwiftManifestParse {
255
+ /** Target name -> package-relative directory. */
217
256
  targets: Map<string, string>;
257
+ /**
258
+ * Targets with no `path:`. Their directory above is the conventional
259
+ * default; `loadSwiftPackageConfig` replaces it with the directory SwiftPM
260
+ * would actually pick (`Sources`, `Source`, `src`, or `srcs`).
261
+ */
262
+ implicitDirs: Map<string, SwiftTargetDirKind>;
263
+ /** Plugin targets: modules for grouping, never `import`-able. */
264
+ plugins: Set<string>;
265
+ /** `sources:` / `exclude:` per target name, relative to the target directory. */
266
+ filters: Map<string, SwiftTargetFilter>;
218
267
  complete: boolean;
219
- };
220
- export declare function loadSwiftPackageConfig(repoRoot: string): Promise<SwiftPackageConfig | null>;
268
+ }
269
+ /** A target's `sources:` / `exclude:` lists; absent means "no filter". */
270
+ export interface SwiftTargetFilter {
271
+ readonly sources?: readonly string[];
272
+ readonly exclude?: readonly string[];
273
+ }
274
+ /** Heuristic Package.swift scan. Never shells out to `swift package dump-package`. */
275
+ export declare function parseSwiftPackageManifest(source: string): SwiftManifestParse;
276
+ /**
277
+ * Load the SwiftPM config of the package at `packageDir` (repo root when
278
+ * omitted). Target directories stay package-relative; see
279
+ * {@link loadSwiftWorkspaceConfig} for the repo-relative merge.
280
+ */
281
+ export declare function loadSwiftPackageConfig(repoRoot: string, packageDir?: string): Promise<SwiftPackageConfig | null>;
221
282
  /**
222
283
  * Load the Zig build configuration a repo's `build.zig.zon` + root `build.zig`
223
284
  * declare: `.path` deps (and the roots their own build.zig names) from the
@@ -298,6 +359,38 @@ export declare function zigPackageFor(index: ZigWorkspaceIndex | null | undefine
298
359
  * `loadTsconfigPaths` and the repo-walking `loadTsconfigIndex`.
299
360
  */
300
361
  export declare function loadZigWorkspaceIndex(repoRoot: string): Promise<ZigWorkspaceIndex | null>;
362
+ /**
363
+ * Normalize a `.path` value from build.zig.zon into a repo-relative form.
364
+ * Returns null for paths that escape the repo root (start with `..`) or
365
+ * are absolute — those point to files we don't index. `.` / `./` normalize
366
+ * to the empty string (the repo root itself). Shared with the import
367
+ * resolver so both sides agree on which deps are in-repo.
368
+ */
369
+ /**
370
+ * Does this `.path` value point outside the repository BY ITS SPELLING —
371
+ * POSIX absolute (`/dep`), Windows drive-qualified (`C:\dep`, `C:/dep`),
372
+ * root-relative (`\dep`) or UNC (`\\server\share`)?
373
+ *
374
+ * Separators are normalized first so every Windows spelling is visible to the
375
+ * one test. Exported-in-spirit rather than inlined because it must be asked in
376
+ * TWO places and the two must not drift: `normalizeZigDepPath` asks it of the
377
+ * value it is given, and `loadZigBuildConfig` asks it of a NESTED package's
378
+ * value BEFORE prefixing the package directory. That second call is the whole
379
+ * point — prefixing turns `/dep` into `packages/app//dep`, which is relative by
380
+ * inspection, so the check inside `normalizeZigDepPath` no longer sees an
381
+ * absolute path and the empty segment is simply dropped, mapping an
382
+ * out-of-repo dependency onto a real in-repo directory if one happens to exist.
383
+ *
384
+ * `path.posix.join` is NOT a substitute: it strips the leading slash too
385
+ * (`join('packages/app/', '/dep')` is `packages/app/dep`), so it produces the
386
+ * same fabricated path without ever rejecting anything.
387
+ *
388
+ * A `..` prefix is deliberately NOT handled here. `../core` escapes the
389
+ * package but not necessarily the repo, and rebasing it is exactly what the
390
+ * nested-package branch exists to do; `normalizeZigDepPath` rejects the ones
391
+ * that still escape the ROOT after rebasing.
392
+ */
393
+ export declare function isAbsoluteZigDepPath(depPath: string): boolean;
301
394
  export declare function normalizeZigDepPath(depPath: string): string | null;
302
395
  /**
303
396
  * The `root_source_file` paths a `build.zig` declares, dep-relative, with the
@@ -439,8 +439,15 @@ async function collectDeclaredNamespaces(filePath, declaredNamespaces, rootNames
439
439
  // import whose namespace was dropped.
440
440
  return structure.incomplete ? 'truncated' : 'ok';
441
441
  }
442
- const SWIFT_SOURCE_FACTORY_NAMES = ['target', 'executableTarget', 'testTarget', 'macro'];
443
- const SWIFT_SKIP_FACTORY_NAMES = ['binaryTarget', 'plugin', 'systemLibrary'];
442
+ const SWIFT_SOURCE_FACTORY_NAMES = [
443
+ 'target',
444
+ 'executableTarget',
445
+ 'testTarget',
446
+ 'macro',
447
+ 'plugin',
448
+ ];
449
+ /** No Swift sources: a prebuilt artifact or a C module map. */
450
+ const SWIFT_SKIP_FACTORY_NAMES = ['binaryTarget', 'systemLibrary'];
444
451
  const SWIFT_SKIP_FACTORIES = new Set(SWIFT_SKIP_FACTORY_NAMES);
445
452
  const SWIFT_FACTORY_RE = new RegExp(`\\.(${[...SWIFT_SOURCE_FACTORY_NAMES, ...SWIFT_SKIP_FACTORY_NAMES].join('|')})\\s*\\(`, 'g');
446
453
  function extractBalancedParen(source, openIndex) {
@@ -550,7 +557,8 @@ function skipSwiftWsAndComments(source, start) {
550
557
  return null;
551
558
  }
552
559
  /** First `name:` / `path:` string outside comments. Escapes and interpolations are unreadable. */
553
- function readSwiftFactoryField(block, field) {
560
+ /** Where `field:`'s value starts in a factory block, or null when absent. */
561
+ function findSwiftFactoryFieldValue(block, field) {
554
562
  let inString = null;
555
563
  let escape = false;
556
564
  let inLineComment = false;
@@ -615,18 +623,56 @@ function readSwiftFactoryField(block, field) {
615
623
  i = j - 1;
616
624
  continue;
617
625
  }
618
- const valueAt = skipSwiftWsAndComments(block, colonAt + 1);
619
- if (valueAt === null)
620
- return { value: undefined, keyPresent: true };
621
- const quote = block[valueAt];
622
- if (quote !== '"' && quote !== "'")
623
- return { value: undefined, keyPresent: true };
624
- const parsed = readSwiftSimpleQuotedString(block, valueAt);
625
- if (parsed === null)
626
- return { value: undefined, keyPresent: true };
627
- return { value: parsed, keyPresent: true };
626
+ return { valueAt: skipSwiftWsAndComments(block, colonAt + 1) };
627
+ }
628
+ return null;
629
+ }
630
+ function readSwiftFactoryField(block, field) {
631
+ const found = findSwiftFactoryFieldValue(block, field);
632
+ if (found === null)
633
+ return { value: undefined, keyPresent: false };
634
+ const { valueAt } = found;
635
+ if (valueAt === null)
636
+ return { value: undefined, keyPresent: true };
637
+ const quote = block[valueAt];
638
+ if (quote !== '"' && quote !== "'")
639
+ return { value: undefined, keyPresent: true };
640
+ const parsed = readSwiftSimpleQuotedString(block, valueAt);
641
+ return { value: parsed ?? undefined, keyPresent: true };
642
+ }
643
+ /**
644
+ * `sources:` / `exclude:` as a list of string literals. `undefined` when the
645
+ * key is absent; `null` when present but not a plain literal list.
646
+ */
647
+ function readSwiftFactoryStringList(block, field) {
648
+ const found = findSwiftFactoryFieldValue(block, field);
649
+ if (found === null)
650
+ return undefined;
651
+ let i = found.valueAt;
652
+ if (i === null || block[i] !== '[')
653
+ return null;
654
+ const out = [];
655
+ for (;;) {
656
+ const at = skipSwiftWsAndComments(block, i + 1);
657
+ if (at === null)
658
+ return null;
659
+ if (block[at] === ']')
660
+ return out;
661
+ if (block[at] !== '"' && block[at] !== "'")
662
+ return null;
663
+ const value = readSwiftSimpleQuotedString(block, at);
664
+ if (value === null)
665
+ return null;
666
+ out.push(value);
667
+ const after = skipSwiftWsAndComments(block, at + value.length + 2);
668
+ if (after === null)
669
+ return null;
670
+ if (block[after] === ']')
671
+ return out;
672
+ if (block[after] !== ',')
673
+ return null;
674
+ i = after;
628
675
  }
629
- return { value: undefined, keyPresent: false };
630
676
  }
631
677
  /** Quoted literal with no escapes. Any `\` (including `\u{…}` and `\(`) is unreadable. */
632
678
  function readSwiftSimpleQuotedString(source, openIndex) {
@@ -788,8 +834,11 @@ function swiftPathIsUnreadable(customPath, hasPathKey) {
788
834
  /** Heuristic Package.swift scan. Never shells out to `swift package dump-package`. */
789
835
  export function parseSwiftPackageManifest(source) {
790
836
  const targets = new Map();
837
+ const implicitDirs = new Map();
838
+ const plugins = new Set();
839
+ const filters = new Map();
791
840
  if (swiftManifestHasCompletenessHazard(source)) {
792
- return { targets, complete: false };
841
+ return { targets, implicitDirs, plugins, filters, complete: false };
793
842
  }
794
843
  const packageTargets = inspectSwiftPackageTargets(source);
795
844
  SWIFT_FACTORY_RE.lastIndex = 0;
@@ -832,22 +881,59 @@ export function parseSwiftPackageManifest(source) {
832
881
  sawUnreadableFactory = true;
833
882
  continue;
834
883
  }
835
- const dir = customPath ?? (kind === 'testTarget' ? `Tests/${name}` : `Sources/${name}`);
884
+ const sources = readSwiftFactoryStringList(block, 'sources');
885
+ const exclude = readSwiftFactoryStringList(block, 'exclude');
886
+ if (sources === null || exclude === null) {
887
+ sawUnreadableFactory = true;
888
+ continue;
889
+ }
890
+ if (sources !== undefined || exclude !== undefined) {
891
+ filters.set(name, {
892
+ ...(sources !== undefined ? { sources } : {}),
893
+ ...(exclude !== undefined ? { exclude } : {}),
894
+ });
895
+ }
896
+ const dirKind = kind === 'testTarget' ? 'test' : kind === 'plugin' ? 'plugin' : 'source';
897
+ if (dirKind === 'plugin')
898
+ plugins.add(name);
899
+ const dir = customPath ?? `${SWIFT_DEFAULT_TARGET_PARENT[dirKind]}/${name}`;
836
900
  const existing = targets.get(name);
837
901
  if (existing === undefined) {
838
902
  targets.set(name, dir);
903
+ if (customPath === undefined)
904
+ implicitDirs.set(name, dirKind);
839
905
  }
840
- else if (customPath !== undefined && existing === `Sources/${name}`) {
906
+ else if (customPath !== undefined && implicitDirs.has(name)) {
841
907
  // A later `.target(name:path:)` wins over an earlier same-name
842
908
  // factory that only implied the default path.
843
909
  targets.set(name, customPath);
910
+ implicitDirs.delete(name);
844
911
  }
845
912
  }
846
913
  return {
847
914
  targets,
915
+ implicitDirs,
916
+ plugins,
917
+ filters,
848
918
  complete: !sawUnreadableFactory && !packageTargets.helperBuilt,
849
919
  };
850
920
  }
921
+ /** Conventional parent of a target with no `path:`, before disk lookup. */
922
+ const SWIFT_DEFAULT_TARGET_PARENT = {
923
+ source: 'Sources',
924
+ test: 'Tests',
925
+ plugin: 'Plugins',
926
+ };
927
+ /**
928
+ * SwiftPM's predefined parents, in its search order (`PackageBuilder`):
929
+ * sources in `Sources`, `Source`, `src`, `srcs`; tests in `Tests` first, then
930
+ * the source parents; plugins only in `Plugins`.
931
+ */
932
+ const SWIFT_PREDEFINED_TARGET_PARENTS = {
933
+ source: ['Sources', 'Source', 'src', 'srcs'],
934
+ test: ['Tests', 'Sources', 'Source', 'src', 'srcs'],
935
+ plugin: ['Plugins'],
936
+ };
851
937
  const SWIFT_ALL_FACTORY_NAMES = new Set([
852
938
  ...SWIFT_SOURCE_FACTORY_NAMES,
853
939
  ...SWIFT_SKIP_FACTORY_NAMES,
@@ -1189,15 +1275,16 @@ function matchSwiftSquare(source, openIndex) {
1189
1275
  }
1190
1276
  return null;
1191
1277
  }
1192
- async function inferSwiftDirectoryTargets(repoRoot) {
1278
+ async function inferSwiftDirectoryTargets(repoRoot, packageDir = '') {
1193
1279
  const targets = new Map();
1194
- const sourceDirs = ['Sources', 'Package/Sources', 'src'];
1280
+ const sourceDirs = ['Sources', 'Source', 'Package/Sources', 'src', 'srcs'];
1195
1281
  for (const sourceDir of sourceDirs) {
1196
1282
  try {
1197
- const fullPath = path.join(repoRoot, sourceDir);
1283
+ const fullPath = path.join(repoRoot, packageDir, sourceDir);
1198
1284
  const entries = await fs.readdir(fullPath, { withFileTypes: true });
1199
1285
  for (const entry of entries) {
1200
- if (entry.isDirectory()) {
1286
+ // First parent in SwiftPM's search order wins a repeated name.
1287
+ if (entry.isDirectory() && !targets.has(entry.name)) {
1201
1288
  targets.set(entry.name, sourceDir + '/' + entry.name);
1202
1289
  }
1203
1290
  }
@@ -1208,33 +1295,46 @@ async function inferSwiftDirectoryTargets(repoRoot) {
1208
1295
  }
1209
1296
  return targets;
1210
1297
  }
1211
- export async function loadSwiftPackageConfig(repoRoot) {
1298
+ /**
1299
+ * Load the SwiftPM config of the package at `packageDir` (repo root when
1300
+ * omitted). Target directories stay package-relative; see
1301
+ * {@link loadSwiftWorkspaceConfig} for the repo-relative merge.
1302
+ */
1303
+ export async function loadSwiftPackageConfig(repoRoot, packageDir = '') {
1304
+ const pkgRoot = path.join(repoRoot, packageDir);
1212
1305
  try {
1213
- const source = await fs.readFile(path.join(repoRoot, 'Package.swift'), 'utf-8');
1306
+ const source = await fs.readFile(path.join(pkgRoot, await pickSwiftManifestFile(pkgRoot)), 'utf-8');
1214
1307
  const parsed = parseSwiftPackageManifest(source);
1215
1308
  if (parsed.complete) {
1216
1309
  if (isDev) {
1217
1310
  logger.info(`📦 Loaded ${parsed.targets.size} Swift package targets from Package.swift`);
1218
1311
  }
1312
+ const parents = new Map();
1313
+ for (const [name, kind] of parsed.implicitDirs) {
1314
+ if (!parents.has(kind))
1315
+ parents.set(kind, await findSwiftPredefinedParent(pkgRoot, kind));
1316
+ const parent = parents.get(kind);
1317
+ if (parent != null)
1318
+ parsed.targets.set(name, `${parent}/${name}`);
1319
+ }
1320
+ // Plugins are modules (grouping) but never `import`-able.
1321
+ const declaredTargets = new Map([...parsed.targets].filter(([name]) => !parsed.plugins.has(name)));
1219
1322
  if (parsed.targets.size > 0) {
1220
1323
  return {
1221
1324
  targets: parsed.targets,
1222
1325
  origin: 'package.swift',
1223
- declaredTargets: parsed.targets,
1326
+ declaredTargets,
1327
+ ...(parsed.filters.size > 0 ? { targetFilters: parsed.filters } : {}),
1224
1328
  };
1225
1329
  }
1226
- const inferred = await inferSwiftDirectoryTargets(repoRoot);
1227
- return {
1228
- targets: inferred,
1229
- origin: 'package.swift',
1230
- declaredTargets: parsed.targets,
1231
- };
1330
+ const inferred = await inferSwiftDirectoryTargets(repoRoot, packageDir);
1331
+ return { targets: inferred, origin: 'package.swift', declaredTargets };
1232
1332
  }
1233
1333
  }
1234
1334
  catch {
1235
1335
  // Missing or unreadable — fall through to inferred folders.
1236
1336
  }
1237
- const inferred = await inferSwiftDirectoryTargets(repoRoot);
1337
+ const inferred = await inferSwiftDirectoryTargets(repoRoot, packageDir);
1238
1338
  if (inferred.size > 0) {
1239
1339
  if (isDev) {
1240
1340
  logger.info(`📦 Inferred ${inferred.size} Swift source folders`);
@@ -1243,6 +1343,60 @@ export async function loadSwiftPackageConfig(repoRoot) {
1243
1343
  }
1244
1344
  return null;
1245
1345
  }
1346
+ const SWIFT_VERSIONED_MANIFEST_RE = /^Package@swift-(\d+)(?:\.(\d+))?(?:\.(\d+))?\.swift$/;
1347
+ /**
1348
+ * The manifest SwiftPM would read in `pkgRoot`. A `Package@swift-X.Y.swift`
1349
+ * overrides `Package.swift` for toolchains at or above X.Y; the toolchain is
1350
+ * unknown here, so assume the newest and take the highest version present.
1351
+ */
1352
+ async function pickSwiftManifestFile(pkgRoot) {
1353
+ let best = 'Package.swift';
1354
+ let bestVersion = [];
1355
+ let names;
1356
+ try {
1357
+ names = await fs.readdir(pkgRoot);
1358
+ }
1359
+ catch {
1360
+ return best;
1361
+ }
1362
+ for (const name of names) {
1363
+ const m = SWIFT_VERSIONED_MANIFEST_RE.exec(name);
1364
+ if (m === null)
1365
+ continue;
1366
+ const version = [Number(m[1]), Number(m[2] ?? 0), Number(m[3] ?? 0)];
1367
+ if (compareVersions(version, bestVersion) > 0) {
1368
+ best = name;
1369
+ bestVersion = version;
1370
+ }
1371
+ }
1372
+ return best;
1373
+ }
1374
+ function compareVersions(a, b) {
1375
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
1376
+ const d = (a[i] ?? -1) - (b[i] ?? -1);
1377
+ if (d !== 0)
1378
+ return d;
1379
+ }
1380
+ return 0;
1381
+ }
1382
+ /**
1383
+ * SwiftPM's parent directory for targets of `kind` in `pkgRoot`: the first
1384
+ * predefined parent that exists, chosen once per package (a target missing
1385
+ * from it is a manifest error in SwiftPM, not a fallthrough). Null when none
1386
+ * exists.
1387
+ */
1388
+ async function findSwiftPredefinedParent(pkgRoot, kind) {
1389
+ for (const parent of SWIFT_PREDEFINED_TARGET_PARENTS[kind]) {
1390
+ try {
1391
+ if ((await fs.stat(path.join(pkgRoot, parent))).isDirectory())
1392
+ return parent;
1393
+ }
1394
+ catch {
1395
+ // Not there — try the next predefined parent.
1396
+ }
1397
+ }
1398
+ return null;
1399
+ }
1246
1400
  /**
1247
1401
  * Load the Zig build configuration a repo's `build.zig.zon` + root `build.zig`
1248
1402
  * declare: `.path` deps (and the roots their own build.zig names) from the
@@ -1563,7 +1717,7 @@ async function findZigPackageDirs(repoRoot) {
1563
1717
  * nested-package branch exists to do; `normalizeZigDepPath` rejects the ones
1564
1718
  * that still escape the ROOT after rebasing.
1565
1719
  */
1566
- function isAbsoluteZigDepPath(depPath) {
1720
+ export function isAbsoluteZigDepPath(depPath) {
1567
1721
  const normalized = depPath.replace(/\\/g, '/');
1568
1722
  return normalized.startsWith('/') || /^[A-Za-z]:\//.test(normalized);
1569
1723
  }
@@ -1,37 +1,35 @@
1
1
  /**
2
- * Swift same-module implicit IMPORTS-edge emission for the
3
- * `emitImplicitImportEdges` hook.
2
+ * Swift same-module implicit IMPORTS for the `emitImplicitImportEdges` hook.
4
3
  *
5
- * Swift gives every file in a module (an SPM target) visibility of every
6
- * other file's top-level declarations WITHOUT any `import` statement
7
- * (whole-module visibility). The legacy DAG models this with File→File
8
- * IMPORTS edges via `wireSwiftImplicitImports`; under registry-primary
9
- * that wirer's `addImportEdge` is gated off, and the scope-resolution
10
- * import pipeline (`emitImportEdges`) only materializes edges from
11
- * finalized `ImportEdge`s — of which there are none here, because there
12
- * is no syntactic `import`. This hook emits the missing edges directly.
4
+ * Swift gives every file in a module visibility of every other file's
5
+ * top-level declarations WITHOUT any `import` statement (whole-module
6
+ * visibility). There is no syntactic `import`, so the finalized-ImportEdge
7
+ * pipeline (`emitImportEdges`) has nothing to materialize; this hook records
8
+ * module membership directly.
13
9
  *
14
- * Module identity: Swift has no in-source `package X` marker. Module
15
- * membership is the SPM target *subtree* (`Sources/<Target>/…`), threaded
16
- * in via the SPM target map (`resolutionConfig` → `coerceSwiftTargets`)
17
- * and grouped by `groupSwiftFilesBySpmTarget`. The helper preserves the
18
- * legacy `groupSwiftFilesByTarget` bucketing contract for ordinary layouts
19
- * while intentionally fixing #2931's repeated-prefix edge case. With no
20
- * target map (no Package.swift and no scanned `Sources/*`) the map is null
21
- * and all files form one `__default__` module (single-Xcode-project
22
- * assumption). An inferred folder map must keep sibling folders isolated.
23
- * Every pair of distinct `.swift`
24
- * files in the same module gets a directed IMPORTS edge in both directions
25
- * (whole-module visibility is symmetric).
10
+ * Representation: one `Module` node per Swift module with at least two member
11
+ * files, and one File → Module IMPORTS edge per member, reason
12
+ * `MODULE_MEMBERSHIP_REASON`. This is how the compiler sees it — a file
13
+ * imports its own module — and it is linear. The earlier File → File form
14
+ * wrote n·(n−1) edges per module and exhausted V8's 2^24 Map limit on a 5,200
15
+ * file module (#3355). "Files that see file B" is now "files with a
16
+ * membership edge to a Module that B also has one to"; incremental importer
17
+ * expansion follows that rule (`queryImportersBatch`).
26
18
  *
27
- * Node identity + edge construction mirror the generic `emitImportEdges`
28
- * convention (`graph-bridge/imports-to-edges.ts`): `generateId('File', path)`
29
- * for endpoints and `generateId('IMPORTS', key)` for the relationship id,
30
- * deduped by `(sourceFile -> targetFile)`. Re-invocation idempotency comes
31
- * from `graph.addRelationship` id-dedup (the same `IMPORTS` id is produced
32
- * for a given ordered pair), so no local `seen` set is needed.
19
+ * A file compiled into several Xcode targets gets an edge to each module.
20
+ *
21
+ * The Module node's `filePath` is the target directory (SwiftPM), the
22
+ * `.xcodeproj` bundle (Xcode), or '.' (`__default__`). Incremental writeback
23
+ * keys rows by `filePath`: a rewritten member file pulls that key into the
24
+ * write set across its membership edge, so the Module node and every
25
+ * membership edge are deleted and rewritten together.
26
+ *
27
+ * Idempotent: node and edge ids are derived from the module key and the
28
+ * member path, and `graph.addNode` / `graph.addRelationship` dedupe by id.
33
29
  */
34
- import type { ParsedFile } from '../../../../_shared/index.js';
30
+ import { type ParsedFile } from '../../../../_shared/index.js';
35
31
  import type { KnowledgeGraph } from '../../../graph/types.js';
36
32
  import type { GraphNodeLookup } from '../../scope-resolution/graph-bridge/node-lookup.js';
33
+ /** Graph id of the `Module` node for a Swift module key. */
34
+ export declare function swiftModuleNodeId(moduleKey: string): string;
37
35
  export declare function emitSwiftImplicitImportEdges(graph: KnowledgeGraph, parsedFiles: readonly ParsedFile[], _nodeLookup: GraphNodeLookup, resolutionConfig?: unknown): void;