@faicad/faijs 0.13.2 → 0.14.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 (285) hide show
  1. package/dist/api/api-namespace.d.ts.map +1 -1
  2. package/dist/api/api-namespace.js +30 -4
  3. package/dist/api/api-namespace.js.map +1 -1
  4. package/dist/api/boolean.d.ts +19 -0
  5. package/dist/api/boolean.d.ts.map +1 -1
  6. package/dist/api/boolean.js +51 -10
  7. package/dist/api/boolean.js.map +1 -1
  8. package/dist/api/chamfer.d.ts.map +1 -1
  9. package/dist/api/chamfer.js +8 -7
  10. package/dist/api/chamfer.js.map +1 -1
  11. package/dist/api/compound-geom.d.ts +45 -0
  12. package/dist/api/compound-geom.d.ts.map +1 -0
  13. package/dist/api/compound-geom.js +85 -0
  14. package/dist/api/compound-geom.js.map +1 -0
  15. package/dist/api/compound.d.ts +2 -0
  16. package/dist/api/compound.d.ts.map +1 -1
  17. package/dist/api/compound.js +2 -0
  18. package/dist/api/compound.js.map +1 -1
  19. package/dist/api/copy.d.ts +1 -0
  20. package/dist/api/copy.d.ts.map +1 -1
  21. package/dist/api/copy.js +4 -2
  22. package/dist/api/copy.js.map +1 -1
  23. package/dist/api/edge-ref.d.ts.map +1 -1
  24. package/dist/api/edge-ref.js +7 -4
  25. package/dist/api/edge-ref.js.map +1 -1
  26. package/dist/api/engrave.d.ts.map +1 -1
  27. package/dist/api/engrave.js +1 -0
  28. package/dist/api/engrave.js.map +1 -1
  29. package/dist/api/extrude.d.ts.map +1 -1
  30. package/dist/api/extrude.js +131 -3
  31. package/dist/api/extrude.js.map +1 -1
  32. package/dist/api/face-ref.d.ts.map +1 -1
  33. package/dist/api/face-ref.js +7 -4
  34. package/dist/api/face-ref.js.map +1 -1
  35. package/dist/api/fai_drill.d.ts +1 -1
  36. package/dist/api/fai_drill.d.ts.map +1 -1
  37. package/dist/api/fai_drill.js +31 -18
  38. package/dist/api/fai_drill.js.map +1 -1
  39. package/dist/api/fai_extrude.d.ts +1 -1
  40. package/dist/api/fai_extrude.d.ts.map +1 -1
  41. package/dist/api/fai_extrude.js +2 -1
  42. package/dist/api/fai_extrude.js.map +1 -1
  43. package/dist/api/fai_split.d.ts +1 -1
  44. package/dist/api/fai_split.d.ts.map +1 -1
  45. package/dist/api/fai_split.js +2 -1
  46. package/dist/api/fai_split.js.map +1 -1
  47. package/dist/api/fillet.d.ts.map +1 -1
  48. package/dist/api/fillet.js +6 -5
  49. package/dist/api/fillet.js.map +1 -1
  50. package/dist/api/generated/operations.js +16 -16
  51. package/dist/api/generated/operations.js.map +1 -1
  52. package/dist/api/generated/script-face-manifest.d.ts.map +1 -1
  53. package/dist/api/generated/script-face-manifest.js +0 -1
  54. package/dist/api/generated/script-face-manifest.js.map +1 -1
  55. package/dist/api/generated/script-face.d.ts +1 -4
  56. package/dist/api/generated/script-face.d.ts.map +1 -1
  57. package/dist/api/generated/script-face.js +2 -3
  58. package/dist/api/generated/script-face.js.map +1 -1
  59. package/dist/api/generated/sketching.js +1 -1
  60. package/dist/api/generated/sketching.js.map +1 -1
  61. package/dist/api/generated/topology.d.ts +0 -48
  62. package/dist/api/generated/topology.d.ts.map +1 -1
  63. package/dist/api/generated/topology.js +20 -62
  64. package/dist/api/generated/topology.js.map +1 -1
  65. package/dist/api/import-brep.d.ts +36 -0
  66. package/dist/api/import-brep.d.ts.map +1 -0
  67. package/dist/api/import-brep.js +72 -0
  68. package/dist/api/import-brep.js.map +1 -0
  69. package/dist/api/import-step.d.ts +28 -0
  70. package/dist/api/import-step.d.ts.map +1 -0
  71. package/dist/api/import-step.js +57 -0
  72. package/dist/api/import-step.js.map +1 -0
  73. package/dist/api/index.d.ts +7 -2
  74. package/dist/api/index.d.ts.map +1 -1
  75. package/dist/api/index.js +19 -5
  76. package/dist/api/index.js.map +1 -1
  77. package/dist/api/internal/compat-op.d.ts.map +1 -1
  78. package/dist/api/internal/compat-op.js +2 -1
  79. package/dist/api/internal/compat-op.js.map +1 -1
  80. package/dist/api/knurl.d.ts.map +1 -1
  81. package/dist/api/knurl.js +1 -0
  82. package/dist/api/knurl.js.map +1 -1
  83. package/dist/api/load.d.ts +3 -1
  84. package/dist/api/load.d.ts.map +1 -1
  85. package/dist/api/load.js +3 -1
  86. package/dist/api/load.js.map +1 -1
  87. package/dist/api/pattern.d.ts +32 -0
  88. package/dist/api/pattern.d.ts.map +1 -0
  89. package/dist/api/pattern.js +121 -0
  90. package/dist/api/pattern.js.map +1 -0
  91. package/dist/api/place.d.ts +35 -0
  92. package/dist/api/place.d.ts.map +1 -0
  93. package/dist/api/place.js +113 -0
  94. package/dist/api/place.js.map +1 -0
  95. package/dist/api/primitives.d.ts.map +1 -1
  96. package/dist/api/primitives.js +10 -3
  97. package/dist/api/primitives.js.map +1 -1
  98. package/dist/api/screw.d.ts.map +1 -1
  99. package/dist/api/screw.js +1 -0
  100. package/dist/api/screw.js.map +1 -1
  101. package/dist/api/sdf.d.ts.map +1 -1
  102. package/dist/api/sdf.js +1 -0
  103. package/dist/api/sdf.js.map +1 -1
  104. package/dist/api/sketch.d.ts.map +1 -1
  105. package/dist/api/sketch.js +1 -0
  106. package/dist/api/sketch.js.map +1 -1
  107. package/dist/api/split.d.ts +31 -0
  108. package/dist/api/split.d.ts.map +1 -0
  109. package/dist/api/split.js +96 -0
  110. package/dist/api/split.js.map +1 -0
  111. package/dist/api/surface/arg-spec.d.ts +9 -0
  112. package/dist/api/surface/arg-spec.d.ts.map +1 -1
  113. package/dist/api/surface/arg-spec.js +49 -30
  114. package/dist/api/surface/arg-spec.js.map +1 -1
  115. package/dist/api/svgExtrude.d.ts.map +1 -1
  116. package/dist/api/svgExtrude.js +1 -0
  117. package/dist/api/svgExtrude.js.map +1 -1
  118. package/dist/api/text.d.ts.map +1 -1
  119. package/dist/api/text.js +1 -0
  120. package/dist/api/text.js.map +1 -1
  121. package/dist/api/topo-resolve.d.ts.map +1 -1
  122. package/dist/api/topo-resolve.js +65 -3
  123. package/dist/api/topo-resolve.js.map +1 -1
  124. package/dist/api/transform.d.ts +4 -0
  125. package/dist/api/transform.d.ts.map +1 -1
  126. package/dist/api/transform.js +44 -12
  127. package/dist/api/transform.js.map +1 -1
  128. package/dist/brep/brep-ops.d.ts +34 -1
  129. package/dist/brep/brep-ops.d.ts.map +1 -1
  130. package/dist/brep/brep-ops.js +62 -22
  131. package/dist/brep/brep-ops.js.map +1 -1
  132. package/dist/brep/engine/adapters/brep-mock.d.ts.map +1 -1
  133. package/dist/brep/engine/adapters/brep-mock.js +8 -0
  134. package/dist/brep/engine/adapters/brep-mock.js.map +1 -1
  135. package/dist/brep/engine/adapters/brepkit.d.ts.map +1 -1
  136. package/dist/brep/engine/adapters/brepkit.js +6 -1
  137. package/dist/brep/engine/adapters/brepkit.js.map +1 -1
  138. package/dist/brep/engine/adapters/occt.d.ts.map +1 -1
  139. package/dist/brep/engine/adapters/occt.js +24 -1
  140. package/dist/brep/engine/adapters/occt.js.map +1 -1
  141. package/dist/brep/engine/primitives.d.ts +17 -0
  142. package/dist/brep/engine/primitives.d.ts.map +1 -1
  143. package/dist/brep/engine/primitives.js.map +1 -1
  144. package/dist/brep/engine/types.d.ts +49 -6
  145. package/dist/brep/engine/types.d.ts.map +1 -1
  146. package/dist/brep/engine/types.js.map +1 -1
  147. package/dist/brep/export/step.d.ts.map +1 -1
  148. package/dist/brep/export/step.js +6 -1
  149. package/dist/brep/export/step.js.map +1 -1
  150. package/dist/brep/face-evolution.d.ts +78 -8
  151. package/dist/brep/face-evolution.d.ts.map +1 -1
  152. package/dist/brep/face-evolution.js +130 -17
  153. package/dist/brep/face-evolution.js.map +1 -1
  154. package/dist/brepkit-kernel/brepkitKernel.d.ts +1 -1
  155. package/dist/brepkit-kernel/brepkitKernel.d.ts.map +1 -1
  156. package/dist/brepkit-kernel/brepkitKernel.js +27 -1
  157. package/dist/brepkit-kernel/brepkitKernel.js.map +1 -1
  158. package/dist/cad-runtime/admit-compat-lib.d.ts +19 -1
  159. package/dist/cad-runtime/admit-compat-lib.d.ts.map +1 -1
  160. package/dist/cad-runtime/admit-compat-lib.js +21 -1
  161. package/dist/cad-runtime/admit-compat-lib.js.map +1 -1
  162. package/dist/cad-runtime/backend-dispatch.d.ts +44 -3
  163. package/dist/cad-runtime/backend-dispatch.d.ts.map +1 -1
  164. package/dist/cad-runtime/backend-dispatch.js +46 -3
  165. package/dist/cad-runtime/backend-dispatch.js.map +1 -1
  166. package/dist/cad-runtime/browser-lib-loader.d.ts +2 -0
  167. package/dist/cad-runtime/browser-lib-loader.d.ts.map +1 -1
  168. package/dist/cad-runtime/browser-lib-loader.js +9 -1
  169. package/dist/cad-runtime/browser-lib-loader.js.map +1 -1
  170. package/dist/cad-runtime/direct-executor.d.ts.map +1 -1
  171. package/dist/cad-runtime/direct-executor.js +14 -4
  172. package/dist/cad-runtime/direct-executor.js.map +1 -1
  173. package/dist/cad-runtime/module-registry.d.ts +3 -1
  174. package/dist/cad-runtime/module-registry.d.ts.map +1 -1
  175. package/dist/cad-runtime/module-registry.js +13 -1
  176. package/dist/cad-runtime/module-registry.js.map +1 -1
  177. package/dist/cad-runtime/ports.d.ts +7 -0
  178. package/dist/cad-runtime/ports.d.ts.map +1 -1
  179. package/dist/cad-runtime/ports.js.map +1 -1
  180. package/dist/cad-runtime/runtime-concurrent-error.d.ts +38 -0
  181. package/dist/cad-runtime/runtime-concurrent-error.d.ts.map +1 -0
  182. package/dist/cad-runtime/runtime-concurrent-error.js +43 -0
  183. package/dist/cad-runtime/runtime-concurrent-error.js.map +1 -0
  184. package/dist/cad-runtime/runtime.d.ts +54 -2
  185. package/dist/cad-runtime/runtime.d.ts.map +1 -1
  186. package/dist/cad-runtime/runtime.js +186 -10
  187. package/dist/cad-runtime/runtime.js.map +1 -1
  188. package/dist/define-op.d.ts +22 -0
  189. package/dist/define-op.d.ts.map +1 -1
  190. package/dist/define-op.js +82 -12
  191. package/dist/define-op.js.map +1 -1
  192. package/dist/fcstd/attachment.d.ts +8 -1
  193. package/dist/fcstd/attachment.d.ts.map +1 -1
  194. package/dist/fcstd/attachment.js +7 -1
  195. package/dist/fcstd/attachment.js.map +1 -1
  196. package/dist/fcstd/build-fai-zip.d.ts.map +1 -1
  197. package/dist/fcstd/build-fai-zip.js +12 -7
  198. package/dist/fcstd/build-fai-zip.js.map +1 -1
  199. package/dist/fcstd/codegen.d.ts +10 -1
  200. package/dist/fcstd/codegen.d.ts.map +1 -1
  201. package/dist/fcstd/codegen.js +150 -48
  202. package/dist/fcstd/codegen.js.map +1 -1
  203. package/dist/fcstd/contour.d.ts.map +1 -1
  204. package/dist/fcstd/contour.js +23 -24
  205. package/dist/fcstd/contour.js.map +1 -1
  206. package/dist/fcstd/convert.d.ts +2 -18
  207. package/dist/fcstd/convert.d.ts.map +1 -1
  208. package/dist/fcstd/convert.js +35 -53
  209. package/dist/fcstd/convert.js.map +1 -1
  210. package/dist/fcstd/external-geo.d.ts +12 -1
  211. package/dist/fcstd/external-geo.d.ts.map +1 -1
  212. package/dist/fcstd/external-geo.js +15 -1
  213. package/dist/fcstd/external-geo.js.map +1 -1
  214. package/dist/fcstd/feature-translate.d.ts +21 -1
  215. package/dist/fcstd/feature-translate.d.ts.map +1 -1
  216. package/dist/fcstd/feature-translate.js +108 -6
  217. package/dist/fcstd/feature-translate.js.map +1 -1
  218. package/dist/fcstd/structural-types.d.ts +83 -0
  219. package/dist/fcstd/structural-types.d.ts.map +1 -0
  220. package/dist/fcstd/structural-types.js +116 -0
  221. package/dist/fcstd/structural-types.js.map +1 -0
  222. package/dist/index.d.ts +1 -0
  223. package/dist/index.d.ts.map +1 -1
  224. package/dist/index.js.map +1 -1
  225. package/dist/lang/determinism-scanner.d.ts +62 -0
  226. package/dist/lang/determinism-scanner.d.ts.map +1 -0
  227. package/dist/lang/determinism-scanner.js +619 -0
  228. package/dist/lang/determinism-scanner.js.map +1 -0
  229. package/dist/lang/symbol-table.generated.d.ts +7 -3
  230. package/dist/lang/symbol-table.generated.d.ts.map +1 -1
  231. package/dist/lang/symbol-table.generated.js +7 -3
  232. package/dist/lang/symbol-table.generated.js.map +1 -1
  233. package/dist/mesh/index.d.ts.map +1 -1
  234. package/dist/mesh/index.js +3 -2
  235. package/dist/mesh/index.js.map +1 -1
  236. package/dist/node-host/cli.d.ts.map +1 -1
  237. package/dist/node-host/cli.js +56 -2
  238. package/dist/node-host/cli.js.map +1 -1
  239. package/dist/runtime-state.d.ts +18 -6
  240. package/dist/runtime-state.d.ts.map +1 -1
  241. package/dist/runtime-state.js +9 -1
  242. package/dist/runtime-state.js.map +1 -1
  243. package/dist/shape.d.ts +15 -0
  244. package/dist/shape.d.ts.map +1 -1
  245. package/dist/shape.js +26 -3
  246. package/dist/shape.js.map +1 -1
  247. package/dist/topology/naming/build-naming.d.ts +14 -30
  248. package/dist/topology/naming/build-naming.d.ts.map +1 -1
  249. package/dist/topology/naming/build-naming.js +21 -54
  250. package/dist/topology/naming/build-naming.js.map +1 -1
  251. package/dist/topology/naming/capture-topo-ref.d.ts.map +1 -1
  252. package/dist/topology/naming/capture-topo-ref.js +5 -0
  253. package/dist/topology/naming/capture-topo-ref.js.map +1 -1
  254. package/dist/topology/naming/index.d.ts +3 -0
  255. package/dist/topology/naming/index.d.ts.map +1 -1
  256. package/dist/topology/naming/index.js +3 -0
  257. package/dist/topology/naming/index.js.map +1 -1
  258. package/dist/topology/naming/lineage-resolve.d.ts +80 -0
  259. package/dist/topology/naming/lineage-resolve.d.ts.map +1 -0
  260. package/dist/topology/naming/lineage-resolve.js +120 -0
  261. package/dist/topology/naming/lineage-resolve.js.map +1 -0
  262. package/dist/topology/naming/lineage.d.ts +349 -0
  263. package/dist/topology/naming/lineage.d.ts.map +1 -0
  264. package/dist/topology/naming/lineage.js +398 -0
  265. package/dist/topology/naming/lineage.js.map +1 -0
  266. package/dist/topology/naming/migrate.d.ts +41 -0
  267. package/dist/topology/naming/migrate.d.ts.map +1 -0
  268. package/dist/topology/naming/migrate.js +88 -0
  269. package/dist/topology/naming/migrate.js.map +1 -0
  270. package/dist/topology/naming/role-name.d.ts +176 -0
  271. package/dist/topology/naming/role-name.d.ts.map +1 -0
  272. package/dist/topology/naming/role-name.js +344 -0
  273. package/dist/topology/naming/role-name.js.map +1 -0
  274. package/dist/topology/naming/roles.d.ts +20 -22
  275. package/dist/topology/naming/roles.d.ts.map +1 -1
  276. package/dist/topology/naming/roles.js +51 -49
  277. package/dist/topology/naming/roles.js.map +1 -1
  278. package/dist/topology/naming/types.d.ts +65 -18
  279. package/dist/topology/naming/types.d.ts.map +1 -1
  280. package/dist/topology/naming/types.js.map +1 -1
  281. package/dist/weapp.d.ts +1 -1
  282. package/dist/weapp.d.ts.map +1 -1
  283. package/dist/weapp.js +1 -1
  284. package/dist/weapp.js.map +1 -1
  285. package/package.json +1 -1
@@ -0,0 +1,619 @@
1
+ /**
2
+ * determinism-scanner — .fai.js determinism gate (B-tier, conservative taint)
3
+ *
4
+ * Decides statically whether any non-deterministic value (`Math.random`,
5
+ * `Date.now` / `new Date`, `crypto.*`, `performance.now`) flows into geometry
6
+ * generation. Conservative policy: anything the analysis cannot prove safe is a
7
+ * violation — "when in doubt, reject".
8
+ *
9
+ * Model:
10
+ * - **sources** produce tainted values.
11
+ * - **propagation** moves taint through arithmetic, member access, assignment,
12
+ * arrays/objects, calls, and implicit control flow.
13
+ * - **sinks** are geometry-generating call sites: members of the `cad`
14
+ * namespace (and any library namespace), named library callees, and unknown
15
+ * function calls (conservative). `console.*` and the pure built-ins are safe
16
+ * containers — taint may pass through them but does not, by itself, violate.
17
+ *
18
+ * This gate is entry prevention; exit equivalence is checked separately by
19
+ * cross-kernel parity tests (docs/reproducibility-contract.md §5).
20
+ */
21
+ import { parse as acornParse } from 'acorn';
22
+ // ── rule tables ──
23
+ /** Containers (identifier → member) whose member access is a non-deterministic source. */
24
+ const SOURCE_MEMBERS = {
25
+ Math: new Set(['random']),
26
+ Date: new Set(['now', 'parse', 'UTC']),
27
+ performance: new Set(['now']),
28
+ crypto: new Set(['getRandomValues', 'randomUUID']),
29
+ };
30
+ /**
31
+ * Identifiers that are themselves non-deterministic values (a bare `Date` or
32
+ * `crypto` reference is a source — `new Date()` / `crypto.subtle` alias paths).
33
+ */
34
+ const SOURCE_IDENTS = new Set(['Date', 'crypto', 'getRandomValues', 'performance']);
35
+ /**
36
+ * Safe containers: member calls on these neither violate nor introduce taint;
37
+ * taint only propagates through their arguments (pure/observable-only globals).
38
+ */
39
+ const SAFE_CONTAINERS = new Set([
40
+ 'console', 'Math', 'JSON', 'Number', 'String', 'Boolean', 'Object', 'Array',
41
+ 'Map', 'Set', 'Symbol', 'RegExp', 'Promise', 'Error', 'Intl', 'Reflect',
42
+ ]);
43
+ /** Safe top-level functions (pure/known, not geometry). */
44
+ const SAFE_FUNCTIONS = new Set([
45
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'String', 'Number', 'Boolean',
46
+ 'encodeURI', 'encodeURIComponent', 'decodeURI', 'decodeURIComponent',
47
+ ]);
48
+ function childScope(parent) {
49
+ return { parent, tainted: new Map() };
50
+ }
51
+ function lookupTaint(scope, name) {
52
+ let s = scope;
53
+ while (s) {
54
+ const v = s.tainted.get(name);
55
+ if (v !== undefined)
56
+ return v;
57
+ s = s.parent;
58
+ }
59
+ return false;
60
+ }
61
+ function markTaint(scope, name) {
62
+ // Write through to the nearest scope that declares `name`, else current scope.
63
+ let s = scope;
64
+ while (s) {
65
+ if (s.tainted.has(name)) {
66
+ s.tainted.set(name, true);
67
+ return;
68
+ }
69
+ s = s.parent;
70
+ }
71
+ scope.tainted.set(name, true);
72
+ }
73
+ /**
74
+ * Static non-determinism scanner (B-tier, conservative).
75
+ * @param code - the `.fai.js` / library source text.
76
+ * @param opts - scan options (geometry namespace/callee hints).
77
+ * @returns scan result with all violations.
78
+ */
79
+ export function scanDeterminism(code, opts = {}) {
80
+ const defaultNs = opts.defaultNs ?? 'cad';
81
+ const geometryNamespaces = new Set([defaultNs, ...(opts.extraNamespaces ?? [])]);
82
+ const geometryCallees = new Set(opts.extraCallees ?? []);
83
+ const violations = [];
84
+ let ast;
85
+ try {
86
+ ast = acornParse(code, {
87
+ ecmaVersion: 'latest',
88
+ sourceType: 'module',
89
+ locations: true,
90
+ });
91
+ }
92
+ catch {
93
+ // A syntax error is not a determinism violation; the main parse path owns
94
+ // syntax diagnostics. Return ok (empty).
95
+ return { ok: true, violations: [] };
96
+ }
97
+ const rootScope = { parent: null, tainted: new Map() };
98
+ function lineOf(node) {
99
+ return node?.loc?.start?.line ?? 1;
100
+ }
101
+ function report(node, source, detail) {
102
+ violations.push({
103
+ lineNo: lineOf(node),
104
+ source,
105
+ message: `[determinism] ${detail} (non-deterministic value reaches geometry)`,
106
+ });
107
+ }
108
+ /** Return the source kind of a call/construct callee, else null. */
109
+ function sourceKindOfCallee(callee) {
110
+ if (callee?.type === 'MemberExpression' &&
111
+ callee.object?.type === 'Identifier' &&
112
+ !callee.computed &&
113
+ callee.property?.type === 'Identifier') {
114
+ const members = SOURCE_MEMBERS[callee.object.name];
115
+ if (members?.has(callee.property.name)) {
116
+ const o = callee.object.name;
117
+ if (o === 'Math')
118
+ return 'Math.random';
119
+ if (o === 'Date')
120
+ return 'Date';
121
+ if (o === 'crypto')
122
+ return callee.property.name === 'randomUUID' ? 'crypto' : 'getRandomValues';
123
+ return 'performance.now';
124
+ }
125
+ }
126
+ if (callee?.type === 'Identifier' && SOURCE_IDENTS.has(callee.name)) {
127
+ if (callee.name === 'performance')
128
+ return 'performance.now';
129
+ if (callee.name === 'getRandomValues')
130
+ return 'getRandomValues';
131
+ return callee.name;
132
+ }
133
+ return null;
134
+ }
135
+ /**
136
+ * Whether a call/construct site is a geometry sink, and if so, check its
137
+ * arguments for taint. Returns the tainted-ness of the call result.
138
+ */
139
+ function handleCall(node, scope) {
140
+ const callee = node.callee;
141
+ let resultTainted = false;
142
+ // A source call (Date.now / Math.random / crypto.* / performance.now)
143
+ // produces a tainted result regardless of its arguments.
144
+ const srcKind = sourceKindOfCallee(callee);
145
+ if (srcKind) {
146
+ lastSource = srcKind;
147
+ // Still walk arguments: nested calls inside may themselves violate.
148
+ for (const a of node.arguments ?? []) {
149
+ evalTaint(a?.type === 'SpreadElement' ? a.argument : a, scope);
150
+ }
151
+ return true;
152
+ }
153
+ // Evaluate argument taint (propagation, eager evaluation).
154
+ const argTaints = [];
155
+ for (const a of node.arguments ?? []) {
156
+ if (a?.type === 'SpreadElement') {
157
+ argTaints.push(evalTaint(a.argument, scope));
158
+ }
159
+ else {
160
+ argTaints.push(evalTaint(a, scope));
161
+ }
162
+ if (argTaints[argTaints.length - 1])
163
+ resultTainted = true;
164
+ }
165
+ // Determine the call target kind.
166
+ // 1) Geometry namespace member call: `cad.box(...)` / `sheet.fn(...)`.
167
+ if (callee?.type === 'MemberExpression') {
168
+ const obj = callee.object;
169
+ const prop = callee.computed ? null : callee.property?.name;
170
+ const objName = obj?.type === 'Identifier' ? obj.name : null;
171
+ if (objName !== null && geometryNamespaces.has(objName)) {
172
+ // Geometry op: any tainted argument violates.
173
+ const taintedArg = argTaints.findIndex((t) => t);
174
+ if (taintedArg >= 0) {
175
+ report(callee, inferSourceForArgs(), `${objName}.${prop ?? '(computed)'} receives a non-deterministic argument`);
176
+ }
177
+ return resultTainted;
178
+ }
179
+ if (objName !== null && SAFE_CONTAINERS.has(objName)) {
180
+ // Safe container (console/math/json/...): propagate only.
181
+ return resultTainted;
182
+ }
183
+ // Object itself tainted (e.g. `d.getSeconds()` where `d = new Date()`)
184
+ // → result conservatively tainted (method may return a dependent value).
185
+ if (objName !== null && evalTaint(obj, scope))
186
+ resultTainted = true;
187
+ }
188
+ // 2) Direct named geometry callee: `gear(...)` from `import { gear }`.
189
+ if (callee?.type === 'Identifier' && geometryCallees.has(callee.name)) {
190
+ const taintedArg = argTaints.findIndex((t) => t);
191
+ if (taintedArg >= 0) {
192
+ report(callee, inferSourceForArgs(), `${callee.name}(...) receives a non-deterministic argument`);
193
+ }
194
+ return resultTainted;
195
+ }
196
+ // 3) Known safe top-level function: propagate only.
197
+ if (callee?.type === 'Identifier' && SAFE_FUNCTIONS.has(callee.name)) {
198
+ return resultTainted;
199
+ }
200
+ // 4) Unknown function call: conservative — a tainted argument may feed
201
+ // geometry, and we cannot prove otherwise.
202
+ const taintedArg = argTaints.findIndex((t) => t);
203
+ if (taintedArg >= 0) {
204
+ report(callee, inferSourceForArgs(), `${calleeName(callee)}(...) receives a non-deterministic argument through an unknown callee`);
205
+ }
206
+ return resultTainted;
207
+ }
208
+ function inferSourceForArgs() {
209
+ // Best-effort attribution; the precise source is tracked per-node via lastSource.
210
+ return lastSource ?? 'Date';
211
+ }
212
+ function calleeName(node) {
213
+ if (!node)
214
+ return '(unknown)';
215
+ if (node.type === 'Identifier')
216
+ return node.name;
217
+ if (node.type === 'MemberExpression') {
218
+ const o = node.object?.type === 'Identifier' ? node.object.name : '?';
219
+ const p = node.computed ? '[...]' : node.property?.name ?? '?';
220
+ return `${o}.${p}`;
221
+ }
222
+ return node.type;
223
+ }
224
+ // Track the most recent source kind seen (for coarse attribution in reports).
225
+ let lastSource;
226
+ /**
227
+ * Evaluate whether an expression is tainted, recording sources as side
228
+ * effects. Memo-free (exponential-free because the AST is a tree).
229
+ */
230
+ function evalTaint(node, scope) {
231
+ if (!node)
232
+ return false;
233
+ switch (node.type) {
234
+ case 'Literal':
235
+ return false;
236
+ case 'Identifier': {
237
+ if (node.name === 'undefined' || node.name === 'NaN' || node.name === 'Infinity')
238
+ return false;
239
+ // Source: bare `Date` / `crypto` / `performance` reference.
240
+ if (SOURCE_IDENTS.has(node.name)) {
241
+ lastSource = node.name !== 'performance' ? node.name : 'performance.now';
242
+ return true;
243
+ }
244
+ return lookupTaint(scope, node.name);
245
+ }
246
+ case 'MemberExpression': {
247
+ // Source: `Math.random`, `Date.now`, `crypto.getRandomValues`, ...
248
+ if (node.object?.type === 'Identifier') {
249
+ const members = SOURCE_MEMBERS[node.object.name];
250
+ if (members && !node.computed && node.property?.type === 'Identifier' && members.has(node.property.name)) {
251
+ const kind = node.object.name === 'Math' ? 'Math.random'
252
+ : node.object.name === 'Date' ? 'Date'
253
+ : node.object.name === 'crypto' ? (node.property.name === 'randomUUID' ? 'crypto' : 'getRandomValues')
254
+ : 'performance.now';
255
+ lastSource = kind;
256
+ return true;
257
+ }
258
+ }
259
+ // Otherwise: taint flows through the object (and, conservatively, the key).
260
+ const objTaint = evalTaint(node.object, scope);
261
+ const keyTaint = node.computed ? evalTaint(node.property, scope) : false;
262
+ return objTaint || keyTaint;
263
+ }
264
+ case 'NewExpression':
265
+ // `new Date(...)` is a source regardless of arguments (timezone/sytem
266
+ // clock dependence). `new crypto.X` likewise.
267
+ if (node.callee?.type === 'Identifier' && SOURCE_IDENTS.has(node.callee.name)) {
268
+ lastSource = node.callee.name === 'Date' ? 'Date' : 'crypto';
269
+ return true;
270
+ }
271
+ return handleCall(node, scope);
272
+ case 'CallExpression':
273
+ return handleCall(node, scope);
274
+ case 'ArrayExpression': {
275
+ let t = false;
276
+ for (const el of node.elements ?? []) {
277
+ if (el && evalTaint(el, scope))
278
+ t = true;
279
+ }
280
+ return t;
281
+ }
282
+ case 'ObjectExpression': {
283
+ let t = false;
284
+ for (const prop of node.properties ?? []) {
285
+ if (prop.type === 'SpreadElement') {
286
+ if (evalTaint(prop.argument, scope))
287
+ t = true;
288
+ }
289
+ else if (prop.value && evalTaint(prop.value, scope)) {
290
+ t = true;
291
+ }
292
+ }
293
+ return t;
294
+ }
295
+ case 'UnaryExpression':
296
+ return evalTaint(node.argument, scope);
297
+ case 'UpdateExpression':
298
+ return evalTaint(node.argument, scope);
299
+ case 'BinaryExpression':
300
+ case 'LogicalExpression':
301
+ return evalTaint(node.left, scope) || evalTaint(node.right, scope);
302
+ case 'ConditionalExpression': {
303
+ const testT = evalTaint(node.test, scope);
304
+ const consT = evalTaint(node.consequent, scope);
305
+ const altT = evalTaint(node.alternate, scope);
306
+ return testT || consT || altT;
307
+ }
308
+ case 'AssignmentExpression': {
309
+ const rhsT = evalTaint(node.right, scope);
310
+ if (rhsT) {
311
+ markAssignTarget(node.left, scope);
312
+ }
313
+ return rhsT;
314
+ }
315
+ case 'SequenceExpression': {
316
+ let t = false;
317
+ for (const e of node.expressions ?? []) {
318
+ if (evalTaint(e, scope))
319
+ t = true;
320
+ }
321
+ return t;
322
+ }
323
+ case 'TemplateLiteral': {
324
+ let t = false;
325
+ for (const expr of node.expressions ?? []) {
326
+ if (evalTaint(expr, scope))
327
+ t = true;
328
+ }
329
+ return t;
330
+ }
331
+ case 'ChainExpression':
332
+ return evalTaint(node.expression, scope);
333
+ case 'AwaitExpression':
334
+ return evalTaint(node.argument, scope);
335
+ case 'SpreadElement':
336
+ return evalTaint(node.argument, scope);
337
+ case 'ArrowFunctionExpression':
338
+ case 'FunctionExpression': {
339
+ const fnScope = childScope(scope);
340
+ for (const p of node.params ?? []) {
341
+ collectPatternTaintDefault(p, fnScope);
342
+ }
343
+ walkFunctionBody(node, fnScope);
344
+ return false;
345
+ }
346
+ default:
347
+ return false;
348
+ }
349
+ }
350
+ /** Mark an assignment/declaration target tainted. */
351
+ function markAssignTarget(left, scope) {
352
+ if (left.type === 'Identifier') {
353
+ markTaint(scope, left.name);
354
+ return;
355
+ }
356
+ if (left.type === 'MemberExpression') {
357
+ // Conservatively taint the base object; the precise key is unknown.
358
+ if (left.object?.type === 'Identifier')
359
+ markTaint(scope, left.object.name);
360
+ return;
361
+ }
362
+ if (left.type === 'ArrayPattern' || left.type === 'ObjectPattern') {
363
+ collectPattern(left, (name) => markTaint(scope, name));
364
+ return;
365
+ }
366
+ }
367
+ /** Collect binding names from a destructuring pattern. */
368
+ function collectPattern(pattern, cb) {
369
+ if (!pattern)
370
+ return;
371
+ if (pattern.type === 'Identifier') {
372
+ cb(pattern.name);
373
+ return;
374
+ }
375
+ if (pattern.type === 'RestElement') {
376
+ collectPattern(pattern.argument, cb);
377
+ return;
378
+ }
379
+ if (pattern.type === 'ArrayPattern') {
380
+ for (const el of pattern.elements ?? [])
381
+ if (el)
382
+ collectPattern(el, cb);
383
+ return;
384
+ }
385
+ if (pattern.type === 'ObjectPattern') {
386
+ for (const prop of pattern.properties ?? []) {
387
+ if (prop.type === 'RestElement')
388
+ collectPattern(prop.argument, cb);
389
+ else if (prop.value)
390
+ collectPattern(prop.value, cb);
391
+ }
392
+ return;
393
+ }
394
+ if (pattern.type === 'AssignmentPattern') {
395
+ collectPattern(pattern.left, cb);
396
+ return;
397
+ }
398
+ }
399
+ /** Register a parameter, evaluating its default for taint. */
400
+ function collectPatternTaintDefault(pattern, scope) {
401
+ if (pattern?.type === 'AssignmentPattern') {
402
+ collectPattern(pattern.left, (name) => {
403
+ scope.tainted.set(name, evalTaint(pattern.right, scope));
404
+ });
405
+ return;
406
+ }
407
+ collectPattern(pattern, (name) => {
408
+ if (!scope.tainted.has(name))
409
+ scope.tainted.set(name, false);
410
+ });
411
+ }
412
+ /** Walk a function body (its own scope; statements inside analyzed eagerly). */
413
+ function walkFunctionBody(node, fnScope) {
414
+ if (node.body?.type === 'BlockStatement')
415
+ walkBody(node.body.body ?? [], fnScope);
416
+ else if (node.body) {
417
+ // Expression body: `x => expr`.
418
+ evalTaint(node.body, fnScope);
419
+ }
420
+ }
421
+ /** Walk a statement list, carrying implicit-flow taint. */
422
+ function walkBody(body, scope, implicitTaint = false) {
423
+ // Hoist declarations first so assignments resolve in this scope.
424
+ for (const stmt of body) {
425
+ if (stmt?.type === 'VariableDeclaration') {
426
+ for (const decl of stmt.declarations ?? []) {
427
+ collectPattern(decl.id, (name) => {
428
+ if (!scope.tainted.has(name))
429
+ scope.tainted.set(name, false);
430
+ });
431
+ }
432
+ }
433
+ if (stmt?.type === 'FunctionDeclaration' && stmt.id?.name) {
434
+ if (!scope.tainted.has(stmt.id.name))
435
+ scope.tainted.set(stmt.id.name, false);
436
+ }
437
+ }
438
+ if (implicitTaint) {
439
+ // Any assignment target inside a tainted control flow becomes tainted.
440
+ for (const stmt of body)
441
+ markStmtTargets(stmt, scope);
442
+ }
443
+ for (const stmt of body)
444
+ walkStmt(stmt, scope);
445
+ }
446
+ /** Conservatively mark every LHS a block writes through as tainted. */
447
+ function markStmtTargets(node, scope) {
448
+ if (!node)
449
+ return;
450
+ switch (node.type) {
451
+ case 'VariableDeclaration':
452
+ for (const d of node.declarations ?? [])
453
+ collectPattern(d.id, (n) => markTaint(scope, n));
454
+ return;
455
+ case 'ExpressionStatement':
456
+ if (node.expression?.type === 'AssignmentExpression')
457
+ markAssignTarget(node.expression.left, scope);
458
+ if (node.expression?.type === 'UpdateExpression')
459
+ markAssignTarget(node.expression.argument, scope);
460
+ if (node.expression?.type === 'CallExpression') {
461
+ // In-place mutation ergonomics: taint the first argument receiver.
462
+ const first = node.expression.arguments?.[0];
463
+ if (first?.type === 'Identifier')
464
+ markTaint(scope, first.name);
465
+ }
466
+ return;
467
+ case 'FunctionDeclaration':
468
+ if (node.id?.name)
469
+ markTaint(scope, node.id.name);
470
+ return;
471
+ default:
472
+ return;
473
+ }
474
+ }
475
+ function walkStmt(node, scope) {
476
+ if (!node)
477
+ return;
478
+ switch (node.type) {
479
+ case 'ExpressionStatement':
480
+ evalTaint(node.expression, scope);
481
+ return;
482
+ case 'VariableDeclaration':
483
+ for (const decl of node.declarations ?? []) {
484
+ if (decl.init && evalTaint(decl.init, scope)) {
485
+ collectPattern(decl.id, (name) => markTaint(scope, name));
486
+ }
487
+ }
488
+ return;
489
+ case 'BlockStatement':
490
+ walkBody(node.body ?? [], childScope(scope));
491
+ return;
492
+ case 'IfStatement': {
493
+ const testT = evalTaint(node.test, scope);
494
+ walkBody(toList(node.consequent), childScope(scope), testT);
495
+ if (node.alternate)
496
+ walkBody(toList(node.alternate), childScope(scope), testT);
497
+ return;
498
+ }
499
+ case 'ForStatement': {
500
+ const forScope = childScope(scope);
501
+ if (node.init?.type === 'VariableDeclaration') {
502
+ for (const d of node.init.declarations ?? []) {
503
+ collectPattern(d.id, (n) => { if (!forScope.tainted.has(n))
504
+ forScope.tainted.set(n, false); });
505
+ if (d.init && evalTaint(d.init, forScope))
506
+ collectPattern(d.id, (n) => markTaint(forScope, n));
507
+ }
508
+ }
509
+ else if (node.init) {
510
+ evalTaint(node.init, forScope);
511
+ }
512
+ const testT = node.test ? evalTaint(node.test, forScope) : false;
513
+ if (node.update)
514
+ evalTaint(node.update, forScope);
515
+ walkBody(toList(node.body), forScope, testT);
516
+ return;
517
+ }
518
+ case 'ForInStatement':
519
+ case 'ForOfStatement': {
520
+ const forScope = childScope(scope);
521
+ const rightT = evalTaint(node.right, forScope);
522
+ if (node.left?.type === 'VariableDeclaration') {
523
+ for (const d of node.left.declarations ?? []) {
524
+ collectPattern(d.id, (n) => { if (!forScope.tainted.has(n))
525
+ forScope.tainted.set(n, false); });
526
+ }
527
+ }
528
+ walkBody(toList(node.body), forScope, rightT);
529
+ return;
530
+ }
531
+ case 'WhileStatement':
532
+ case 'DoWhileStatement': {
533
+ const loopScope = childScope(scope);
534
+ const testT = evalTaint(node.test, loopScope);
535
+ walkBody(toList(node.body), loopScope, testT);
536
+ return;
537
+ }
538
+ case 'SwitchStatement': {
539
+ const swScope = childScope(scope);
540
+ const discT = evalTaint(node.discriminant, swScope);
541
+ for (const c of node.cases ?? []) {
542
+ if (c.test)
543
+ evalTaint(c.test, swScope);
544
+ walkBody(c.consequent ?? [], childScope(swScope), discT);
545
+ }
546
+ return;
547
+ }
548
+ case 'ReturnStatement':
549
+ if (node.argument)
550
+ evalTaint(node.argument, scope);
551
+ return;
552
+ case 'ThrowStatement':
553
+ if (node.argument)
554
+ evalTaint(node.argument, scope);
555
+ return;
556
+ case 'TryStatement': {
557
+ walkBody(toList(node.block?.body ?? []), scope);
558
+ if (node.handler) {
559
+ const catchScope = childScope(scope);
560
+ if (node.handler.param)
561
+ collectPattern(node.handler.param, (n) => { catchScope.tainted.set(n, false); });
562
+ walkBody(node.handler.body?.body ?? [], catchScope);
563
+ }
564
+ if (node.finalizer)
565
+ walkBody(node.finalizer.body ?? [], scope);
566
+ return;
567
+ }
568
+ case 'FunctionDeclaration': {
569
+ const fnScope = childScope(scope);
570
+ for (const p of node.params ?? [])
571
+ collectPatternTaintDefault(p, fnScope);
572
+ walkFunctionBody(node, fnScope);
573
+ return;
574
+ }
575
+ case 'LabeledStatement':
576
+ if (node.body)
577
+ walkStmt(node.body, scope);
578
+ return;
579
+ case 'ImportDeclaration':
580
+ case 'ExportNamedDeclaration':
581
+ case 'ExportDefaultDeclaration':
582
+ // Imports are handled by the caller (namespace/callee hints). A default
583
+ // export with a declaration still needs its body walked.
584
+ if (node.type === 'ExportDefaultDeclaration' && node.declaration && node.declaration.type !== 'Identifier') {
585
+ walkStmt(node.declaration, scope);
586
+ }
587
+ if (node.type === 'ExportNamedDeclaration' && node.declaration) {
588
+ walkStmt(node.declaration, scope);
589
+ }
590
+ return;
591
+ case 'ClassDeclaration': {
592
+ const clsScope = childScope(scope);
593
+ for (const m of node.body?.body ?? []) {
594
+ if (m.value)
595
+ walkFunctionBody(m.value, childScope(clsScope));
596
+ }
597
+ return;
598
+ }
599
+ case 'EmptyStatement':
600
+ case 'BreakStatement':
601
+ case 'ContinueStatement':
602
+ case 'DebuggerStatement':
603
+ return;
604
+ default: {
605
+ // Unknown statement node: conservatively walk children via evalTaint on
606
+ // expression-shaped child properties is error-prone; skip structurally.
607
+ return;
608
+ }
609
+ }
610
+ }
611
+ function toList(node) {
612
+ if (!node)
613
+ return [];
614
+ return node.type === 'BlockStatement' ? (node.body ?? []) : [node];
615
+ }
616
+ walkBody(ast.body ?? [], rootScope);
617
+ return { ok: violations.length === 0, violations };
618
+ }
619
+ //# sourceMappingURL=determinism-scanner.js.map