@flagshark/core 2.0.0 → 2.1.1

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 (68) hide show
  1. package/README.md +2 -0
  2. package/dist/config/schema.d.ts +112 -0
  3. package/dist/config/schema.d.ts.map +1 -1
  4. package/dist/config/schema.js +63 -0
  5. package/dist/config/schema.js.map +1 -1
  6. package/dist/detection/detectors/typescript.d.ts.map +1 -1
  7. package/dist/detection/detectors/typescript.js +41 -0
  8. package/dist/detection/detectors/typescript.js.map +1 -1
  9. package/dist/detection/feature-flag.d.ts +28 -0
  10. package/dist/detection/feature-flag.d.ts.map +1 -1
  11. package/dist/detection/helpers.d.ts +16 -0
  12. package/dist/detection/helpers.d.ts.map +1 -1
  13. package/dist/detection/helpers.js +117 -6
  14. package/dist/detection/helpers.js.map +1 -1
  15. package/dist/detection/import-graph.d.ts +234 -0
  16. package/dist/detection/import-graph.d.ts.map +1 -0
  17. package/dist/detection/import-graph.js +641 -0
  18. package/dist/detection/import-graph.js.map +1 -0
  19. package/dist/detection/interface.d.ts +57 -0
  20. package/dist/detection/interface.d.ts.map +1 -1
  21. package/dist/detection/interface.js.map +1 -1
  22. package/dist/detection/polyglot-analyzer.d.ts +8 -0
  23. package/dist/detection/polyglot-analyzer.d.ts.map +1 -1
  24. package/dist/detection/polyglot-analyzer.js +2 -0
  25. package/dist/detection/polyglot-analyzer.js.map +1 -1
  26. package/dist/detection/tree-sitter/engine.d.ts.map +1 -1
  27. package/dist/detection/tree-sitter/engine.js +62 -15
  28. package/dist/detection/tree-sitter/engine.js.map +1 -1
  29. package/dist/detection/tree-sitter/parser-cache.d.ts +20 -0
  30. package/dist/detection/tree-sitter/parser-cache.d.ts.map +1 -1
  31. package/dist/detection/tree-sitter/parser-cache.js +32 -1
  32. package/dist/detection/tree-sitter/parser-cache.js.map +1 -1
  33. package/dist/output/json.d.ts.map +1 -1
  34. package/dist/output/json.js +28 -0
  35. package/dist/output/json.js.map +1 -1
  36. package/dist/output/markdown.d.ts.map +1 -1
  37. package/dist/output/markdown.js +47 -1
  38. package/dist/output/markdown.js.map +1 -1
  39. package/dist/output/text.d.ts.map +1 -1
  40. package/dist/output/text.js +89 -0
  41. package/dist/output/text.js.map +1 -1
  42. package/dist/providers/cross-reference.d.ts +36 -2
  43. package/dist/providers/cross-reference.d.ts.map +1 -1
  44. package/dist/providers/cross-reference.js +102 -7
  45. package/dist/providers/cross-reference.js.map +1 -1
  46. package/dist/providers/interface.d.ts +116 -2
  47. package/dist/providers/interface.d.ts.map +1 -1
  48. package/dist/providers/launchdarkly/client.d.ts +12 -0
  49. package/dist/providers/launchdarkly/client.d.ts.map +1 -1
  50. package/dist/providers/launchdarkly/client.js +243 -24
  51. package/dist/providers/launchdarkly/client.js.map +1 -1
  52. package/dist/providers/launchdarkly/types.d.ts +318 -0
  53. package/dist/providers/launchdarkly/types.d.ts.map +1 -1
  54. package/dist/providers/launchdarkly/types.js +82 -0
  55. package/dist/providers/launchdarkly/types.js.map +1 -1
  56. package/dist/providers/orchestrate.d.ts +34 -2
  57. package/dist/providers/orchestrate.d.ts.map +1 -1
  58. package/dist/providers/orchestrate.js +59 -7
  59. package/dist/providers/orchestrate.js.map +1 -1
  60. package/dist/scan-repo.d.ts +28 -0
  61. package/dist/scan-repo.d.ts.map +1 -1
  62. package/dist/scan-repo.js +290 -4
  63. package/dist/scan-repo.js.map +1 -1
  64. package/dist/staleness.d.ts +31 -1
  65. package/dist/staleness.d.ts.map +1 -1
  66. package/dist/staleness.js +66 -10
  67. package/dist/staleness.js.map +1 -1
  68. package/package.json +2 -1
@@ -0,0 +1,641 @@
1
+ /**
2
+ * Lightweight import-graph builder for TS/JS files.
3
+ *
4
+ * Goal: extend the existing "file imports a known SDK" detection gate to cover
5
+ * the common case where consumer files reach the SDK through a local wrapper —
6
+ * an internal `flag-resolver.ts`, a Pinia store, a custom React hook, a re-export
7
+ * barrel, etc. Without this, real codebases that hide the SDK behind an
8
+ * abstraction silently report 0 flags (see the pre-launch shakedown notes:
9
+ * Unleash at 4 of 62, n8n at 0 of many).
10
+ *
11
+ * Design — three deliberate compromises in service of "ship soon and don't add
12
+ * false positives":
13
+ *
14
+ * 1. **Regex import extraction, not tree-sitter.** The existing analyzer
15
+ * already parses each file once for flag detection; running a second
16
+ * tree-sitter pass for imports would roughly double scan time on a large
17
+ * monorepo. Three regexes cover ESM static imports, CJS require(), and
18
+ * dynamic import() with >95% coverage of real-world syntax. The misses
19
+ * (heavily multi-line imports, computed-string specifiers, etc.) only
20
+ * cost us some recall — they never add false positives because the gate
21
+ * still requires a *real* SDK string match downstream.
22
+ *
23
+ * 2. **Relative-path resolution only.** TypeScript path aliases
24
+ * (`@/foo` via tsconfig.paths), workspace `@scope/pkg` resolution, and
25
+ * package.json `exports` are out of scope. A user with path aliases will
26
+ * hit the same blind spot as today; we document it. The right fix is to
27
+ * read tsconfig.json and apply paths — separate workstream, not a Show HN
28
+ * blocker.
29
+ *
30
+ * 3. **Reverse-reachability BFS from SDK-importing seeds.** We propagate
31
+ * SDK provenance *away* from the SDK importers. A file is "in scope" iff
32
+ * it transitively imports something that imports an SDK. Cycle-safe via
33
+ * a "did the set actually grow?" check before requeueing — bounded
34
+ * O(edges × seeds) in the worst case.
35
+ *
36
+ * The output is consumed by PolyglotAnalyzer, which uses it to mark
37
+ * wrapper-consumer files as SDK-positive *for the gating step only* — actual
38
+ * flag-key extraction still goes through the same per-provider patterns, so
39
+ * precision stays the same as direct-import detection.
40
+ */
41
+ import * as fs from 'node:fs';
42
+ import path from 'node:path';
43
+ import { Languages } from './interface.js';
44
+ // -- Import-extraction regexes --------------------------------------------------
45
+ //
46
+ // All three patterns capture the bare module specifier in group 1. Anchored
47
+ // loosely enough to handle whitespace variations but not so loosely that they
48
+ // match inside strings or comments — both forms have a leading non-word boundary
49
+ // (or start-of-line, for ESM) before the keyword.
50
+ /**
51
+ * ESM static imports. Matches:
52
+ * import 'foo'
53
+ * import x from 'foo'
54
+ * import { a, b } from 'foo'
55
+ * import * as x from 'foo'
56
+ * import x, { a } from 'foo'
57
+ * import type { a } from 'foo'
58
+ * import type * as x from 'foo'
59
+ *
60
+ * The optional `\s+...\s+from\s+` clause uses a lazy quantifier so it doesn't
61
+ * greedily swallow a no-from `import 'foo'`. Multi-line imports (newlines
62
+ * inside the import clause) are a known miss — `[^'"`;\n]*` excludes newlines
63
+ * to keep matching linear-time. In practice those are rare outside
64
+ * auto-generated barrels.
65
+ */
66
+ const ESM_IMPORT_RE = /^[ \t]*import\s*(?:\s+type)?\s*(?:[^'"`;\n]*?\s+from\s+)?['"`]([^'"`]+)['"`]/gm;
67
+ /**
68
+ * CJS require(). Matches:
69
+ * const x = require('foo')
70
+ * require('foo')
71
+ *
72
+ * The lookbehind `(?<![.\w$])` blocks `foo.require('bar')` and
73
+ * `myRequire('bar')` — anything where `require` is part of a longer identifier
74
+ * or a member-expression property name.
75
+ */
76
+ const REQUIRE_RE = /(?<![.\w$])require\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
77
+ /**
78
+ * Dynamic ESM imports. Matches:
79
+ * import('foo')
80
+ * await import('foo')
81
+ *
82
+ * Same lookbehind guard as REQUIRE_RE to avoid `obj.import('foo')`.
83
+ */
84
+ const DYNAMIC_IMPORT_RE = /(?<![.\w$])import\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
85
+ /**
86
+ * Python `from X import Y` matcher. `X` can be:
87
+ * - a dotted module path (`posthog.client`)
88
+ * - a relative path (`.utils`, `..lib.flags`, `...common.feature_flags`)
89
+ *
90
+ * Captures group 1 = the module path (including leading dots if any).
91
+ * Names after `import` are intentionally not captured — for the import-graph
92
+ * we care about which MODULE was reached, not which names were brought in.
93
+ *
94
+ * Anchored to line start (after optional indentation) to skip `from` keywords
95
+ * that appear mid-expression (e.g. in docstrings or string literals).
96
+ */
97
+ const PY_FROM_IMPORT_RE = /^[ \t]*from\s+(\.*[\w.]+)\s+import\s+/gm;
98
+ /**
99
+ * Python bare `import X` / `import X as Y` matcher (top-level only, never
100
+ * `if cond: import X` because conditional imports almost always indicate
101
+ * fallback paths we shouldn't follow as the primary edge).
102
+ *
103
+ * Captures the module path — comma-separated multi-imports (`import a, b`)
104
+ * are uncommon in real code; we handle them by running this regex once and
105
+ * splitting the capture on `,` post-hoc.
106
+ */
107
+ const PY_IMPORT_RE = /^[ \t]*import\s+([\w.]+(?:\s*,\s*[\w.]+)*)/gm;
108
+ /**
109
+ * Extracts every module specifier mentioned in a TS/JS file.
110
+ *
111
+ * @returns a deduplicated array of specifier strings, in the order first seen.
112
+ * Order is not load-bearing for the graph algorithm but makes test fixtures
113
+ * readable.
114
+ */
115
+ export function extractImports(content) {
116
+ const seen = new Set();
117
+ const out = [];
118
+ const collect = (re) => {
119
+ // Reset state on the shared RegExp — we use the /g flag for exec loops.
120
+ re.lastIndex = 0;
121
+ let m;
122
+ while ((m = re.exec(content)) !== null) {
123
+ const spec = m[1];
124
+ if (!seen.has(spec)) {
125
+ seen.add(spec);
126
+ out.push(spec);
127
+ }
128
+ }
129
+ };
130
+ collect(ESM_IMPORT_RE);
131
+ collect(REQUIRE_RE);
132
+ collect(DYNAMIC_IMPORT_RE);
133
+ return out;
134
+ }
135
+ /**
136
+ * Extracts every module specifier mentioned in a Python file. Returns each
137
+ * specifier verbatim (including leading dots for relative imports). The
138
+ * graph's seed-matching logic compares the bare module name (everything
139
+ * before the first dot, e.g. `posthog` from `posthog.client`) against the
140
+ * SDK seed list.
141
+ *
142
+ * Multi-import lines like `import a, b, c` are split into individual
143
+ * specifiers; deduplicated within a file.
144
+ */
145
+ export function extractPythonImports(content) {
146
+ const seen = new Set();
147
+ const out = [];
148
+ const push = (spec) => {
149
+ const trimmed = spec.trim();
150
+ if (trimmed.length === 0 || seen.has(trimmed))
151
+ return;
152
+ seen.add(trimmed);
153
+ out.push(trimmed);
154
+ };
155
+ PY_FROM_IMPORT_RE.lastIndex = 0;
156
+ let m;
157
+ while ((m = PY_FROM_IMPORT_RE.exec(content)) !== null)
158
+ push(m[1]);
159
+ PY_IMPORT_RE.lastIndex = 0;
160
+ while ((m = PY_IMPORT_RE.exec(content)) !== null) {
161
+ // `import a, b, c` -- split the captured run on commas.
162
+ for (const part of m[1].split(','))
163
+ push(part);
164
+ }
165
+ return out;
166
+ }
167
+ /**
168
+ * Resolves a Python import specifier to a file path that exists in
169
+ * `fileSet`. Python relative imports use dot notation rather than `./`:
170
+ * - `from . import x` → same package as importer
171
+ * - `from .utils import x` → sibling module `utils` in same package
172
+ * - `from ..lib import x` → parent package's `lib`
173
+ * - `from .. import x` → parent package's `__init__.py`
174
+ *
175
+ * Absolute imports (`from posthog.client import x`) are NOT resolved into
176
+ * the file map — they reference installed packages outside the repo. Those
177
+ * get matched against the SDK seed list separately.
178
+ *
179
+ * Returns null for absolute imports or unresolvable paths.
180
+ */
181
+ export function resolvePythonImport(importerPath, specifier, fileSet) {
182
+ // Count leading dots — that's the package walk depth (one dot = same
183
+ // package, two = parent, three = grandparent, etc.). The remaining
184
+ // dotted name is the module path under that package.
185
+ let dots = 0;
186
+ while (dots < specifier.length && specifier[dots] === '.')
187
+ dots++;
188
+ if (dots === 0)
189
+ return null; // absolute import — see SDK seed matching upstream
190
+ const importerDir = path.dirname(importerPath);
191
+ // First dot is "same dir", each subsequent dot walks up one level.
192
+ let baseDir = importerDir;
193
+ for (let i = 1; i < dots; i++)
194
+ baseDir = path.dirname(baseDir);
195
+ const rest = specifier.slice(dots); // e.g. "utils.helpers" or ""
196
+ if (rest.length === 0) {
197
+ // `from . import x` -- target is the package's __init__.py.
198
+ const candidate = path.join(baseDir, '__init__.py');
199
+ return fileSet.has(candidate) ? candidate : null;
200
+ }
201
+ // Convert "utils.helpers" to "utils/helpers" then try both `.py` and
202
+ // `/__init__.py` forms (Python packages vs modules).
203
+ const segments = rest.split('.').filter((s) => s.length > 0);
204
+ const moduleBase = path.join(baseDir, ...segments);
205
+ const candidateModule = moduleBase + '.py';
206
+ if (fileSet.has(candidateModule))
207
+ return candidateModule;
208
+ const candidatePackage = path.join(moduleBase, '__init__.py');
209
+ if (fileSet.has(candidatePackage))
210
+ return candidatePackage;
211
+ return null;
212
+ }
213
+ // -- Path resolution ----------------------------------------------------------
214
+ const TS_JS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
215
+ /**
216
+ * Finds the nearest tsconfig.json (or jsconfig.json) at or above `root` and
217
+ * parses `compilerOptions.baseUrl` + `compilerOptions.paths`. Returns null
218
+ * if no config exists, the config has no path aliases, or the file fails
219
+ * to parse.
220
+ *
221
+ * Intentional limitations (documented so the recall ceiling is honest):
222
+ * - We do NOT follow `extends` chains. A tsconfig that inherits aliases
223
+ * from a parent project won't have them resolved here. Real fix would
224
+ * require a recursive resolver matching tsc's actual behaviour.
225
+ * - We only look at the first matching tsconfig walking up from `root`.
226
+ * Nested workspaces with distinct tsconfigs per package aren't handled
227
+ * specially — the outermost one wins. Sufficient for the typical
228
+ * "alias is in repo-root tsconfig" pattern.
229
+ */
230
+ export function loadTsconfigAliases(root) {
231
+ // Walk up from `root` looking for tsconfig.json, then jsconfig.json.
232
+ // Stop at the filesystem root. Bound the loop so a pathological setup
233
+ // can't hang us — 32 levels is well past any realistic monorepo depth.
234
+ let dir = path.resolve(root);
235
+ for (let i = 0; i < 32; i++) {
236
+ for (const name of ['tsconfig.json', 'jsconfig.json']) {
237
+ const candidate = path.join(dir, name);
238
+ if (fs.existsSync(candidate)) {
239
+ const parsed = readTsconfigJson(candidate);
240
+ if (parsed?.paths)
241
+ return parsed;
242
+ }
243
+ }
244
+ const parent = path.dirname(dir);
245
+ if (parent === dir)
246
+ break;
247
+ dir = parent;
248
+ }
249
+ return null;
250
+ }
251
+ /**
252
+ * Parses a JSONC tsconfig file (allows `//` line comments and `/* *\/` block
253
+ * comments — tsc accepts both, JSON.parse does not). The comment stripper is
254
+ * intentionally minimal: it doesn't try to be perfect inside string values,
255
+ * just inside the structural JSON. In practice tsconfig.json string values
256
+ * rarely contain comment-like text (`http://...` paths are URLs, not
257
+ * comments — handled below) so this works for the long tail.
258
+ */
259
+ function readTsconfigJson(file) {
260
+ let raw;
261
+ try {
262
+ raw = fs.readFileSync(file, 'utf-8');
263
+ }
264
+ catch {
265
+ return null;
266
+ }
267
+ // Strip /* ... */ block comments and // line comments outside strings.
268
+ // We walk the file once, tracking whether we're inside a "..." string —
269
+ // this avoids false-stripping `://` from a URL value, which a naive regex
270
+ // would mangle.
271
+ const stripped = stripJsoncComments(raw);
272
+ let parsed;
273
+ try {
274
+ parsed = JSON.parse(stripped);
275
+ }
276
+ catch {
277
+ return null;
278
+ }
279
+ const co = parsed.compilerOptions;
280
+ if (!co?.paths || Object.keys(co.paths).length === 0)
281
+ return null;
282
+ const baseUrl = path.resolve(path.dirname(file), co.baseUrl ?? '.');
283
+ const paths = new Map();
284
+ for (const [alias, targets] of Object.entries(co.paths)) {
285
+ if (Array.isArray(targets))
286
+ paths.set(alias, targets);
287
+ }
288
+ return { baseUrl, paths };
289
+ }
290
+ function stripJsoncComments(src) {
291
+ let out = '';
292
+ let i = 0;
293
+ let inString = false;
294
+ while (i < src.length) {
295
+ const ch = src[i];
296
+ if (inString) {
297
+ out += ch;
298
+ if (ch === '\\' && i + 1 < src.length) {
299
+ out += src[i + 1];
300
+ i += 2;
301
+ continue;
302
+ }
303
+ if (ch === '"')
304
+ inString = false;
305
+ i++;
306
+ continue;
307
+ }
308
+ if (ch === '"') {
309
+ inString = true;
310
+ out += ch;
311
+ i++;
312
+ continue;
313
+ }
314
+ if (ch === '/' && src[i + 1] === '/') {
315
+ // Line comment — skip to next newline.
316
+ while (i < src.length && src[i] !== '\n')
317
+ i++;
318
+ continue;
319
+ }
320
+ if (ch === '/' && src[i + 1] === '*') {
321
+ // Block comment — skip to closing */.
322
+ i += 2;
323
+ while (i < src.length - 1 && !(src[i] === '*' && src[i + 1] === '/'))
324
+ i++;
325
+ i += 2;
326
+ continue;
327
+ }
328
+ out += ch;
329
+ i++;
330
+ }
331
+ return out;
332
+ }
333
+ /**
334
+ * Tries to apply tsconfig path aliases to a bare-spec import. Returns the
335
+ * resolved absolute file path (if it exists in `fileSet`) or null.
336
+ *
337
+ * Two alias shapes are supported, matching tsc:
338
+ * - Wildcard: key ends with `/*`, target ends with `/*`. The portion
339
+ * after the alias prefix is substituted into the target wildcard.
340
+ * Example: alias `@/*` → `src/*`; specifier `@/lib/foo` resolves to
341
+ * `<baseUrl>/src/lib/foo`.
342
+ * - Exact: no `*`. The specifier matches the alias exactly and resolves
343
+ * to the literal target.
344
+ */
345
+ export function resolveAliasedImport(specifier, aliases, fileSet) {
346
+ for (const [alias, targets] of aliases.paths) {
347
+ if (alias.endsWith('/*')) {
348
+ const prefix = alias.slice(0, -2) + '/';
349
+ // `@/` must be matched as a prefix; `@/lib/foo` matches `@/*`.
350
+ // Edge case: an alias key like `@/*` should also match `@/lib`
351
+ // without the trailing slash if the user wrote `@/lib` not `@/lib/`.
352
+ // The slash is part of the prefix, so `@/lib` doesn't start with
353
+ // `@//` — handle the slash-less prefix separately.
354
+ const dotPrefix = alias.slice(0, -2); // "@"
355
+ if (!specifier.startsWith(prefix) && specifier !== dotPrefix)
356
+ continue;
357
+ const rest = specifier.slice(prefix.length);
358
+ for (const target of targets) {
359
+ if (!target.endsWith('/*'))
360
+ continue;
361
+ const targetPrefix = target.slice(0, -2);
362
+ const resolved = path.resolve(aliases.baseUrl, targetPrefix, rest);
363
+ const found = tryResolveExisting(resolved, fileSet);
364
+ if (found)
365
+ return found;
366
+ }
367
+ }
368
+ else if (alias === specifier) {
369
+ for (const target of targets) {
370
+ const resolved = path.resolve(aliases.baseUrl, target);
371
+ const found = tryResolveExisting(resolved, fileSet);
372
+ if (found)
373
+ return found;
374
+ }
375
+ }
376
+ }
377
+ return null;
378
+ }
379
+ /**
380
+ * Given a resolved path (no extension), tries the same exact-then-extension-
381
+ * then-/index.* lookup that resolveImportPath does for relative imports.
382
+ * Factored out so both code paths agree on what "exists" means.
383
+ */
384
+ function tryResolveExisting(resolved, fileSet) {
385
+ if (fileSet.has(resolved))
386
+ return resolved;
387
+ for (const ext of TS_JS_EXTENSIONS) {
388
+ if (fileSet.has(resolved + ext))
389
+ return resolved + ext;
390
+ }
391
+ for (const ext of TS_JS_EXTENSIONS) {
392
+ const candidate = path.join(resolved, `index${ext}`);
393
+ if (fileSet.has(candidate))
394
+ return candidate;
395
+ }
396
+ return null;
397
+ }
398
+ /**
399
+ * Resolves a relative or absolute import specifier to a file path that exists
400
+ * in `fileSet`. Returns null for bare specifiers (unless `aliases` provided
401
+ * and the specifier matches an alias) or unresolvable paths.
402
+ *
403
+ * Resolution order matches Node + TS behavior closely enough for our purposes:
404
+ * 1. Path aliases (when `aliases` provided) — `@/foo` → `src/foo`.
405
+ * 2. Exact match (`./foo.ts`).
406
+ * 3. With each candidate extension (`./foo` → `./foo.ts`).
407
+ * 4. As a directory with `index.<ext>` (`./foo` → `./foo/index.ts`).
408
+ * 5. TS-emitted `.js` rewritten to `.ts` (`./foo.js` → `./foo.ts`), which
409
+ * handles the verbatimModuleSyntax / NodeNext convention where TS source
410
+ * writes `.js` but the actual file is `.ts`. Same for `.jsx`/`.tsx`.
411
+ */
412
+ export function resolveImportPath(importerPath, specifier, fileSet, aliases) {
413
+ // Try path aliases first — they win over both relative and bare interpretation.
414
+ // A monorepo's `@/foo` is conceptually a re-mapping, not a relative path,
415
+ // so it gets the highest-priority resolution attempt.
416
+ if (aliases) {
417
+ const aliased = resolveAliasedImport(specifier, aliases, fileSet);
418
+ if (aliased)
419
+ return aliased;
420
+ }
421
+ if (!specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('/')) {
422
+ return null;
423
+ }
424
+ const importerDir = path.dirname(importerPath);
425
+ const absSpecifier = path.resolve(importerDir, specifier);
426
+ // 1. Exact match
427
+ if (fileSet.has(absSpecifier))
428
+ return absSpecifier;
429
+ // 2. + extension
430
+ for (const ext of TS_JS_EXTENSIONS) {
431
+ const candidate = absSpecifier + ext;
432
+ if (fileSet.has(candidate))
433
+ return candidate;
434
+ }
435
+ // 3. /index.<ext>
436
+ for (const ext of TS_JS_EXTENSIONS) {
437
+ const candidate = path.join(absSpecifier, `index${ext}`);
438
+ if (fileSet.has(candidate))
439
+ return candidate;
440
+ }
441
+ // 4. TS source convention: import './foo.js' → real file is './foo.ts' / './foo.tsx'.
442
+ // Same for .jsx → .tsx, .mjs → .mts, .cjs → .cts.
443
+ const emittedToSourceExt = {
444
+ '.js': ['.ts', '.tsx'],
445
+ '.jsx': ['.tsx', '.jsx'],
446
+ '.mjs': ['.mts', '.mjs'],
447
+ '.cjs': ['.cts', '.cjs'],
448
+ };
449
+ const ext = path.extname(absSpecifier);
450
+ const replacements = emittedToSourceExt[ext];
451
+ if (replacements) {
452
+ const base = absSpecifier.slice(0, absSpecifier.length - ext.length);
453
+ for (const r of replacements) {
454
+ const candidate = base + r;
455
+ if (fileSet.has(candidate))
456
+ return candidate;
457
+ }
458
+ }
459
+ return null;
460
+ }
461
+ /**
462
+ * Builds the transitive SDK-reach map for a set of TS/JS files.
463
+ *
464
+ * Algorithm:
465
+ * 1. Walk every TS/JS file, extract module specifiers.
466
+ * 2. For each specifier: if it's a seed SDK → record direct SDK reach;
467
+ * if it's a relative path that resolves in the file map → add forward edge.
468
+ * 3. Build the reverse adjacency map (target → importers).
469
+ * 4. Seed the worklist with every directly-importing file. BFS outward
470
+ * through the reverse graph, propagating each importer's SDK set
471
+ * (union of all reachable importees). Requeue only when a set actually
472
+ * grew — this terminates on every cyclic import graph.
473
+ */
474
+ export function buildImportGraph(files, opts) {
475
+ const fileSet = new Set();
476
+ for (const fp of files.keys()) {
477
+ if (opts.isTsJs(fp))
478
+ fileSet.add(fp);
479
+ }
480
+ const forward = new Map();
481
+ const directSdks = new Map();
482
+ let edgesResolved = 0;
483
+ let edgesUnresolved = 0;
484
+ for (const [filePath, content] of files) {
485
+ if (!fileSet.has(filePath))
486
+ continue;
487
+ // Dispatch extraction and resolution by file extension. Python is the
488
+ // only added language for now; Go has module-path complications (no
489
+ // relative imports — every Go import is a module path) that need a
490
+ // separate design pass. See B4 in the bug inventory.
491
+ const python = isPythonFile(filePath);
492
+ const specs = python ? extractPythonImports(content) : extractImports(content);
493
+ if (specs.length === 0)
494
+ continue;
495
+ const fileImports = new Set();
496
+ const fileSdks = new Set();
497
+ for (const spec of specs) {
498
+ // Seed SDK match. Python's matching is dotted (`posthog.client`
499
+ // starts with `posthog.`); TS/JS is path-style (`unleash-client/lib/x`
500
+ // starts with `unleash-client/`). Both forms reduce to "specifier
501
+ // equals seed OR specifier starts with seed + sep". We pick the
502
+ // separator per-language.
503
+ const sep = python ? '.' : '/';
504
+ for (const sdk of opts.seedSdkPatterns) {
505
+ if (spec === sdk || spec.startsWith(sdk + sep)) {
506
+ fileSdks.add(sdk);
507
+ }
508
+ }
509
+ // Resolve to a file in our scan set.
510
+ if (python) {
511
+ // Python relative imports start with one or more dots.
512
+ if (spec.startsWith('.')) {
513
+ const target = resolvePythonImport(filePath, spec, fileSet);
514
+ if (target) {
515
+ fileImports.add(target);
516
+ edgesResolved++;
517
+ }
518
+ else {
519
+ edgesUnresolved++;
520
+ }
521
+ }
522
+ // Absolute Python imports never resolve into the file map (they
523
+ // reference installed packages); SDK seed matching above catches
524
+ // the ones we care about.
525
+ }
526
+ else {
527
+ // TS/JS: relative or potentially-aliased bare specifier.
528
+ const isRelative = spec.startsWith('./') || spec.startsWith('../') || spec.startsWith('/');
529
+ if (isRelative || opts.aliases) {
530
+ const target = resolveImportPath(filePath, spec, fileSet, opts.aliases);
531
+ if (target) {
532
+ fileImports.add(target);
533
+ edgesResolved++;
534
+ }
535
+ else if (isRelative) {
536
+ // Only count un-resolved RELATIVE edges as misses — bare specs
537
+ // that don't match an alias aren't expected to resolve and
538
+ // shouldn't pollute the stat.
539
+ edgesUnresolved++;
540
+ }
541
+ }
542
+ }
543
+ }
544
+ if (fileImports.size > 0)
545
+ forward.set(filePath, fileImports);
546
+ if (fileSdks.size > 0)
547
+ directSdks.set(filePath, fileSdks);
548
+ }
549
+ // Reverse adjacency: for each "imported file", who imports it?
550
+ const reverse = new Map();
551
+ for (const [importer, targets] of forward) {
552
+ for (const target of targets) {
553
+ let importers = reverse.get(target);
554
+ if (!importers) {
555
+ importers = new Set();
556
+ reverse.set(target, importers);
557
+ }
558
+ importers.add(importer);
559
+ }
560
+ }
561
+ // BFS the reverse graph from every direct-SDK file, propagating its SDK set
562
+ // up the import chain. The propagation is monotonic: a file's SDK set only
563
+ // grows, never shrinks. We requeue only when growth happens, so the algorithm
564
+ // terminates in O(edges × |seed-sdks|) even with cyclic imports.
565
+ const transitiveSdks = new Map();
566
+ for (const [file, sdks] of directSdks) {
567
+ transitiveSdks.set(file, new Set(sdks));
568
+ }
569
+ const worklist = [...directSdks.keys()];
570
+ while (worklist.length > 0) {
571
+ const file = worklist.shift();
572
+ const sdksOfFile = transitiveSdks.get(file);
573
+ /* c8 ignore next -- defensive; file is only added to worklist when its sdk set exists */
574
+ if (!sdksOfFile)
575
+ continue;
576
+ const importers = reverse.get(file);
577
+ if (!importers)
578
+ continue;
579
+ for (const importer of importers) {
580
+ let importerSdks = transitiveSdks.get(importer);
581
+ if (!importerSdks) {
582
+ importerSdks = new Set();
583
+ transitiveSdks.set(importer, importerSdks);
584
+ }
585
+ let grew = false;
586
+ for (const sdk of sdksOfFile) {
587
+ if (!importerSdks.has(sdk)) {
588
+ importerSdks.add(sdk);
589
+ grew = true;
590
+ }
591
+ }
592
+ if (grew)
593
+ worklist.push(importer);
594
+ }
595
+ }
596
+ return {
597
+ transitiveSdks,
598
+ stats: {
599
+ filesWalked: fileSet.size,
600
+ seedFiles: directSdks.size,
601
+ inScopeFiles: transitiveSdks.size,
602
+ edgesResolved,
603
+ edgesUnresolved,
604
+ },
605
+ };
606
+ }
607
+ // -- Language helper ----------------------------------------------------------
608
+ /**
609
+ * Returns true when the file path is a TS/JS source flagshark would parse.
610
+ * Mirrors the file-extension set used by TypeScriptDetector / JavaScriptDetector.
611
+ */
612
+ /**
613
+ * True if the file is a Python source file the graph should walk.
614
+ * Mirrors the extension list of the Python detector — keep in sync.
615
+ */
616
+ export function isPythonFile(filePath) {
617
+ const lower = filePath.toLowerCase();
618
+ return lower.endsWith('.py') || lower.endsWith('.pyx') || lower.endsWith('.pyi');
619
+ }
620
+ /**
621
+ * Polyglot scope predicate: true for TS/JS *and* Python source files.
622
+ * Pass this as `ImportGraphOptions.isTsJs` when you want the graph to
623
+ * walk Python wrappers too. Replaces the TS/JS-only check incrementally
624
+ * without forcing every caller to update.
625
+ */
626
+ export function isScannedSourceFile(filePath) {
627
+ return isTsJsFile(filePath) || isPythonFile(filePath);
628
+ }
629
+ export function isTsJsFile(filePath) {
630
+ const lower = filePath.toLowerCase();
631
+ const dot = lower.lastIndexOf('.');
632
+ if (dot === -1)
633
+ return false;
634
+ const ext = lower.slice(dot);
635
+ return TS_JS_EXTENSIONS.includes(ext);
636
+ }
637
+ // Exported for tests so they can assert on the resolution-order without
638
+ // re-listing the constant.
639
+ export const _TS_JS_EXTENSIONS_FOR_TESTS = TS_JS_EXTENSIONS;
640
+ export { Languages };
641
+ //# sourceMappingURL=import-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-graph.js","sourceRoot":"","sources":["../../src/detection/import-graph.ts"],"names":[],"mappings":"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"}