circle-ir 4.16.1 → 4.18.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 (295) hide show
  1. package/dist/analysis/advisories.d.ts +186 -0
  2. package/dist/analysis/advisories.d.ts.map +1 -0
  3. package/dist/analysis/advisories.js +391 -0
  4. package/dist/analysis/advisories.js.map +1 -0
  5. package/dist/analysis/comment-lines.d.ts +14 -0
  6. package/dist/analysis/comment-lines.d.ts.map +1 -0
  7. package/dist/analysis/comment-lines.js +34 -0
  8. package/dist/analysis/comment-lines.js.map +1 -0
  9. package/dist/analysis/config-loader.d.ts.map +1 -1
  10. package/dist/analysis/config-loader.js +59 -5
  11. package/dist/analysis/config-loader.js.map +1 -1
  12. package/dist/analysis/constant-caller-gate.d.ts +2 -2
  13. package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
  14. package/dist/analysis/constant-caller-gate.js +205 -29
  15. package/dist/analysis/constant-caller-gate.js.map +1 -1
  16. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  17. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  18. package/dist/analysis/constant-propagation/index.js +10 -0
  19. package/dist/analysis/constant-propagation/index.js.map +1 -1
  20. package/dist/analysis/constant-propagation/propagator.d.ts +15 -0
  21. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  22. package/dist/analysis/constant-propagation/propagator.js +224 -20
  23. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  24. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  25. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  26. package/dist/analysis/constant-propagation.d.ts +1 -1
  27. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  28. package/dist/analysis/constant-propagation.js +1 -1
  29. package/dist/analysis/constant-propagation.js.map +1 -1
  30. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  31. package/dist/analysis/entry-point-detection.js +72 -0
  32. package/dist/analysis/entry-point-detection.js.map +1 -1
  33. package/dist/analysis/interprocedural.d.ts.map +1 -1
  34. package/dist/analysis/interprocedural.js +75 -8
  35. package/dist/analysis/interprocedural.js.map +1 -1
  36. package/dist/analysis/maven-declared.d.ts +72 -0
  37. package/dist/analysis/maven-declared.d.ts.map +1 -0
  38. package/dist/analysis/maven-declared.js +216 -0
  39. package/dist/analysis/maven-declared.js.map +1 -0
  40. package/dist/analysis/maven-version.d.ts +28 -0
  41. package/dist/analysis/maven-version.d.ts.map +1 -0
  42. package/dist/analysis/maven-version.js +172 -0
  43. package/dist/analysis/maven-version.js.map +1 -0
  44. package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
  45. package/dist/analysis/passes/constant-propagation-pass.js +29 -1
  46. package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
  47. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  48. package/dist/analysis/passes/cross-file-pass.js +84 -2
  49. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  50. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
  51. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  52. package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
  53. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  54. package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
  55. package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
  56. package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
  57. package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
  58. package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
  59. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  60. package/dist/analysis/passes/interprocedural-pass.js +562 -0
  61. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  62. package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
  63. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  64. package/dist/analysis/passes/language-sources-pass.js +1079 -14
  65. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  66. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  67. package/dist/analysis/passes/param-source-flow-gate-pass.js +82 -3
  68. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  69. package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
  70. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  71. package/dist/analysis/passes/sink-filter-pass.js +690 -12
  72. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  73. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  74. package/dist/analysis/passes/taint-propagation-pass.js +298 -19
  75. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  76. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
  77. package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
  78. package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
  79. package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
  80. package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
  81. package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
  82. package/dist/analysis/passes/weak-random-pass.js +29 -0
  83. package/dist/analysis/passes/weak-random-pass.js.map +1 -1
  84. package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
  85. package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
  86. package/dist/analysis/promote-cross-file-flows.js +32 -0
  87. package/dist/analysis/promote-cross-file-flows.js.map +1 -0
  88. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  89. package/dist/analysis/rust-literal-guard.js +100 -1
  90. package/dist/analysis/rust-literal-guard.js.map +1 -1
  91. package/dist/analysis/sbom.d.ts +100 -0
  92. package/dist/analysis/sbom.d.ts.map +1 -1
  93. package/dist/analysis/sbom.js +514 -3
  94. package/dist/analysis/sbom.js.map +1 -1
  95. package/dist/analysis/semver-version.d.ts +30 -0
  96. package/dist/analysis/semver-version.d.ts.map +1 -0
  97. package/dist/analysis/semver-version.js +76 -0
  98. package/dist/analysis/semver-version.js.map +1 -0
  99. package/dist/analysis/taint-matcher.d.ts +5 -0
  100. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  101. package/dist/analysis/taint-matcher.js +226 -4
  102. package/dist/analysis/taint-matcher.js.map +1 -1
  103. package/dist/analyzer.d.ts +57 -0
  104. package/dist/analyzer.d.ts.map +1 -1
  105. package/dist/analyzer.js +69 -6
  106. package/dist/analyzer.js.map +1 -1
  107. package/dist/browser/circle-ir.js +3360 -314
  108. package/dist/core/circle-ir-core.cjs +783 -220
  109. package/dist/core/circle-ir-core.js +783 -220
  110. package/dist/core/extractors/dfg.js +205 -2
  111. package/dist/core/extractors/dfg.js.map +1 -1
  112. package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
  113. package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
  114. package/dist/core/extractors/ts-declaration-detail.js +266 -0
  115. package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
  116. package/dist/core/extractors/types.js +53 -9
  117. package/dist/core/extractors/types.js.map +1 -1
  118. package/dist/graph/analysis-pass.d.ts +4 -1
  119. package/dist/graph/analysis-pass.d.ts.map +1 -1
  120. package/dist/graph/analysis-pass.js +4 -1
  121. package/dist/graph/analysis-pass.js.map +1 -1
  122. package/dist/graph-facts/dead-code.d.ts +62 -0
  123. package/dist/graph-facts/dead-code.d.ts.map +1 -0
  124. package/dist/graph-facts/dead-code.js +90 -0
  125. package/dist/graph-facts/dead-code.js.map +1 -0
  126. package/dist/graph-facts/dependency-calls.d.ts +86 -0
  127. package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
  128. package/dist/graph-facts/dependency-calls.js +93 -0
  129. package/dist/graph-facts/dependency-calls.js.map +1 -0
  130. package/dist/graph-facts/index.d.ts +10 -0
  131. package/dist/graph-facts/index.d.ts.map +1 -0
  132. package/dist/graph-facts/index.js +10 -0
  133. package/dist/graph-facts/index.js.map +1 -0
  134. package/dist/graph-facts/maven-packages.d.ts +42 -0
  135. package/dist/graph-facts/maven-packages.d.ts.map +1 -0
  136. package/dist/graph-facts/maven-packages.js +133 -0
  137. package/dist/graph-facts/maven-packages.js.map +1 -0
  138. package/dist/graph-facts/reach.d.ts +203 -0
  139. package/dist/graph-facts/reach.d.ts.map +1 -0
  140. package/dist/graph-facts/reach.js +219 -0
  141. package/dist/graph-facts/reach.js.map +1 -0
  142. package/dist/graph-facts/removal-reach.d.ts +73 -0
  143. package/dist/graph-facts/removal-reach.d.ts.map +1 -0
  144. package/dist/graph-facts/removal-reach.js +64 -0
  145. package/dist/graph-facts/removal-reach.js.map +1 -0
  146. package/dist/index.d.ts +21 -6
  147. package/dist/index.d.ts.map +1 -1
  148. package/dist/index.js +20 -3
  149. package/dist/index.js.map +1 -1
  150. package/dist/navigation/go-graph.d.ts +68 -0
  151. package/dist/navigation/go-graph.d.ts.map +1 -0
  152. package/dist/navigation/go-graph.js +753 -0
  153. package/dist/navigation/go-graph.js.map +1 -0
  154. package/dist/navigation/index.d.ts +7 -1
  155. package/dist/navigation/index.d.ts.map +1 -1
  156. package/dist/navigation/index.js +6 -0
  157. package/dist/navigation/index.js.map +1 -1
  158. package/dist/navigation/java-entry-tables.d.ts +43 -0
  159. package/dist/navigation/java-entry-tables.d.ts.map +1 -0
  160. package/dist/navigation/java-entry-tables.js +56 -0
  161. package/dist/navigation/java-entry-tables.js.map +1 -0
  162. package/dist/navigation/java-graph.d.ts +64 -0
  163. package/dist/navigation/java-graph.d.ts.map +1 -0
  164. package/dist/navigation/java-graph.js +466 -0
  165. package/dist/navigation/java-graph.js.map +1 -0
  166. package/dist/navigation/navigation-index.d.ts +91 -2
  167. package/dist/navigation/navigation-index.d.ts.map +1 -1
  168. package/dist/navigation/navigation-index.js +372 -45
  169. package/dist/navigation/navigation-index.js.map +1 -1
  170. package/dist/navigation/python-graph.d.ts +73 -0
  171. package/dist/navigation/python-graph.d.ts.map +1 -0
  172. package/dist/navigation/python-graph.js +867 -0
  173. package/dist/navigation/python-graph.js.map +1 -0
  174. package/dist/navigation/ts-call-edges.d.ts +193 -0
  175. package/dist/navigation/ts-call-edges.d.ts.map +1 -0
  176. package/dist/navigation/ts-call-edges.js +2089 -0
  177. package/dist/navigation/ts-call-edges.js.map +1 -0
  178. package/dist/navigation/ts-entry-tables.d.ts +88 -0
  179. package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
  180. package/dist/navigation/ts-entry-tables.js +50 -0
  181. package/dist/navigation/ts-entry-tables.js.map +1 -0
  182. package/dist/path-set/branches.d.ts +52 -0
  183. package/dist/path-set/branches.d.ts.map +1 -0
  184. package/dist/path-set/branches.js +124 -0
  185. package/dist/path-set/branches.js.map +1 -0
  186. package/dist/path-set/finding-diff.d.ts +63 -0
  187. package/dist/path-set/finding-diff.d.ts.map +1 -0
  188. package/dist/path-set/finding-diff.js +150 -0
  189. package/dist/path-set/finding-diff.js.map +1 -0
  190. package/dist/path-set/index.d.ts +18 -0
  191. package/dist/path-set/index.d.ts.map +1 -0
  192. package/dist/path-set/index.js +12 -0
  193. package/dist/path-set/index.js.map +1 -0
  194. package/dist/path-set/line-diff.d.ts +19 -0
  195. package/dist/path-set/line-diff.d.ts.map +1 -0
  196. package/dist/path-set/line-diff.js +98 -0
  197. package/dist/path-set/line-diff.js.map +1 -0
  198. package/dist/path-set/path-set.d.ts +88 -0
  199. package/dist/path-set/path-set.d.ts.map +1 -0
  200. package/dist/path-set/path-set.js +716 -0
  201. package/dist/path-set/path-set.js.map +1 -0
  202. package/dist/path-set/templates.d.ts +42 -0
  203. package/dist/path-set/templates.d.ts.map +1 -0
  204. package/dist/path-set/templates.js +119 -0
  205. package/dist/path-set/templates.js.map +1 -0
  206. package/dist/path-set/types.d.ts +195 -0
  207. package/dist/path-set/types.d.ts.map +1 -0
  208. package/dist/path-set/types.js +8 -0
  209. package/dist/path-set/types.js.map +1 -0
  210. package/dist/remedy-class/index.d.ts +12 -0
  211. package/dist/remedy-class/index.d.ts.map +1 -0
  212. package/dist/remedy-class/index.js +9 -0
  213. package/dist/remedy-class/index.js.map +1 -0
  214. package/dist/remedy-class/match.d.ts +165 -0
  215. package/dist/remedy-class/match.d.ts.map +1 -0
  216. package/dist/remedy-class/match.js +705 -0
  217. package/dist/remedy-class/match.js.map +1 -0
  218. package/dist/remedy-class/output-context.d.ts +25 -0
  219. package/dist/remedy-class/output-context.d.ts.map +1 -0
  220. package/dist/remedy-class/output-context.js +150 -0
  221. package/dist/remedy-class/output-context.js.map +1 -0
  222. package/dist/remedy-class/sha256.d.ts +18 -0
  223. package/dist/remedy-class/sha256.d.ts.map +1 -0
  224. package/dist/remedy-class/sha256.js +89 -0
  225. package/dist/remedy-class/sha256.js.map +1 -0
  226. package/dist/remedy-class/table.d.ts +130 -0
  227. package/dist/remedy-class/table.d.ts.map +1 -0
  228. package/dist/remedy-class/table.js +457 -0
  229. package/dist/remedy-class/table.js.map +1 -0
  230. package/dist/resolution/cross-file.d.ts +18 -0
  231. package/dist/resolution/cross-file.d.ts.map +1 -1
  232. package/dist/resolution/cross-file.js +194 -5
  233. package/dist/resolution/cross-file.js.map +1 -1
  234. package/dist/stable-ids/builder.d.ts +20 -0
  235. package/dist/stable-ids/builder.d.ts.map +1 -0
  236. package/dist/stable-ids/builder.js +65 -0
  237. package/dist/stable-ids/builder.js.map +1 -0
  238. package/dist/stable-ids/diff.d.ts +18 -0
  239. package/dist/stable-ids/diff.d.ts.map +1 -0
  240. package/dist/stable-ids/diff.js +37 -0
  241. package/dist/stable-ids/diff.js.map +1 -0
  242. package/dist/stable-ids/go.d.ts +28 -0
  243. package/dist/stable-ids/go.d.ts.map +1 -0
  244. package/dist/stable-ids/go.js +160 -0
  245. package/dist/stable-ids/go.js.map +1 -0
  246. package/dist/stable-ids/hash.d.ts +22 -0
  247. package/dist/stable-ids/hash.d.ts.map +1 -0
  248. package/dist/stable-ids/hash.js +167 -0
  249. package/dist/stable-ids/hash.js.map +1 -0
  250. package/dist/stable-ids/index.d.ts +30 -0
  251. package/dist/stable-ids/index.d.ts.map +1 -0
  252. package/dist/stable-ids/index.js +59 -0
  253. package/dist/stable-ids/index.js.map +1 -0
  254. package/dist/stable-ids/java.d.ts +28 -0
  255. package/dist/stable-ids/java.d.ts.map +1 -0
  256. package/dist/stable-ids/java.js +224 -0
  257. package/dist/stable-ids/java.js.map +1 -0
  258. package/dist/stable-ids/moves.d.ts +87 -0
  259. package/dist/stable-ids/moves.d.ts.map +1 -0
  260. package/dist/stable-ids/moves.js +222 -0
  261. package/dist/stable-ids/moves.js.map +1 -0
  262. package/dist/stable-ids/nodes.d.ts +32 -0
  263. package/dist/stable-ids/nodes.d.ts.map +1 -0
  264. package/dist/stable-ids/nodes.js +92 -0
  265. package/dist/stable-ids/nodes.js.map +1 -0
  266. package/dist/stable-ids/normalize.d.ts +35 -0
  267. package/dist/stable-ids/normalize.d.ts.map +1 -0
  268. package/dist/stable-ids/normalize.js +174 -0
  269. package/dist/stable-ids/normalize.js.map +1 -0
  270. package/dist/stable-ids/parse-errors.d.ts +24 -0
  271. package/dist/stable-ids/parse-errors.d.ts.map +1 -0
  272. package/dist/stable-ids/parse-errors.js +46 -0
  273. package/dist/stable-ids/parse-errors.js.map +1 -0
  274. package/dist/stable-ids/python.d.ts +27 -0
  275. package/dist/stable-ids/python.d.ts.map +1 -0
  276. package/dist/stable-ids/python.js +212 -0
  277. package/dist/stable-ids/python.js.map +1 -0
  278. package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
  279. package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
  280. package/dist/stable-ids/ts-parse-errors.js +75 -0
  281. package/dist/stable-ids/ts-parse-errors.js.map +1 -0
  282. package/dist/stable-ids/types.d.ts +106 -0
  283. package/dist/stable-ids/types.d.ts.map +1 -0
  284. package/dist/stable-ids/types.js +16 -0
  285. package/dist/stable-ids/types.js.map +1 -0
  286. package/dist/stable-ids/typescript.d.ts +30 -0
  287. package/dist/stable-ids/typescript.d.ts.map +1 -0
  288. package/dist/stable-ids/typescript.js +362 -0
  289. package/dist/stable-ids/typescript.js.map +1 -0
  290. package/dist/types/config.d.ts +9 -0
  291. package/dist/types/config.d.ts.map +1 -1
  292. package/dist/types/index.d.ts +53 -0
  293. package/dist/types/index.d.ts.map +1 -1
  294. package/dist/types/index.js.map +1 -1
  295. package/package.json +3 -3
@@ -20,6 +20,7 @@
20
20
  * runtime dependencies circle-ir permits are `web-tree-sitter` and `yaml`,
21
21
  * and a manifest dependency list needs nothing heavier.
22
22
  */
23
+ import { parse as parseYaml } from 'yaml';
23
24
  // ---------------------------------------------------------------------------
24
25
  // purl helpers
25
26
  // ---------------------------------------------------------------------------
@@ -185,6 +186,332 @@ export function parseNpmLockDependencies(packageLock) {
185
186
  return true;
186
187
  });
187
188
  }
189
+ /**
190
+ * Scope every node by reachability from the project roots: reached from a
191
+ * production root → `required`; else from an optional root → `optional`;
192
+ * else from a dev root → `dev`. A node no root reaches keeps the lockfile's
193
+ * own flag (pnpm v5/v6 `dev` / `optional`), else `required`.
194
+ * `direct` is set when any root names the node; `undefined` when `hasRoots`
195
+ * is false (the input did not carry the project's declarations).
196
+ */
197
+ function scopeLockNodes(nodes, roots, hasRoots) {
198
+ const reach = (starts) => {
199
+ const seen = new Set();
200
+ const stack = starts.filter((k) => nodes.has(k));
201
+ while (stack.length > 0) {
202
+ const k = stack.pop();
203
+ if (seen.has(k))
204
+ continue;
205
+ seen.add(k);
206
+ for (const next of nodes.get(k)?.deps ?? [])
207
+ if (nodes.has(next) && !seen.has(next))
208
+ stack.push(next);
209
+ }
210
+ return seen;
211
+ };
212
+ const prod = reach(roots.required);
213
+ const opt = reach(roots.optional);
214
+ const dev = reach(roots.dev);
215
+ const direct = new Set([...roots.required, ...roots.optional, ...roots.dev]);
216
+ const out = [];
217
+ const seen = new Set();
218
+ for (const [key, n] of [...nodes.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
219
+ let scope;
220
+ if (prod.has(key))
221
+ scope = 'required';
222
+ else if (opt.has(key))
223
+ scope = 'optional';
224
+ else if (dev.has(key))
225
+ scope = 'dev';
226
+ else
227
+ scope = n.flagDev ? 'dev' : n.flagOptional ? 'optional' : 'required';
228
+ const id = `${n.name}|${n.version}|${scope}`;
229
+ if (seen.has(id))
230
+ continue;
231
+ seen.add(id);
232
+ const d = dep('npm', n.name, n.version, scope);
233
+ if (hasRoots)
234
+ d.direct = direct.has(key);
235
+ out.push(d);
236
+ }
237
+ return out;
238
+ }
239
+ /** Split `name@version` (name may be `@scope/name`). */
240
+ function splitAt(spec) {
241
+ const at = spec.lastIndexOf('@');
242
+ if (at <= 0)
243
+ return null;
244
+ return { name: spec.slice(0, at), rest: spec.slice(at + 1) };
245
+ }
246
+ /** Drop pnpm's peer suffix: `1.2.3(react@18.2.0)` → `1.2.3`; v5 `1.2.3_react@18` → `1.2.3`. */
247
+ function stripPnpmPeers(v) {
248
+ const paren = v.indexOf('(');
249
+ let out = paren >= 0 ? v.slice(0, paren) : v;
250
+ const us = out.indexOf('_');
251
+ if (us > 0)
252
+ out = out.slice(0, us);
253
+ return out;
254
+ }
255
+ /** Local or workspace references are the project's own code, not npm packages. */
256
+ const PNPM_LOCAL = /^(link:|file:|workspace:|portal:)/;
257
+ /**
258
+ * Turn a pnpm package key into `{ name, version }`:
259
+ * - v9: `name@1.2.3` / `name@1.2.3(peer@x)`
260
+ * - v6: `/name@1.2.3(peer@x)`
261
+ * - v5: `/name/1.2.3_peer@x` / `/@scope/name/1.2.3`
262
+ */
263
+ function parsePnpmKey(rawKey) {
264
+ let key = rawKey.startsWith('/') ? rawKey.slice(1) : rawKey;
265
+ // v5 slash form first: its peer suffix (`_react@18`) contains an `@`.
266
+ const v5 = key.match(/^(@[^/@]+\/[^/@]+|[^/@]+)\/(\d[^/]*)$/);
267
+ if (v5)
268
+ return { name: v5[1], version: stripPnpmPeers(v5[2]) };
269
+ const paren = key.indexOf('(');
270
+ if (paren >= 0)
271
+ key = key.slice(0, paren);
272
+ const at = splitAt(key);
273
+ if (at && /^\d/.test(at.rest))
274
+ return { name: at.name, version: stripPnpmPeers(at.rest) };
275
+ return null;
276
+ }
277
+ /**
278
+ * Resolve a dependency reference inside a pnpm lockfile to a node key
279
+ * (`name@version`). A reference is a version (`1.2.3`, `1.2.3(peer)`), a
280
+ * path key (`/other@1.0.0`, v5 `/other/1.0.0`) or an alias (`other@1.0.0`).
281
+ * Returns `null` for local/workspace links.
282
+ */
283
+ function pnpmRef(depName, ref) {
284
+ if (typeof ref !== 'string')
285
+ return null;
286
+ if (PNPM_LOCAL.test(ref))
287
+ return null;
288
+ if (/^\d/.test(ref))
289
+ return `${depName}@${stripPnpmPeers(ref)}`;
290
+ const parsed = parsePnpmKey(ref);
291
+ return parsed ? `${parsed.name}@${parsed.version}` : null;
292
+ }
293
+ /** The version of an importer entry: v6/v9 `{ specifier, version }`, v5 a plain string. */
294
+ function importerVersion(entry) {
295
+ if (entry && typeof entry === 'object' && 'version' in entry) {
296
+ return entry.version;
297
+ }
298
+ return entry;
299
+ }
300
+ function asRecord(v) {
301
+ return v && typeof v === 'object' && !Array.isArray(v) ? v : undefined;
302
+ }
303
+ /**
304
+ * Parse a `pnpm-lock.yaml` (lockfile formats 5.x, 6.0 and 9.0) into exact
305
+ * resolved npm dependencies, direct and transitive.
306
+ *
307
+ * - **Roots:** every importer (`importers['.']`, `importers['packages/x']`, …),
308
+ * or, in single-project v5/v6 files, the top-level `dependencies` /
309
+ * `devDependencies` / `optionalDependencies`. Workspace and local links
310
+ * (`link:`, `file:`, `workspace:`) are the project's own packages and are
311
+ * not listed.
312
+ * - **Scope:** by reachability from the roots (prod → `required`, optional
313
+ * roots → `optional`, dev-only → `dev`); a package no root reaches keeps
314
+ * pnpm v5/v6's own `dev` / `optional` flag.
315
+ * - **direct:** `true` for a package an importer declares, else `false`.
316
+ *
317
+ * Peer-dependency variants of one version (`a@1.0.0(react@17)`,
318
+ * `a@1.0.0(react@18)`) collapse into one entry. Malformed YAML yields `[]`.
319
+ */
320
+ export function parsePnpmLockDependencies(pnpmLock) {
321
+ if (!pnpmLock)
322
+ return [];
323
+ let root;
324
+ try {
325
+ root = asRecord(parseYaml(pnpmLock, { uniqueKeys: false }));
326
+ }
327
+ catch {
328
+ return [];
329
+ }
330
+ if (!root)
331
+ return [];
332
+ const nodes = new Map();
333
+ const addNode = (rawKey, info) => {
334
+ const id = parsePnpmKey(rawKey);
335
+ if (!id)
336
+ return;
337
+ const key = `${id.name}@${id.version}`;
338
+ const node = nodes.get(key) ?? { name: id.name, version: id.version, deps: [] };
339
+ if (info) {
340
+ for (const section of ['dependencies', 'optionalDependencies']) {
341
+ for (const [n, ref] of Object.entries(asRecord(info[section]) ?? {})) {
342
+ const k = pnpmRef(n, ref);
343
+ if (k && !node.deps.includes(k))
344
+ node.deps.push(k);
345
+ }
346
+ }
347
+ if (info.dev === true)
348
+ node.flagDev = true;
349
+ if (info.optional === true)
350
+ node.flagOptional = true;
351
+ }
352
+ nodes.set(key, node);
353
+ };
354
+ for (const [k, v] of Object.entries(asRecord(root.packages) ?? {}))
355
+ addNode(k, asRecord(v));
356
+ // v9 keeps the graph in `snapshots`, identities in `packages`.
357
+ for (const [k, v] of Object.entries(asRecord(root.snapshots) ?? {}))
358
+ addNode(k, asRecord(v));
359
+ const roots = { required: [], optional: [], dev: [] };
360
+ const importers = asRecord(root.importers);
361
+ const projects = importers
362
+ ? Object.values(importers).map(asRecord).filter((x) => Boolean(x))
363
+ : [root];
364
+ for (const project of projects) {
365
+ for (const [section, bucket] of [
366
+ ['dependencies', roots.required],
367
+ ['optionalDependencies', roots.optional],
368
+ ['devDependencies', roots.dev],
369
+ ]) {
370
+ for (const [n, entry] of Object.entries(asRecord(project[section]) ?? {})) {
371
+ const k = pnpmRef(n, importerVersion(entry));
372
+ if (k)
373
+ bucket.push(k);
374
+ }
375
+ }
376
+ }
377
+ return scopeLockNodes(nodes, roots, true);
378
+ }
379
+ /**
380
+ * The project directories a `pnpm-lock.yaml` covers, relative to the
381
+ * lockfile (`.` for the root). A workspace lockfile resolves every listed
382
+ * package, so it supersedes each of their `package.json` files (DT4).
383
+ * Single-project lockfiles return `['.']`; malformed YAML returns `[]`.
384
+ */
385
+ export function pnpmLockImporters(pnpmLock) {
386
+ if (!pnpmLock)
387
+ return [];
388
+ let root;
389
+ try {
390
+ root = asRecord(parseYaml(pnpmLock, { uniqueKeys: false }));
391
+ }
392
+ catch {
393
+ return [];
394
+ }
395
+ if (!root)
396
+ return [];
397
+ const importers = asRecord(root.importers);
398
+ return importers ? Object.keys(importers).sort() : ['.'];
399
+ }
400
+ /** Unquote a yarn v1 token: `"@babel/core@^7.0.0"` → `@babel/core@^7.0.0`. */
401
+ function yarnUnquote(s) {
402
+ const t = s.trim();
403
+ return t.length >= 2 && t.startsWith('"') && t.endsWith('"') ? t.slice(1, -1) : t;
404
+ }
405
+ /**
406
+ * Parse a Yarn classic (v1) `yarn.lock` into exact resolved npm
407
+ * dependencies. Yarn v1 records no dev or direct marker, so:
408
+ *
409
+ * - with only the lockfile, every package is `required` and `direct` is
410
+ * absent (unknown);
411
+ * - with the project's `package.json` as the second argument, scope and
412
+ * `direct` come from reachability, as for pnpm.
413
+ *
414
+ * Yarn Berry (v2+, a YAML file with `__metadata`) is not handled and yields
415
+ * `[]`. Malformed entries are skipped.
416
+ */
417
+ export function parseYarnLockDependencies(yarnLock, packageJson) {
418
+ if (!yarnLock)
419
+ return [];
420
+ if (/^__metadata:/m.test(yarnLock))
421
+ return [];
422
+ const nodes = new Map();
423
+ const specToKey = new Map();
424
+ const pendingDeps = [];
425
+ let specs = [];
426
+ let version;
427
+ let deps = [];
428
+ let inDeps = false;
429
+ const flush = () => {
430
+ if (specs.length > 0 && version) {
431
+ const first = splitAt(specs[0]);
432
+ if (first) {
433
+ const key = `${first.name}@${version}`;
434
+ if (!nodes.has(key))
435
+ nodes.set(key, { name: first.name, version, deps: [] });
436
+ for (const s of specs)
437
+ specToKey.set(s, key);
438
+ pendingDeps.push([key, deps]);
439
+ }
440
+ }
441
+ specs = [];
442
+ version = undefined;
443
+ deps = [];
444
+ inDeps = false;
445
+ };
446
+ for (const rawLine of yarnLock.split(/\r?\n/)) {
447
+ if (!rawLine.trim() || rawLine.trimStart().startsWith('#'))
448
+ continue;
449
+ const indent = rawLine.length - rawLine.trimStart().length;
450
+ const line = rawLine.trim();
451
+ if (indent === 0) {
452
+ flush();
453
+ if (!line.endsWith(':'))
454
+ continue;
455
+ specs = line.slice(0, -1).split(',').map(yarnUnquote).filter(Boolean);
456
+ continue;
457
+ }
458
+ if (indent <= 2) {
459
+ inDeps = false;
460
+ const m = line.match(/^(\S+)\s+(.+)$/);
461
+ if (line === 'dependencies:' || line === 'optionalDependencies:') {
462
+ inDeps = true;
463
+ }
464
+ else if (m && yarnUnquote(m[1]) === 'version') {
465
+ version = yarnUnquote(m[2]);
466
+ }
467
+ continue;
468
+ }
469
+ if (inDeps) {
470
+ const m = line.match(/^(\S+)\s+(.+)$/);
471
+ if (m)
472
+ deps.push(`${yarnUnquote(m[1])}@${yarnUnquote(m[2])}`);
473
+ }
474
+ }
475
+ flush();
476
+ for (const [key, ds] of pendingDeps) {
477
+ const node = nodes.get(key);
478
+ if (!node)
479
+ continue;
480
+ for (const s of ds) {
481
+ const k = specToKey.get(s);
482
+ if (k && !node.deps.includes(k))
483
+ node.deps.push(k);
484
+ }
485
+ }
486
+ const roots = { required: [], optional: [], dev: [] };
487
+ let hasRoots = false;
488
+ if (packageJson) {
489
+ try {
490
+ const pkg = asRecord(JSON.parse(packageJson));
491
+ if (pkg) {
492
+ hasRoots = true;
493
+ for (const [section, bucket] of [
494
+ ['dependencies', roots.required],
495
+ ['peerDependencies', roots.required],
496
+ ['optionalDependencies', roots.optional],
497
+ ['devDependencies', roots.dev],
498
+ ]) {
499
+ for (const [n, range] of Object.entries(asRecord(pkg[section]) ?? {})) {
500
+ if (typeof range !== 'string')
501
+ continue;
502
+ const k = specToKey.get(`${n}@${range}`);
503
+ if (k)
504
+ bucket.push(k);
505
+ }
506
+ }
507
+ }
508
+ }
509
+ catch {
510
+ hasRoots = false;
511
+ }
512
+ }
513
+ return scopeLockNodes(nodes, roots, hasRoots);
514
+ }
188
515
  /**
189
516
  * Parse a TOML `[[package]]` array (the shared shape of `Cargo.lock` and
190
517
  * `poetry.lock`): a sequence of blocks each carrying `name = "..."` and
@@ -404,6 +731,125 @@ export function parseMavenDependencies(pomXml) {
404
731
  }
405
732
  return out;
406
733
  }
734
+ /**
735
+ * Parse a Gradle dependency lockfile — the resolved, exact versions of every
736
+ * direct and transitive dependency, per configuration. Two layouts:
737
+ *
738
+ * - **`gradle.lockfile`** (Gradle 6.4+, one file per project):
739
+ * `group:artifact:version=conf1,conf2,…`; the `empty=…` line lists
740
+ * configurations that resolved nothing and is skipped.
741
+ * - **`gradle/dependency-locks/<configuration>.lockfile`** (older, one file
742
+ * per configuration): bare `group:artifact:version` lines. The
743
+ * configuration is the file name, which the caller may pass as
744
+ * `configuration`; without it the entries count as `required`.
745
+ *
746
+ * A dependency is `dev` only when every configuration that resolved it is a
747
+ * test configuration (`test…`, or `…Test…` such as `integrationTestRuntimeClasspath`);
748
+ * any other configuration makes it `required`. Comment lines (`#`) are skipped.
749
+ */
750
+ export function parseGradleLockfile(lockfile, configuration) {
751
+ if (!lockfile)
752
+ return [];
753
+ const out = [];
754
+ const seen = new Set();
755
+ for (const raw of lockfile.split(/\r?\n/)) {
756
+ const line = raw.trim();
757
+ if (!line || line.startsWith('#'))
758
+ continue;
759
+ const eq = line.indexOf('=');
760
+ const coordPart = eq >= 0 ? line.slice(0, eq).trim() : line;
761
+ if (coordPart === 'empty')
762
+ continue;
763
+ const coord = coordPart.split(':');
764
+ if (coord.length !== 3 || coord.some((c) => !c))
765
+ continue;
766
+ const configs = eq >= 0
767
+ ? line.slice(eq + 1).split(',').map((c) => c.trim()).filter(Boolean)
768
+ : configuration
769
+ ? [configuration]
770
+ : [];
771
+ const scope = configs.length > 0 && configs.every(isGradleTestConfiguration) ? 'dev' : 'required';
772
+ const name = `${coord[0]}:${coord[1]}`;
773
+ const key = `${name}|${coord[2]}|${scope}`;
774
+ if (seen.has(key))
775
+ continue;
776
+ seen.add(key);
777
+ out.push(dep('maven', name, coord[2], scope));
778
+ }
779
+ return out;
780
+ }
781
+ function isGradleTestConfiguration(config) {
782
+ return /^test[A-Z]?/.test(config) || /Test[A-Z]/.test(config);
783
+ }
784
+ /** Maven scopes as printed by `dependency:list` / `dependency:tree`. */
785
+ const MAVEN_SCOPES = 'compile|provided|runtime|test|system|import';
786
+ /**
787
+ * Coordinate as Maven prints it in resolved output:
788
+ * `groupId:artifactId:type[:classifier]:version:scope`.
789
+ */
790
+ const MAVEN_RESOLVED_COORD = new RegExp(`^([\\w.-]+):([\\w.-]+):([\\w.-]+)(?::([\\w.-]+))?:([^:\\s()]+):(${MAVEN_SCOPES})\\b(.*)$`);
791
+ /**
792
+ * Parse the text output of `mvn dependency:list` or `mvn dependency:tree`
793
+ * (console or `-DoutputFile`): the dependency set Maven actually resolved,
794
+ * direct and transitive, after mediation. A `pom.xml` alone cannot give this —
795
+ * versions come from parents, BOMs and nearest-wins mediation — so the caller
796
+ * runs Maven (in a build sandbox) and hands the text here; the engine never
797
+ * runs a build.
798
+ *
799
+ * Accepts `[INFO]` prefixes, tree drawing (`+-`, `|`, `\-`), the module
800
+ * suffix newer plugins print (`-- module x`), and the root project line of a
801
+ * tree (skipped: it has no scope). Verbose-tree entries that Maven did **not**
802
+ * resolve (`omitted for conflict with …`) are skipped; `omitted for duplicate`
803
+ * entries collapse into the one kept. `test` / `provided` → `dev`, a trailing
804
+ * `(optional)` → `optional`, everything else → `required`. From a tree, each
805
+ * entry also carries `direct` and `via` (its path from a direct dependency).
806
+ */
807
+ export function parseMavenResolvedDependencies(output) {
808
+ if (!output)
809
+ return [];
810
+ const out = [];
811
+ const seen = new Set();
812
+ const lines = output.split(/\r?\n/).map((raw) => raw.replace(/^\s*\[(?:INFO|WARNING|DEBUG)\]\s?/, ''));
813
+ // A tree carries the graph: depth from indentation (3 columns per level),
814
+ // the path from the stack of ancestors. A list does not.
815
+ const isTree = lines.some((l) => /^[\s|]*[+\\]- /.test(l));
816
+ const ancestors = [];
817
+ for (const raw of lines) {
818
+ const depth = isTree ? Math.round(raw.search(/[A-Za-z0-9(]/) / 3) : -1;
819
+ if (/omitted for conflict/.test(raw))
820
+ continue;
821
+ // Strip tree drawing and the verbose-tree parenthesis around an entry.
822
+ const line = raw.replace(/^[\s|+\\`-]*/, '').replace(/^\(/, '').trim();
823
+ const m = MAVEN_RESOLVED_COORD.exec(line);
824
+ if (!m) {
825
+ // A root (project) line of a tree starts a new graph.
826
+ if (isTree && depth === 0 && /^[\w.-]+:[\w.-]+:/.test(line))
827
+ ancestors.length = 0;
828
+ continue;
829
+ }
830
+ const [, gid, aid, , , version, mvnScope, rest] = m;
831
+ const name = `${gid}:${aid}`;
832
+ let via;
833
+ if (isTree && depth >= 1) {
834
+ ancestors.length = depth - 1;
835
+ via = ancestors.slice();
836
+ ancestors.push(name);
837
+ }
838
+ const optional = /\(optional\)/.test(rest);
839
+ const scope = mvnScope === 'test' || mvnScope === 'provided' ? 'dev' : optional ? 'optional' : 'required';
840
+ const key = `${name}|${version}|${scope}`;
841
+ if (seen.has(key))
842
+ continue;
843
+ seen.add(key);
844
+ const d = dep('maven', name, version, scope);
845
+ if (via) {
846
+ d.direct = via.length === 0;
847
+ d.via = via;
848
+ }
849
+ out.push(d);
850
+ }
851
+ return out;
852
+ }
407
853
  /**
408
854
  * Parse a Cargo `Cargo.toml`. `[dependencies]` → required,
409
855
  * `[dev-dependencies]` → dev, `[build-dependencies]` → optional. Handles both
@@ -503,15 +949,29 @@ export function collectDependencies(ctx) {
503
949
  if (!ctx)
504
950
  return [];
505
951
  const all = [];
506
- if (ctx.js?.packageJson)
952
+ // npm resolved sets (exact + transitive) supersede `package.json` (DT4):
953
+ // pnpm > package-lock > yarn when more than one is supplied.
954
+ if (ctx.js?.pnpmLock)
955
+ all.push(...parsePnpmLockDependencies(ctx.js.pnpmLock));
956
+ else if (ctx.js?.packageLock)
957
+ all.push(...parseNpmLockDependencies(ctx.js.packageLock));
958
+ else if (ctx.js?.yarnLock)
959
+ all.push(...parseYarnLockDependencies(ctx.js.yarnLock, ctx.js.packageJson));
960
+ else if (ctx.js?.packageJson)
507
961
  all.push(...parseNpmDependencies(ctx.js.packageJson));
508
962
  if (ctx.python?.requirementsTxt)
509
963
  all.push(...parsePypiDependencies(ctx.python.requirementsTxt));
510
964
  if (ctx.python?.pyprojectToml)
511
965
  all.push(...parsePyprojectDependencies(ctx.python.pyprojectToml));
512
- if (ctx.java?.pomXml)
966
+ // Resolved Java dependency sets (exact + transitive) supersede the declared
967
+ // manifest they came from, as a lockfile does for npm, Cargo and Poetry.
968
+ if (ctx.java?.mavenResolved)
969
+ all.push(...parseMavenResolvedDependencies(ctx.java.mavenResolved));
970
+ else if (ctx.java?.pomXml)
513
971
  all.push(...parseMavenDependencies(ctx.java.pomXml));
514
- if (ctx.java?.buildGradle)
972
+ if (ctx.java?.gradleLockfile)
973
+ all.push(...parseGradleLockfile(ctx.java.gradleLockfile));
974
+ else if (ctx.java?.buildGradle)
515
975
  all.push(...parseGradleDependencies(ctx.java.buildGradle));
516
976
  if (ctx.rust?.cargoToml)
517
977
  all.push(...parseCargoDependencies(ctx.rust.cargoToml));
@@ -526,6 +986,57 @@ export function collectDependencies(ctx) {
526
986
  }
527
987
  return deduped;
528
988
  }
989
+ /**
990
+ * The licence a package's own `package.json` declares, as one expression:
991
+ * `license` (string, or the legacy `{ type }` object), else the legacy
992
+ * `licenses: [{ type }]` array joined with ` OR `. `undefined` when the text
993
+ * does not parse or declares none.
994
+ */
995
+ export function licenseFromPackageJson(packageJson) {
996
+ let pkg;
997
+ try {
998
+ pkg = JSON.parse(packageJson);
999
+ }
1000
+ catch {
1001
+ return undefined;
1002
+ }
1003
+ if (!pkg || typeof pkg !== 'object')
1004
+ return undefined;
1005
+ const rec = pkg;
1006
+ const one = (v) => {
1007
+ if (typeof v === 'string' && v.trim())
1008
+ return v.trim();
1009
+ if (v && typeof v === 'object') {
1010
+ const t = v.type;
1011
+ if (typeof t === 'string' && t.trim())
1012
+ return t.trim();
1013
+ }
1014
+ return undefined;
1015
+ };
1016
+ const direct = one(rec.license);
1017
+ if (direct)
1018
+ return direct;
1019
+ if (Array.isArray(rec.licenses)) {
1020
+ const all = rec.licenses.map(one).filter((x) => !!x);
1021
+ if (all.length === 1)
1022
+ return all[0];
1023
+ if (all.length > 1)
1024
+ return `(${all.join(' OR ')})`;
1025
+ }
1026
+ return undefined;
1027
+ }
1028
+ /**
1029
+ * Fill in `license` from an offline map for dependencies that have none.
1030
+ * Returns new objects; an existing `license` is never overwritten.
1031
+ */
1032
+ export function applyLicenses(deps, map) {
1033
+ return deps.map((d) => {
1034
+ if (d.license)
1035
+ return d;
1036
+ const lic = map[`${d.name}@${d.version}`] ?? map[d.name];
1037
+ return lic ? { ...d, license: lic } : d;
1038
+ });
1039
+ }
529
1040
  // ---------------------------------------------------------------------------
530
1041
  // emitters
531
1042
  // ---------------------------------------------------------------------------