redscript-mc 2.6.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (636) hide show
  1. package/.github/workflows/ci.yml +11 -0
  2. package/CHANGELOG.md +18 -9
  3. package/README-benchmarks.md +48 -0
  4. package/README-vscode-test.md +251 -0
  5. package/RELEASE_NOTES.md +74 -0
  6. package/ROADMAP.md +131 -167
  7. package/benchmarks/_shared.ts +468 -0
  8. package/benchmarks/baseline.json +2816 -0
  9. package/benchmarks/baseline.md +13 -0
  10. package/benchmarks/compiler-perf.report.json +207 -0
  11. package/benchmarks/compiler-perf.ts +76 -0
  12. package/benchmarks/results.md +13 -0
  13. package/benchmarks/stdlib-complexity.report.json +2606 -0
  14. package/benchmarks/stdlib-complexity.ts +54 -0
  15. package/benchmarks/stdlib-size.md +57 -0
  16. package/benchmarks/stdlib-size.ts +91 -0
  17. package/coverage-report.md +177 -0
  18. package/dist/src/__tests__/budget.test.js +4 -0
  19. package/dist/src/__tests__/cache/cache-behavior.test.d.ts +10 -0
  20. package/dist/src/__tests__/cache/cache-behavior.test.js +425 -0
  21. package/dist/src/__tests__/cache-extra.test.d.ts +5 -0
  22. package/dist/src/__tests__/cache-extra.test.js +211 -0
  23. package/dist/src/__tests__/cli-init.test.d.ts +1 -0
  24. package/dist/src/__tests__/cli-init.test.js +97 -0
  25. package/dist/src/__tests__/cli-publish.test.d.ts +9 -0
  26. package/dist/src/__tests__/cli-publish.test.js +189 -0
  27. package/dist/src/__tests__/cli.test.js +76 -0
  28. package/dist/src/__tests__/compile-preprocess.test.d.ts +11 -0
  29. package/dist/src/__tests__/compile-preprocess.test.js +328 -0
  30. package/dist/src/__tests__/compiler/break-stmt.test.d.ts +1 -0
  31. package/dist/src/__tests__/compiler/break-stmt.test.js +58 -0
  32. package/dist/src/__tests__/compiler/const-decl.test.d.ts +1 -0
  33. package/dist/src/__tests__/compiler/const-decl.test.js +123 -0
  34. package/dist/src/__tests__/compiler/continue-stmt.test.d.ts +1 -0
  35. package/dist/src/__tests__/compiler/continue-stmt.test.js +67 -0
  36. package/dist/src/__tests__/compiler/coroutine-extended.test.d.ts +17 -0
  37. package/dist/src/__tests__/compiler/coroutine-extended.test.js +565 -0
  38. package/dist/src/__tests__/compiler/deprecated.test.d.ts +4 -0
  39. package/dist/src/__tests__/compiler/deprecated.test.js +285 -0
  40. package/dist/src/__tests__/compiler/do-while.test.d.ts +1 -0
  41. package/dist/src/__tests__/compiler/do-while.test.js +120 -0
  42. package/dist/src/__tests__/compiler/enum-payload.test.d.ts +9 -0
  43. package/dist/src/__tests__/compiler/enum-payload.test.js +272 -0
  44. package/dist/src/__tests__/compiler/interface.test.d.ts +10 -0
  45. package/dist/src/__tests__/compiler/interface.test.js +258 -0
  46. package/dist/src/__tests__/compiler/labeled-loops.test.d.ts +1 -0
  47. package/dist/src/__tests__/compiler/labeled-loops.test.js +263 -0
  48. package/dist/src/__tests__/compiler/match-string.test.d.ts +1 -0
  49. package/dist/src/__tests__/compiler/match-string.test.js +43 -0
  50. package/dist/src/__tests__/compiler/memoize.test.d.ts +1 -0
  51. package/dist/src/__tests__/compiler/memoize.test.js +113 -0
  52. package/dist/src/__tests__/compiler/method-chain.test.d.ts +5 -0
  53. package/dist/src/__tests__/compiler/method-chain.test.js +115 -0
  54. package/dist/src/__tests__/compiler/module-import.test.d.ts +12 -0
  55. package/dist/src/__tests__/compiler/module-import.test.js +261 -0
  56. package/dist/src/__tests__/compiler/option-extensions.test.d.ts +6 -0
  57. package/dist/src/__tests__/compiler/option-extensions.test.js +191 -0
  58. package/dist/src/__tests__/compiler/profile-decorator.test.d.ts +1 -0
  59. package/dist/src/__tests__/compiler/profile-decorator.test.js +69 -0
  60. package/dist/src/__tests__/compiler/string-advanced.test.d.ts +7 -0
  61. package/dist/src/__tests__/compiler/string-advanced.test.js +281 -0
  62. package/dist/src/__tests__/compiler/struct-extends.test.d.ts +1 -0
  63. package/dist/src/__tests__/compiler/struct-extends.test.js +95 -0
  64. package/dist/src/__tests__/compiler/throttle-retry.test.d.ts +1 -0
  65. package/dist/src/__tests__/compiler/throttle-retry.test.js +166 -0
  66. package/dist/src/__tests__/compiler/tuple-type.test.d.ts +10 -0
  67. package/dist/src/__tests__/compiler/tuple-type.test.js +229 -0
  68. package/dist/src/__tests__/compiler/watch-decorator.test.d.ts +1 -0
  69. package/dist/src/__tests__/compiler/watch-decorator.test.js +65 -0
  70. package/dist/src/__tests__/config/project-config.test.d.ts +1 -0
  71. package/dist/src/__tests__/config/project-config.test.js +199 -0
  72. package/dist/src/__tests__/config-decorator.test.d.ts +8 -0
  73. package/dist/src/__tests__/config-decorator.test.js +142 -0
  74. package/dist/src/__tests__/diagnostics-extra.test.d.ts +6 -0
  75. package/dist/src/__tests__/diagnostics-extra.test.js +132 -0
  76. package/dist/src/__tests__/emit/compile-branches.test.d.ts +1 -0
  77. package/dist/src/__tests__/emit/compile-branches.test.js +123 -0
  78. package/dist/src/__tests__/emit/compile-coverage.test.d.ts +25 -0
  79. package/dist/src/__tests__/emit/compile-coverage.test.js +617 -0
  80. package/dist/src/__tests__/emit/compile-extra-branches.test.d.ts +12 -0
  81. package/dist/src/__tests__/emit/compile-extra-branches.test.js +225 -0
  82. package/dist/src/__tests__/emit/compile-mocked-branches.test.d.ts +0 -0
  83. package/dist/src/__tests__/emit/compile-mocked-branches.test.js +238 -0
  84. package/dist/src/__tests__/emit/execute-chain.test.d.ts +10 -0
  85. package/dist/src/__tests__/emit/execute-chain.test.js +94 -0
  86. package/dist/src/__tests__/emit/index.test.js +2 -1
  87. package/dist/src/__tests__/emit/modules-branches.test.d.ts +1 -0
  88. package/dist/src/__tests__/emit/modules-branches.test.js +88 -0
  89. package/dist/src/__tests__/emit/modules-coverage.test.d.ts +15 -0
  90. package/dist/src/__tests__/emit/modules-coverage.test.js +221 -0
  91. package/dist/src/__tests__/emit/modules-errors.test.d.ts +12 -0
  92. package/dist/src/__tests__/emit/modules-errors.test.js +169 -0
  93. package/dist/src/__tests__/emit/modules-rewrite.test.d.ts +17 -0
  94. package/dist/src/__tests__/emit/modules-rewrite.test.js +204 -0
  95. package/dist/src/__tests__/emit/source-map.test.d.ts +1 -0
  96. package/dist/src/__tests__/emit/source-map.test.js +167 -0
  97. package/dist/src/__tests__/enum.test.js +9 -4
  98. package/dist/src/__tests__/error-recovery.test.d.ts +7 -0
  99. package/dist/src/__tests__/error-recovery.test.js +217 -0
  100. package/dist/src/__tests__/events-types-extra.test.d.ts +10 -0
  101. package/dist/src/__tests__/events-types-extra.test.js +91 -0
  102. package/dist/src/__tests__/events-types.test.d.ts +4 -0
  103. package/dist/src/__tests__/events-types.test.js +56 -0
  104. package/dist/src/__tests__/formatter.test.js +13 -5
  105. package/dist/src/__tests__/hir/lower-extra.test.d.ts +9 -0
  106. package/dist/src/__tests__/hir/lower-extra.test.js +140 -0
  107. package/dist/src/__tests__/hir/monomorphize-extra.test.d.ts +15 -0
  108. package/dist/src/__tests__/hir/monomorphize-extra.test.js +200 -0
  109. package/dist/src/__tests__/hir/monomorphize-extra2.test.d.ts +16 -0
  110. package/dist/src/__tests__/hir/monomorphize-extra2.test.js +316 -0
  111. package/dist/src/__tests__/incremental.test.js +10 -2
  112. package/dist/src/__tests__/index-extra.test.d.ts +10 -0
  113. package/dist/src/__tests__/index-extra.test.js +71 -0
  114. package/dist/src/__tests__/lexer.test.js +2 -2
  115. package/dist/src/__tests__/lint/rules.test.d.ts +5 -0
  116. package/dist/src/__tests__/lint/rules.test.js +208 -0
  117. package/dist/src/__tests__/lir/lower.test.js +29 -0
  118. package/dist/src/__tests__/lir/verify.test.js +30 -0
  119. package/dist/src/__tests__/lsp/completion.test.d.ts +7 -0
  120. package/dist/src/__tests__/lsp/completion.test.js +583 -0
  121. package/dist/src/__tests__/lsp/definition.test.d.ts +7 -0
  122. package/dist/src/__tests__/lsp/definition.test.js +454 -0
  123. package/dist/src/__tests__/lsp/diagnostics.test.d.ts +10 -0
  124. package/dist/src/__tests__/lsp/diagnostics.test.js +98 -0
  125. package/dist/src/__tests__/lsp/hover-docs.test.d.ts +10 -0
  126. package/dist/src/__tests__/lsp/hover-docs.test.js +210 -0
  127. package/dist/src/__tests__/lsp.test.js +4 -1
  128. package/dist/src/__tests__/mc-integration/item-entity-events.test.js +4 -0
  129. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +4 -0
  130. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.d.ts +13 -0
  131. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1227 -0
  132. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.d.ts +13 -0
  133. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1509 -0
  134. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.d.ts +14 -0
  135. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1374 -0
  136. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.d.ts +10 -0
  137. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +759 -0
  138. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.d.ts +13 -0
  139. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +855 -0
  140. package/dist/src/__tests__/mc-integration/stdlib-coverage.test.js +4 -0
  141. package/dist/src/__tests__/mc-integration/syntax-coverage.test.js +4 -0
  142. package/dist/src/__tests__/mc-validator-coverage.test.d.ts +13 -0
  143. package/dist/src/__tests__/mc-validator-coverage.test.js +296 -0
  144. package/dist/src/__tests__/mc-validator-extra.test.d.ts +13 -0
  145. package/dist/src/__tests__/mc-validator-extra.test.js +245 -0
  146. package/dist/src/__tests__/mir/lower-extra.test.d.ts +20 -0
  147. package/dist/src/__tests__/mir/lower-extra.test.js +361 -0
  148. package/dist/src/__tests__/mir/lower-extra2.test.d.ts +17 -0
  149. package/dist/src/__tests__/mir/lower-extra2.test.js +317 -0
  150. package/dist/src/__tests__/mir/lower-extra3.test.d.ts +19 -0
  151. package/dist/src/__tests__/mir/lower-extra3.test.js +249 -0
  152. package/dist/src/__tests__/mir/lower-extra4.test.d.ts +23 -0
  153. package/dist/src/__tests__/mir/lower-extra4.test.js +606 -0
  154. package/dist/src/__tests__/mir/lower-extra5.test.d.ts +25 -0
  155. package/dist/src/__tests__/mir/lower-extra5.test.js +543 -0
  156. package/dist/src/__tests__/mir/lower-extra6.test.d.ts +16 -0
  157. package/dist/src/__tests__/mir/lower-extra6.test.js +471 -0
  158. package/dist/src/__tests__/mir/lower-extra7.test.d.ts +35 -0
  159. package/dist/src/__tests__/mir/lower-extra7.test.js +921 -0
  160. package/dist/src/__tests__/mir/lower-extra8.test.d.ts +19 -0
  161. package/dist/src/__tests__/mir/lower-extra8.test.js +626 -0
  162. package/dist/src/__tests__/mir/lower-extra9.test.d.ts +14 -0
  163. package/dist/src/__tests__/mir/lower-extra9.test.js +717 -0
  164. package/dist/src/__tests__/optimizer/auto-inline.test.d.ts +1 -0
  165. package/dist/src/__tests__/optimizer/auto-inline.test.js +176 -0
  166. package/dist/src/__tests__/optimizer/cse.test.d.ts +4 -0
  167. package/dist/src/__tests__/optimizer/cse.test.js +178 -0
  168. package/dist/src/__tests__/optimizer/inline_fn.test.d.ts +1 -0
  169. package/dist/src/__tests__/optimizer/inline_fn.test.js +221 -0
  170. package/dist/src/__tests__/optimizer/licm.test.d.ts +1 -0
  171. package/dist/src/__tests__/optimizer/licm.test.js +244 -0
  172. package/dist/src/__tests__/optimizer/optimizer-extended.test.d.ts +12 -0
  173. package/dist/src/__tests__/optimizer/optimizer-extended.test.js +993 -0
  174. package/dist/src/__tests__/optimizer/strength-reduction.test.d.ts +1 -0
  175. package/dist/src/__tests__/optimizer/strength-reduction.test.js +86 -0
  176. package/dist/src/__tests__/optimizer/tco.test.d.ts +14 -0
  177. package/dist/src/__tests__/optimizer/tco.test.js +203 -0
  178. package/dist/src/__tests__/parser-coverage.test.d.ts +25 -0
  179. package/dist/src/__tests__/parser-coverage.test.js +491 -0
  180. package/dist/src/__tests__/parser-extra.test.d.ts +6 -0
  181. package/dist/src/__tests__/parser-extra.test.js +451 -0
  182. package/dist/src/__tests__/parser.test.js +12 -0
  183. package/dist/src/__tests__/repl-extra.test.d.ts +13 -0
  184. package/dist/src/__tests__/repl-extra.test.js +174 -0
  185. package/dist/src/__tests__/repl-server-extra.test.d.ts +10 -0
  186. package/dist/src/__tests__/repl-server-extra.test.js +161 -0
  187. package/dist/src/__tests__/repl-server.test.d.ts +6 -0
  188. package/dist/src/__tests__/repl-server.test.js +146 -0
  189. package/dist/src/__tests__/runtime-extra.test.d.ts +15 -0
  190. package/dist/src/__tests__/runtime-extra.test.js +732 -0
  191. package/dist/src/__tests__/singleton-decorator.test.d.ts +11 -0
  192. package/dist/src/__tests__/singleton-decorator.test.js +260 -0
  193. package/dist/src/__tests__/sourcemap.test.js +1 -1
  194. package/dist/src/__tests__/stdlib/advanced.test.d.ts +5 -0
  195. package/dist/src/__tests__/stdlib/advanced.test.js +301 -0
  196. package/dist/src/__tests__/stdlib/bigint.test.d.ts +4 -0
  197. package/dist/src/__tests__/stdlib/bigint.test.js +83 -0
  198. package/dist/src/__tests__/stdlib/bits.test.d.ts +4 -0
  199. package/dist/src/__tests__/stdlib/bits.test.js +96 -0
  200. package/dist/src/__tests__/stdlib/bossbar.test.d.ts +4 -0
  201. package/dist/src/__tests__/stdlib/bossbar.test.js +72 -0
  202. package/dist/src/__tests__/stdlib/color.test.d.ts +4 -0
  203. package/dist/src/__tests__/stdlib/color.test.js +84 -0
  204. package/dist/src/__tests__/stdlib/combat.test.d.ts +4 -0
  205. package/dist/src/__tests__/stdlib/combat.test.js +64 -0
  206. package/dist/src/__tests__/stdlib/cooldown.test.d.ts +4 -0
  207. package/dist/src/__tests__/stdlib/cooldown.test.js +64 -0
  208. package/dist/src/__tests__/stdlib/dialog.test.js +15 -7
  209. package/dist/src/__tests__/stdlib/ecs.test.d.ts +4 -0
  210. package/dist/src/__tests__/stdlib/ecs.test.js +81 -0
  211. package/dist/src/__tests__/stdlib/effects.test.d.ts +4 -0
  212. package/dist/src/__tests__/stdlib/effects.test.js +72 -0
  213. package/dist/src/__tests__/stdlib/events.test.d.ts +4 -0
  214. package/dist/src/__tests__/stdlib/events.test.js +55 -0
  215. package/dist/src/__tests__/stdlib/expr.test.d.ts +4 -0
  216. package/dist/src/__tests__/stdlib/expr.test.js +77 -0
  217. package/dist/src/__tests__/stdlib/fft.test.d.ts +4 -0
  218. package/dist/src/__tests__/stdlib/fft.test.js +82 -0
  219. package/dist/src/__tests__/stdlib/graph.test.d.ts +4 -0
  220. package/dist/src/__tests__/stdlib/graph.test.js +102 -0
  221. package/dist/src/__tests__/stdlib/interactions.test.d.ts +4 -0
  222. package/dist/src/__tests__/stdlib/interactions.test.js +60 -0
  223. package/dist/src/__tests__/stdlib/inventory.test.d.ts +4 -0
  224. package/dist/src/__tests__/stdlib/inventory.test.js +68 -0
  225. package/dist/src/__tests__/stdlib/linalg.test.d.ts +5 -0
  226. package/dist/src/__tests__/stdlib/linalg.test.js +78 -0
  227. package/dist/src/__tests__/stdlib/map.test.d.ts +1 -0
  228. package/dist/src/__tests__/stdlib/map.test.js +84 -0
  229. package/dist/src/__tests__/stdlib/math.test.js +19 -6
  230. package/dist/src/__tests__/stdlib/math_hp.test.d.ts +4 -0
  231. package/dist/src/__tests__/stdlib/math_hp.test.js +80 -0
  232. package/dist/src/__tests__/stdlib/mobs.test.d.ts +4 -0
  233. package/dist/src/__tests__/stdlib/mobs.test.js +61 -0
  234. package/dist/src/__tests__/stdlib/noise.test.d.ts +4 -0
  235. package/dist/src/__tests__/stdlib/noise.test.js +73 -0
  236. package/dist/src/__tests__/stdlib/ode.test.d.ts +4 -0
  237. package/dist/src/__tests__/stdlib/ode.test.js +68 -0
  238. package/dist/src/__tests__/stdlib/parabola.test.d.ts +4 -0
  239. package/dist/src/__tests__/stdlib/parabola.test.js +77 -0
  240. package/dist/src/__tests__/stdlib/particles.test.d.ts +4 -0
  241. package/dist/src/__tests__/stdlib/particles.test.js +68 -0
  242. package/dist/src/__tests__/stdlib/physics.test.d.ts +4 -0
  243. package/dist/src/__tests__/stdlib/physics.test.js +76 -0
  244. package/dist/src/__tests__/stdlib/player.test.d.ts +4 -0
  245. package/dist/src/__tests__/stdlib/player.test.js +64 -0
  246. package/dist/src/__tests__/stdlib/quaternion.test.d.ts +4 -0
  247. package/dist/src/__tests__/stdlib/quaternion.test.js +73 -0
  248. package/dist/src/__tests__/stdlib/queue.test.d.ts +1 -0
  249. package/dist/src/__tests__/stdlib/queue.test.js +97 -0
  250. package/dist/src/__tests__/stdlib/random.test.d.ts +4 -0
  251. package/dist/src/__tests__/stdlib/random.test.js +76 -0
  252. package/dist/src/__tests__/stdlib/result.test.d.ts +12 -0
  253. package/dist/src/__tests__/stdlib/result.test.js +329 -0
  254. package/dist/src/__tests__/stdlib/scheduler.test.js +19 -8
  255. package/dist/src/__tests__/stdlib/set_int.test.d.ts +1 -0
  256. package/dist/src/__tests__/stdlib/set_int.test.js +88 -0
  257. package/dist/src/__tests__/stdlib/sets.test.d.ts +6 -0
  258. package/dist/src/__tests__/stdlib/sets.test.js +60 -0
  259. package/dist/src/__tests__/stdlib/signal.test.d.ts +4 -0
  260. package/dist/src/__tests__/stdlib/signal.test.js +84 -0
  261. package/dist/src/__tests__/stdlib/spawn.test.d.ts +4 -0
  262. package/dist/src/__tests__/stdlib/spawn.test.js +68 -0
  263. package/dist/src/__tests__/stdlib/string.test.d.ts +12 -0
  264. package/dist/src/__tests__/stdlib/string.test.js +231 -0
  265. package/dist/src/__tests__/stdlib/strings.test.d.ts +4 -0
  266. package/dist/src/__tests__/stdlib/strings.test.js +83 -0
  267. package/dist/src/__tests__/stdlib/tags.test.d.ts +4 -0
  268. package/dist/src/__tests__/stdlib/tags.test.js +57 -0
  269. package/dist/src/__tests__/stdlib/teams.test.d.ts +4 -0
  270. package/dist/src/__tests__/stdlib/teams.test.js +72 -0
  271. package/dist/src/__tests__/stdlib/timer.test.d.ts +4 -0
  272. package/dist/src/__tests__/stdlib/timer.test.js +79 -0
  273. package/dist/src/__tests__/stdlib/vec.test.d.ts +5 -0
  274. package/dist/src/__tests__/stdlib/vec.test.js +94 -0
  275. package/dist/src/__tests__/stdlib/world.test.d.ts +4 -0
  276. package/dist/src/__tests__/stdlib/world.test.js +72 -0
  277. package/dist/src/__tests__/struct-display.test.d.ts +1 -0
  278. package/dist/src/__tests__/struct-display.test.js +64 -0
  279. package/dist/src/__tests__/test-framework/runner.test.d.ts +10 -0
  280. package/dist/src/__tests__/test-framework/runner.test.js +193 -0
  281. package/dist/src/__tests__/tuner/adapters.test.d.ts +14 -0
  282. package/dist/src/__tests__/tuner/adapters.test.js +194 -0
  283. package/dist/src/__tests__/tuner/simulator-extra.test.d.ts +4 -0
  284. package/dist/src/__tests__/tuner/simulator-extra.test.js +193 -0
  285. package/dist/src/__tests__/typechecker-coverage.test.d.ts +30 -0
  286. package/dist/src/__tests__/typechecker-coverage.test.js +627 -0
  287. package/dist/src/__tests__/typechecker.test.js +3 -3
  288. package/dist/src/__tests__/watch-decorator.test.d.ts +1 -0
  289. package/dist/src/__tests__/watch-decorator.test.js +54 -0
  290. package/dist/src/ast/types.d.ts +102 -3
  291. package/dist/src/cache/incremental.d.ts +13 -14
  292. package/dist/src/cache/incremental.js +106 -89
  293. package/dist/src/cache/index.d.ts +8 -2
  294. package/dist/src/cache/index.js +18 -6
  295. package/dist/src/cli.d.ts +1 -0
  296. package/dist/src/cli.js +466 -17
  297. package/dist/src/config/project-config.d.ts +29 -0
  298. package/dist/src/config/project-config.js +180 -0
  299. package/dist/src/diagnostics/index.d.ts +9 -0
  300. package/dist/src/diagnostics/index.js +18 -1
  301. package/dist/src/emit/compile.d.ts +10 -0
  302. package/dist/src/emit/compile.js +395 -50
  303. package/dist/src/emit/index.d.ts +40 -0
  304. package/dist/src/emit/index.js +307 -14
  305. package/dist/src/emit/modules.js +21 -3
  306. package/dist/src/emit/sourcemap.d.ts +23 -27
  307. package/dist/src/emit/sourcemap.js +52 -30
  308. package/dist/src/formatter/index.js +33 -8
  309. package/dist/src/hir/deprecated.d.ts +13 -0
  310. package/dist/src/hir/deprecated.js +218 -0
  311. package/dist/src/hir/lower.js +114 -8
  312. package/dist/src/hir/monomorphize.js +22 -2
  313. package/dist/src/hir/types.d.ts +65 -1
  314. package/dist/src/index.d.ts +6 -0
  315. package/dist/src/index.js +18 -3
  316. package/dist/src/lexer/index.d.ts +2 -1
  317. package/dist/src/lexer/index.js +39 -3
  318. package/dist/src/lint/index.d.ts +45 -0
  319. package/dist/src/lint/index.js +930 -0
  320. package/dist/src/lir/lower.js +29 -2
  321. package/dist/src/lir/types.d.ts +2 -0
  322. package/dist/src/lsp/server.js +92 -5
  323. package/dist/src/mir/lower.js +775 -34
  324. package/dist/src/mir/macro.js +36 -2
  325. package/dist/src/mir/types.d.ts +12 -0
  326. package/dist/src/mir/verify.js +9 -0
  327. package/dist/src/optimizer/auto-inline.d.ts +2 -0
  328. package/dist/src/optimizer/auto-inline.js +67 -0
  329. package/dist/src/optimizer/cse.d.ts +20 -0
  330. package/dist/src/optimizer/cse.js +234 -0
  331. package/dist/src/optimizer/inline.d.ts +26 -0
  332. package/dist/src/optimizer/inline.js +286 -0
  333. package/dist/src/optimizer/interprocedural.js +4 -0
  334. package/dist/src/optimizer/licm.d.ts +32 -0
  335. package/dist/src/optimizer/licm.js +371 -0
  336. package/dist/src/optimizer/pipeline.js +12 -2
  337. package/dist/src/optimizer/strength_reduction.d.ts +15 -0
  338. package/dist/src/optimizer/strength_reduction.js +90 -0
  339. package/dist/src/optimizer/tco.d.ts +53 -0
  340. package/dist/src/optimizer/tco.js +238 -0
  341. package/dist/src/parser/index.d.ts +32 -0
  342. package/dist/src/parser/index.js +421 -59
  343. package/dist/src/repl-server.d.ts +13 -0
  344. package/dist/src/repl-server.js +127 -0
  345. package/dist/src/structs/expand.d.ts +15 -0
  346. package/dist/src/structs/expand.js +46 -0
  347. package/dist/src/testing/runner.d.ts +40 -0
  348. package/dist/src/testing/runner.js +237 -0
  349. package/dist/src/typechecker/index.d.ts +3 -0
  350. package/dist/src/typechecker/index.js +254 -9
  351. package/dist/tsconfig.tsbuildinfo +1 -1
  352. package/doc-drafts/redscript-docs/docs/en/stdlib/graph.md +104 -0
  353. package/doc-drafts/redscript-docs/docs/en/stdlib/parabola.md +113 -0
  354. package/doc-drafts/redscript-docs/docs/en/stdlib/pathfind.md +104 -0
  355. package/doc-drafts/redscript-docs/docs/en/stdlib/physics.md +134 -0
  356. package/doc-drafts/redscript-docs/docs/en/stdlib/quaternion.md +135 -0
  357. package/doc-drafts/redscript-docs/docs/zh/stdlib/graph.md +104 -0
  358. package/doc-drafts/redscript-docs/docs/zh/stdlib/parabola.md +113 -0
  359. package/doc-drafts/redscript-docs/docs/zh/stdlib/pathfind.md +104 -0
  360. package/doc-drafts/redscript-docs/docs/zh/stdlib/physics.md +134 -0
  361. package/doc-drafts/redscript-docs/docs/zh/stdlib/quaternion.md +135 -0
  362. package/docs/stdlib/result.md +156 -0
  363. package/docs/stdlib/result.zh.md +156 -0
  364. package/editors/vscode/fixtures/test.mcrs +7 -0
  365. package/editors/vscode/out/extension.js +2095 -225
  366. package/editors/vscode/out/lsp-server.js +519 -51
  367. package/editors/vscode/package-lock.json +9 -4
  368. package/editors/vscode/package.json +1 -1
  369. package/examples/display-demo.mcrs +64 -0
  370. package/examples/game/racing.mcrs +301 -0
  371. package/examples/game/tower_defense.mcrs +311 -0
  372. package/examples/math/physics_sim.mcrs +322 -0
  373. package/examples/rpg/boss_fight.mcrs +313 -0
  374. package/examples/rpg/health_system.mcrs +237 -0
  375. package/examples/rpg/inventory.mcrs +265 -0
  376. package/examples/util/debug_hud.mcrs +279 -0
  377. package/jest.config.js +10 -0
  378. package/package.json +12 -3
  379. package/playground/index.html +823 -0
  380. package/scripts/gen-docs.ts +533 -0
  381. package/scripts/update-redscript-docs-stdlib.sh +770 -0
  382. package/src/__tests__/budget.test.ts +5 -0
  383. package/src/__tests__/cache/cache-behavior.test.ts +480 -0
  384. package/src/__tests__/cache-extra.test.ts +199 -0
  385. package/src/__tests__/cli-docs.test.ts +77 -0
  386. package/src/__tests__/cli-init.test.ts +91 -0
  387. package/src/__tests__/cli-publish.test.ts +190 -0
  388. package/src/__tests__/cli.test.ts +117 -1
  389. package/src/__tests__/compile-preprocess.test.ts +366 -0
  390. package/src/__tests__/compiler/break-stmt.test.ts +66 -0
  391. package/src/__tests__/compiler/const-decl.test.ts +141 -0
  392. package/src/__tests__/compiler/continue-stmt.test.ts +81 -0
  393. package/src/__tests__/compiler/coroutine-extended.test.ts +723 -0
  394. package/src/__tests__/compiler/deprecated.test.ts +305 -0
  395. package/src/__tests__/compiler/do-while.test.ts +130 -0
  396. package/src/__tests__/compiler/enum-payload.test.ts +299 -0
  397. package/src/__tests__/compiler/interface.test.ts +287 -0
  398. package/src/__tests__/compiler/labeled-loops.test.ts +279 -0
  399. package/src/__tests__/compiler/match-string.test.ts +45 -0
  400. package/src/__tests__/compiler/memoize.test.ts +126 -0
  401. package/src/__tests__/compiler/method-chain.test.ts +121 -0
  402. package/src/__tests__/compiler/module-import.test.ts +240 -0
  403. package/src/__tests__/compiler/option-extensions.test.ts +207 -0
  404. package/src/__tests__/compiler/profile-decorator.test.ts +79 -0
  405. package/src/__tests__/compiler/string-advanced.test.ts +310 -0
  406. package/src/__tests__/compiler/struct-extends.test.ts +109 -0
  407. package/src/__tests__/compiler/throttle-retry.test.ts +191 -0
  408. package/src/__tests__/compiler/tuple-type.test.ts +263 -0
  409. package/src/__tests__/compiler/watch-decorator.test.ts +72 -0
  410. package/src/__tests__/config/project-config.test.ts +181 -0
  411. package/src/__tests__/config-decorator.test.ts +157 -0
  412. package/src/__tests__/diagnostics-extra.test.ts +155 -0
  413. package/src/__tests__/emit/compile-branches.test.ts +135 -0
  414. package/src/__tests__/emit/compile-coverage.test.ts +696 -0
  415. package/src/__tests__/emit/compile-extra-branches.test.ts +228 -0
  416. package/src/__tests__/emit/compile-mocked-branches.test.ts +249 -0
  417. package/src/__tests__/emit/compile.test.ts +6 -1
  418. package/src/__tests__/emit/execute-chain.test.ts +114 -0
  419. package/src/__tests__/emit/index.test.ts +2 -1
  420. package/src/__tests__/emit/modules-branches.test.ts +90 -0
  421. package/src/__tests__/emit/modules-coverage.test.ts +241 -0
  422. package/src/__tests__/emit/modules-errors.test.ts +192 -0
  423. package/src/__tests__/emit/modules-rewrite.test.ts +232 -0
  424. package/src/__tests__/emit/source-map.test.ts +152 -0
  425. package/src/__tests__/enum.test.ts +9 -4
  426. package/src/__tests__/error-recovery.test.ts +226 -0
  427. package/src/__tests__/events-types-extra.test.ts +110 -0
  428. package/src/__tests__/events-types.test.ts +66 -0
  429. package/src/__tests__/formatter.test.ts +15 -5
  430. package/src/__tests__/generics.test.ts +16 -9
  431. package/src/__tests__/hir/lower-extra.test.ts +151 -0
  432. package/src/__tests__/hir/monomorphize-coverage.test.ts +432 -0
  433. package/src/__tests__/hir/monomorphize-extra.test.ts +220 -0
  434. package/src/__tests__/hir/monomorphize-extra2.test.ts +350 -0
  435. package/src/__tests__/impl.test.ts +12 -8
  436. package/src/__tests__/incremental.test.ts +10 -2
  437. package/src/__tests__/index-extra.test.ts +79 -0
  438. package/src/__tests__/lexer.test.ts +2 -2
  439. package/src/__tests__/lint/hir-coverage.test.ts +1716 -0
  440. package/src/__tests__/lint/rules-coverage.test.ts +598 -0
  441. package/src/__tests__/lint/rules.test.ts +230 -0
  442. package/src/__tests__/lir/lower.test.ts +33 -0
  443. package/src/__tests__/lir/verify.test.ts +33 -0
  444. package/src/__tests__/lsp/completion.test.ts +687 -0
  445. package/src/__tests__/lsp/definition.test.ts +499 -0
  446. package/src/__tests__/lsp/diagnostics.test.ts +108 -0
  447. package/src/__tests__/lsp/hover-docs.test.ts +222 -0
  448. package/src/__tests__/lsp.test.ts +4 -1
  449. package/src/__tests__/mc-integration/item-entity-events.test.ts +5 -0
  450. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +5 -0
  451. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1105 -0
  452. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1366 -0
  453. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1245 -0
  454. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +755 -0
  455. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +771 -0
  456. package/src/__tests__/mc-integration/stdlib-coverage.test.ts +5 -0
  457. package/src/__tests__/mc-integration/syntax-coverage.test.ts +5 -0
  458. package/src/__tests__/mc-validator-coverage.test.ts +325 -0
  459. package/src/__tests__/mc-validator-extra.test.ts +252 -0
  460. package/src/__tests__/mir/lower-extra.test.ts +402 -0
  461. package/src/__tests__/mir/lower-extra2.test.ts +348 -0
  462. package/src/__tests__/mir/lower-extra3.test.ts +277 -0
  463. package/src/__tests__/mir/lower-extra4.test.ts +636 -0
  464. package/src/__tests__/mir/lower-extra5.test.ts +612 -0
  465. package/src/__tests__/mir/lower-extra6.test.ts +520 -0
  466. package/src/__tests__/mir/lower-extra7.test.ts +1045 -0
  467. package/src/__tests__/mir/lower-extra8.test.ts +704 -0
  468. package/src/__tests__/mir/lower-extra9.test.ts +821 -0
  469. package/src/__tests__/optimizer/auto-inline.test.ts +206 -0
  470. package/src/__tests__/optimizer/cse.test.ts +195 -0
  471. package/src/__tests__/optimizer/inline_fn.test.ts +263 -0
  472. package/src/__tests__/optimizer/licm.test.ts +358 -0
  473. package/src/__tests__/optimizer/nbt-coalesce.test.ts +147 -0
  474. package/src/__tests__/optimizer/optimizer-extended.test.ts +1081 -0
  475. package/src/__tests__/optimizer/scoreboard-batch.test.ts +141 -0
  476. package/src/__tests__/optimizer/strength-reduction.test.ts +111 -0
  477. package/src/__tests__/optimizer/tco-coverage.test.ts +309 -0
  478. package/src/__tests__/optimizer/tco.test.ts +238 -0
  479. package/src/__tests__/option.test.ts +14 -7
  480. package/src/__tests__/parser-coverage.test.ts +576 -0
  481. package/src/__tests__/parser-extra.test.ts +531 -0
  482. package/src/__tests__/parser.test.ts +14 -0
  483. package/src/__tests__/repl-extra.test.ts +195 -0
  484. package/src/__tests__/repl-server-extra.test.ts +150 -0
  485. package/src/__tests__/repl-server.test.ts +122 -0
  486. package/src/__tests__/runtime-extra.test.ts +862 -0
  487. package/src/__tests__/singleton-decorator.test.ts +285 -0
  488. package/src/__tests__/sourcemap.test.ts +1 -1
  489. package/src/__tests__/stdlib/advanced.test.ts +312 -0
  490. package/src/__tests__/stdlib/bigint.test.ts +57 -0
  491. package/src/__tests__/stdlib/bits.test.ts +75 -0
  492. package/src/__tests__/stdlib/bossbar.test.ts +45 -0
  493. package/src/__tests__/stdlib/color.test.ts +60 -0
  494. package/src/__tests__/stdlib/combat.test.ts +35 -0
  495. package/src/__tests__/stdlib/cooldown.test.ts +35 -0
  496. package/src/__tests__/stdlib/dialog.test.ts +14 -6
  497. package/src/__tests__/stdlib/ecs.test.ts +54 -0
  498. package/src/__tests__/stdlib/effects.test.ts +45 -0
  499. package/src/__tests__/stdlib/events.test.ts +23 -0
  500. package/src/__tests__/stdlib/expr.test.ts +48 -0
  501. package/src/__tests__/stdlib/fft.test.ts +54 -0
  502. package/src/__tests__/stdlib/graph.test.ts +77 -0
  503. package/src/__tests__/stdlib/interactions.test.ts +30 -0
  504. package/src/__tests__/stdlib/inventory.test.ts +40 -0
  505. package/src/__tests__/stdlib/linalg.test.ts +52 -0
  506. package/src/__tests__/stdlib/map.test.ts +55 -0
  507. package/src/__tests__/stdlib/math.test.ts +19 -5
  508. package/src/__tests__/stdlib/math_hp.test.ts +55 -0
  509. package/src/__tests__/stdlib/mobs.test.ts +40 -0
  510. package/src/__tests__/stdlib/noise.test.ts +46 -0
  511. package/src/__tests__/stdlib/ode.test.ts +40 -0
  512. package/src/__tests__/stdlib/parabola.test.ts +51 -0
  513. package/src/__tests__/stdlib/particles.test.ts +40 -0
  514. package/src/__tests__/stdlib/physics.test.ts +50 -0
  515. package/src/__tests__/stdlib/player.test.ts +35 -0
  516. package/src/__tests__/stdlib/quaternion.test.ts +46 -0
  517. package/src/__tests__/stdlib/queue.test.ts +73 -0
  518. package/src/__tests__/stdlib/random.test.ts +50 -0
  519. package/src/__tests__/stdlib/result.test.ts +326 -0
  520. package/src/__tests__/stdlib/scheduler.test.ts +18 -7
  521. package/src/__tests__/stdlib/set_int.test.ts +62 -0
  522. package/src/__tests__/stdlib/sets.test.ts +28 -0
  523. package/src/__tests__/stdlib/signal.test.ts +60 -0
  524. package/src/__tests__/stdlib/spawn.test.ts +40 -0
  525. package/src/__tests__/stdlib/string.test.ts +224 -0
  526. package/src/__tests__/stdlib/strings.test.ts +55 -0
  527. package/src/__tests__/stdlib/tags.test.ts +32 -0
  528. package/src/__tests__/stdlib/teams.test.ts +45 -0
  529. package/src/__tests__/stdlib/timer.test.ts +53 -0
  530. package/src/__tests__/stdlib/vec.test.ts +72 -0
  531. package/src/__tests__/stdlib/world.test.ts +45 -0
  532. package/src/__tests__/struct-display.test.ts +69 -0
  533. package/src/__tests__/test-framework/runner.test.ts +208 -0
  534. package/src/__tests__/tuner/adapters.test.ts +232 -0
  535. package/src/__tests__/tuner/simulator-extra.test.ts +222 -0
  536. package/src/__tests__/tuple.test.ts +11 -4
  537. package/src/__tests__/typechecker-coverage.test.ts +671 -0
  538. package/src/__tests__/typechecker.test.ts +4 -3
  539. package/src/__tests__/watch-decorator.test.ts +59 -0
  540. package/src/ast/types.ts +65 -3
  541. package/src/cache/incremental.ts +128 -99
  542. package/src/cache/index.ts +35 -8
  543. package/src/cli.ts +538 -29
  544. package/src/config/project-config.ts +176 -0
  545. package/src/diagnostics/index.ts +22 -0
  546. package/src/docs.ts +98 -0
  547. package/src/emit/compile.ts +408 -51
  548. package/src/emit/index.ts +366 -18
  549. package/src/emit/modules.ts +19 -3
  550. package/src/emit/sourcemap.ts +64 -43
  551. package/src/formatter/index.ts +35 -8
  552. package/src/hir/deprecated.ts +212 -0
  553. package/src/hir/lower.ts +128 -8
  554. package/src/hir/monomorphize.ts +24 -2
  555. package/src/hir/types.ts +26 -1
  556. package/src/index.ts +23 -3
  557. package/src/lexer/index.ts +45 -6
  558. package/src/lint/index.ts +922 -0
  559. package/src/lir/lower.ts +30 -2
  560. package/src/lir/types.ts +4 -0
  561. package/src/lsp/server.ts +100 -1
  562. package/src/mir/lower.ts +785 -40
  563. package/src/mir/macro.ts +30 -2
  564. package/src/mir/types.ts +13 -0
  565. package/src/mir/verify.ts +10 -2
  566. package/src/optimizer/auto-inline.ts +86 -0
  567. package/src/optimizer/copy_prop.ts +2 -2
  568. package/src/optimizer/coroutine.ts +3 -3
  569. package/src/optimizer/cse.ts +205 -0
  570. package/src/optimizer/dce.ts +2 -2
  571. package/src/optimizer/inline.ts +335 -0
  572. package/src/optimizer/interprocedural.ts +5 -1
  573. package/src/optimizer/licm.ts +454 -0
  574. package/src/optimizer/nbt-coalesce.ts +109 -0
  575. package/src/optimizer/pipeline.ts +16 -2
  576. package/src/optimizer/scoreboard-batch.ts +52 -0
  577. package/src/optimizer/strength_reduction.ts +95 -0
  578. package/src/optimizer/tco.ts +267 -0
  579. package/src/optimizer/unroll.ts +2 -2
  580. package/src/parser/index.ts +426 -53
  581. package/src/repl-server.ts +102 -0
  582. package/src/stdlib/advanced.mcrs +271 -101
  583. package/src/stdlib/bigint.mcrs +97 -11
  584. package/src/stdlib/bits.mcrs +75 -12
  585. package/src/stdlib/bossbar.mcrs +37 -8
  586. package/src/stdlib/calculus.mcrs +82 -26
  587. package/src/stdlib/color.mcrs +98 -16
  588. package/src/stdlib/combat.mcrs +23 -5
  589. package/src/stdlib/cooldown.mcrs +19 -0
  590. package/src/stdlib/dialog.mcrs +45 -7
  591. package/src/stdlib/easing.mcrs +132 -12
  592. package/src/stdlib/ecs.mcrs +142 -25
  593. package/src/stdlib/effects.mcrs +88 -12
  594. package/src/stdlib/events.mcrs +21 -2
  595. package/src/stdlib/expr.mcrs +18 -3
  596. package/src/stdlib/fft.mcrs +66 -56
  597. package/src/stdlib/geometry.mcrs +137 -39
  598. package/src/stdlib/graph.mcrs +73 -0
  599. package/src/stdlib/heap.mcrs +49 -8
  600. package/src/stdlib/i18n/zh.yaml +2891 -0
  601. package/src/stdlib/interactions.mcrs +43 -20
  602. package/src/stdlib/inventory.mcrs +14 -3
  603. package/src/stdlib/linalg.mcrs +185 -30
  604. package/src/stdlib/list.mcrs +168 -18
  605. package/src/stdlib/map.mcrs +112 -0
  606. package/src/stdlib/math.mcrs +68 -18
  607. package/src/stdlib/math_hp.mcrs +124 -33
  608. package/src/stdlib/matrix.mcrs +133 -20
  609. package/src/stdlib/mobs.mcrs +87 -0
  610. package/src/stdlib/noise.mcrs +65 -21
  611. package/src/stdlib/ode.mcrs +96 -0
  612. package/src/stdlib/parabola.mcrs +104 -29
  613. package/src/stdlib/particles.mcrs +78 -21
  614. package/src/stdlib/pathfind.mcrs +89 -35
  615. package/src/stdlib/physics.mcrs +134 -26
  616. package/src/stdlib/player.mcrs +18 -0
  617. package/src/stdlib/quaternion.mcrs +213 -9
  618. package/src/stdlib/queue.mcrs +123 -0
  619. package/src/stdlib/random.mcrs +63 -18
  620. package/src/stdlib/result.mcrs +111 -0
  621. package/src/stdlib/scheduler.mcrs +59 -10
  622. package/src/stdlib/set_int.mcrs +240 -0
  623. package/src/stdlib/sets.mcrs +49 -19
  624. package/src/stdlib/signal.mcrs +151 -79
  625. package/src/stdlib/sort.mcrs +44 -24
  626. package/src/stdlib/spawn.mcrs +30 -7
  627. package/src/stdlib/state.mcrs +40 -5
  628. package/src/stdlib/strings.mcrs +131 -3
  629. package/src/stdlib/tags.mcrs +2 -2
  630. package/src/stdlib/teams.mcrs +22 -10
  631. package/src/stdlib/timer.mcrs +36 -6
  632. package/src/stdlib/vec.mcrs +44 -9
  633. package/src/stdlib/world.mcrs +57 -25
  634. package/src/structs/expand.ts +64 -0
  635. package/src/testing/runner.ts +271 -0
  636. package/src/typechecker/index.ts +273 -9
@@ -31,6 +31,11 @@ function mkModule(functions: LIRFunction[]): LIRModule {
31
31
  // ---------------------------------------------------------------------------
32
32
 
33
33
  describe('estimateCommandCount', () => {
34
+ test('missing function returns 0', () => {
35
+ const mod = mkModule([mkFn('main', [])])
36
+ expect(estimateCommandCount('missing', mod)).toBe(0)
37
+ })
38
+
34
39
  test('flat function — counts each instruction as 1', () => {
35
40
  const fn = mkFn('simple', [
36
41
  { kind: 'score_set', dst: slot('$x'), value: 0 },
@@ -0,0 +1,480 @@
1
+ /**
2
+ * cache-behavior.test.ts
3
+ *
4
+ * Focused tests for cache behavior:
5
+ * - Same code compiled twice → second compile uses cache
6
+ * - Modified code → cache invalidation
7
+ * - stdlib / shared module caching
8
+ * - Edge cases to push src/cache/ coverage > 85%
9
+ */
10
+
11
+ import * as fs from 'fs'
12
+ import * as os from 'os'
13
+ import * as path from 'path'
14
+ import { FileCache, hashFile } from '../../cache/index'
15
+ import { DependencyGraph, parseImports } from '../../cache/deps'
16
+ import { compileIncremental, resetCompileCache } from '../../cache/incremental'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ function makeTmpDir(): string {
23
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'rs-cache-behavior-'))
24
+ }
25
+
26
+ function writeFile(dir: string, name: string, content: string): string {
27
+ const filePath = path.join(dir, name)
28
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
29
+ fs.writeFileSync(filePath, content)
30
+ return filePath
31
+ }
32
+
33
+ /** Bump mtime forward so hasChanged detects a modification. */
34
+ function touchFile(filePath: string, offsetMs = 5_000): void {
35
+ const now = Date.now() + offsetMs
36
+ fs.utimesSync(filePath, new Date(now), new Date(now))
37
+ }
38
+
39
+ /** Write new content and bump mtime. */
40
+ function modifyFile(filePath: string, newContent: string): void {
41
+ fs.writeFileSync(filePath, newContent)
42
+ touchFile(filePath)
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // FileCache — uncovered branches
47
+ // ---------------------------------------------------------------------------
48
+
49
+ describe('FileCache — stat throws on cached file (branch coverage)', () => {
50
+ test('hasChanged returns true when stat throws after entry is cached', () => {
51
+ const cache = new FileCache('/tmp/test-no-file')
52
+ // Manually insert an entry for a path that does NOT exist on disk
53
+ const fakePath = '/absolutely/nonexistent/ghost.mcrs'
54
+ cache.set(fakePath, { hash: 'deadbeef', mtime: 12345 })
55
+ // stat will throw → branch at line 53 returns true
56
+ expect(cache.hasChanged(fakePath)).toBe(true)
57
+ })
58
+
59
+ test('update on nonexistent file removes entry (catch branch)', () => {
60
+ const tmp = makeTmpDir()
61
+ const cache = new FileCache(tmp)
62
+ // First create file, cache it
63
+ const filePath = writeFile(tmp, 'ghost.mcrs', 'fn ghost() {}')
64
+ cache.update(filePath)
65
+ expect(cache.size).toBe(1)
66
+
67
+ // Delete the file — now update should catch the error and delete the entry
68
+ fs.unlinkSync(filePath)
69
+ // Manually wipe mtime so hasChanged considers it changed
70
+ cache.set(filePath, { hash: 'stale', mtime: 0 })
71
+ cache.update(filePath) // stat throws inside update → delete branch
72
+ expect(cache.get(filePath)).toBeUndefined()
73
+ })
74
+ })
75
+
76
+ describe('FileCache — update HIR on unchanged file', () => {
77
+ test('update with HIR on unchanged file stores HIR without marking changed', () => {
78
+ const tmp = makeTmpDir()
79
+ const cache = new FileCache(tmp)
80
+ const filePath = writeFile(tmp, 'stable.mcrs', 'fn stable() {}')
81
+ // Initial update — mark file as cached
82
+ cache.update(filePath)
83
+ expect(cache.hasChanged(filePath)).toBe(false)
84
+
85
+ // Call update again with a fake HIR on an unchanged file
86
+ const fakeHir = { functions: [] } as any
87
+ const changed = cache.update(filePath, fakeHir)
88
+ expect(changed).toBe(false) // file didn't change
89
+ // HIR should now be attached
90
+ const entry = cache.get(filePath)
91
+ expect(entry?.hir).toBe(fakeHir)
92
+ })
93
+ })
94
+
95
+ describe('FileCache — load with wrong version is ignored', () => {
96
+ test('load skips entries when cache version !== 1', () => {
97
+ const tmp = makeTmpDir()
98
+ const cacheDir = path.join(tmp, '.cache')
99
+ fs.mkdirSync(cacheDir, { recursive: true })
100
+ // Write a v2 cache file
101
+ fs.writeFileSync(
102
+ path.join(cacheDir, 'cache.json'),
103
+ JSON.stringify({ version: 99, entries: { '/some/file.mcrs': { hash: 'abc', mtime: 1 } } }),
104
+ )
105
+ const cache = new FileCache(cacheDir)
106
+ cache.load()
107
+ expect(cache.size).toBe(0) // version mismatch — ignored
108
+ })
109
+ })
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // Behavior: same code twice → second compile is cached
113
+ // ---------------------------------------------------------------------------
114
+
115
+ describe('Cache hit: same code compiled twice', () => {
116
+ let tmpDir: string
117
+ let cacheDir: string
118
+ let outDir: string
119
+
120
+ beforeEach(() => {
121
+ tmpDir = makeTmpDir()
122
+ cacheDir = path.join(tmpDir, '.cache')
123
+ outDir = path.join(tmpDir, 'out')
124
+ resetCompileCache()
125
+ })
126
+
127
+ afterEach(() => {
128
+ fs.rmSync(tmpDir, { recursive: true, force: true })
129
+ })
130
+
131
+ test('second compile of unchanged file produces cache hit', () => {
132
+ const src = writeFile(tmpDir, 'hello.mcrs', 'fn hello() { let x: int = 1; }')
133
+ const cache = new FileCache(cacheDir)
134
+ const depGraph = new DependencyGraph()
135
+
136
+ // First compile — must be a miss
137
+ const r1 = compileIncremental([src], cache, depGraph, { output: outDir })
138
+ expect(r1.recompiled).toBe(1)
139
+ expect(r1.cached).toBe(0)
140
+
141
+ // Second compile — same file, same content → cache hit
142
+ const r2 = compileIncremental([src], cache, depGraph, { output: outDir })
143
+ expect(r2.recompiled).toBe(0)
144
+ expect(r2.cached).toBe(1)
145
+ })
146
+
147
+ test('cache persists across FileCache reload', () => {
148
+ const src = writeFile(tmpDir, 'hello.mcrs', 'fn hello() { let x: int = 1; }')
149
+
150
+ // First pass: compile and save cache to disk
151
+ const cache1 = new FileCache(cacheDir)
152
+ const depGraph1 = new DependencyGraph()
153
+ compileIncremental([src], cache1, depGraph1, { output: outDir })
154
+ cache1.save()
155
+
156
+ // Reset in-memory compile cache to simulate a fresh process
157
+ resetCompileCache()
158
+
159
+ // Second pass: load cache from disk — still a hit for the FileCache checks
160
+ const cache2 = new FileCache(cacheDir)
161
+ cache2.load()
162
+ expect(cache2.hasChanged(src)).toBe(false)
163
+ })
164
+ })
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Behavior: modifying code invalidates cache
168
+ // ---------------------------------------------------------------------------
169
+
170
+ describe('Cache invalidation: modified source', () => {
171
+ let tmpDir: string
172
+ let cacheDir: string
173
+ let outDir: string
174
+
175
+ beforeEach(() => {
176
+ tmpDir = makeTmpDir()
177
+ cacheDir = path.join(tmpDir, '.cache')
178
+ outDir = path.join(tmpDir, 'out')
179
+ resetCompileCache()
180
+ })
181
+
182
+ afterEach(() => {
183
+ fs.rmSync(tmpDir, { recursive: true, force: true })
184
+ })
185
+
186
+ test('modified file is recompiled after cache hit', () => {
187
+ const src = writeFile(tmpDir, 'counter.mcrs', 'fn counter() { let n: int = 0; }')
188
+ const cache = new FileCache(cacheDir)
189
+ const depGraph = new DependencyGraph()
190
+
191
+ // First compile
192
+ compileIncremental([src], cache, depGraph, { output: outDir })
193
+
194
+ // Modify the file
195
+ modifyFile(src, 'fn counter() { let n: int = 99; }')
196
+
197
+ // hasChanged should now return true
198
+ expect(cache.hasChanged(src)).toBe(true)
199
+
200
+ // Second compile — should recompile
201
+ const r2 = compileIncremental([src], cache, depGraph, { output: outDir })
202
+ expect(r2.recompiled).toBe(1)
203
+ expect(r2.cached).toBe(0)
204
+ })
205
+
206
+ test('mtime change with same content still returns cached (hash match)', () => {
207
+ const tmp2 = makeTmpDir()
208
+ const cache = new FileCache(tmp2)
209
+ const filePath = writeFile(tmp2, 'stable.mcrs', 'fn stable() {}')
210
+ cache.update(filePath)
211
+
212
+ // Touch mtime without changing content
213
+ touchFile(filePath)
214
+
215
+ // mtime changed but hash should match → hasChanged returns false
216
+ expect(cache.hasChanged(filePath)).toBe(false)
217
+
218
+ fs.rmSync(tmp2, { recursive: true, force: true })
219
+ })
220
+
221
+ test('delete file from cache then recompile triggers miss', () => {
222
+ const src = writeFile(tmpDir, 'foo.mcrs', 'fn foo() { let x: int = 5; }')
223
+ const cache = new FileCache(cacheDir)
224
+ const depGraph = new DependencyGraph()
225
+
226
+ // Compile once
227
+ compileIncremental([src], cache, depGraph, { output: outDir })
228
+ expect(cache.hasChanged(src)).toBe(false)
229
+
230
+ // Evict from FileCache
231
+ cache.delete(src)
232
+ expect(cache.hasChanged(src)).toBe(true)
233
+ })
234
+ })
235
+
236
+ // ---------------------------------------------------------------------------
237
+ // Behavior: stdlib / shared module caching
238
+ // ---------------------------------------------------------------------------
239
+
240
+ describe('Stdlib / shared module caching', () => {
241
+ let tmpDir: string
242
+ let cacheDir: string
243
+ let outDir: string
244
+
245
+ beforeEach(() => {
246
+ tmpDir = makeTmpDir()
247
+ cacheDir = path.join(tmpDir, '.cache')
248
+ outDir = path.join(tmpDir, 'out')
249
+ resetCompileCache()
250
+ })
251
+
252
+ afterEach(() => {
253
+ fs.rmSync(tmpDir, { recursive: true, force: true })
254
+ })
255
+
256
+ test('unchanged stdlib module cached across multiple entry files', () => {
257
+ // Simulate a stdlib module shared by many files
258
+ const stdlib = writeFile(tmpDir, 'stdlib/math.mcrs', 'fn math_add(a: int, b: int): int { return a + b; }')
259
+ const file1 = writeFile(tmpDir, 'prog1.mcrs', `import "stdlib/math.mcrs";\nfn prog1() { math_add(1, 2); }`)
260
+ const file2 = writeFile(tmpDir, 'prog2.mcrs', `import "stdlib/math.mcrs";\nfn prog2() { math_add(3, 4); }`)
261
+
262
+ const cache = new FileCache(cacheDir)
263
+ const depGraph = new DependencyGraph()
264
+
265
+ // First compile — all miss
266
+ const r1 = compileIncremental([file1, file2], cache, depGraph, { output: outDir })
267
+ expect(r1.recompiled).toBe(2)
268
+ expect(r1.cached).toBe(0)
269
+
270
+ // Second compile — stdlib unchanged, both files cached
271
+ const r2 = compileIncremental([file1, file2], cache, depGraph, { output: outDir })
272
+ expect(r2.recompiled).toBe(0)
273
+ expect(r2.cached).toBe(2)
274
+
275
+ // Verify stdlib itself is tracked in cache
276
+ expect(cache.hasChanged(stdlib)).toBe(false)
277
+ })
278
+
279
+ test('stdlib change invalidates all dependents', () => {
280
+ const stdlib = writeFile(tmpDir, 'stdlib/core.mcrs', 'fn core_fn() { let v: int = 1; }')
281
+ const file1 = writeFile(tmpDir, 'a.mcrs', `import "stdlib/core.mcrs";\nfn a() { core_fn(); }`)
282
+ const file2 = writeFile(tmpDir, 'b.mcrs', `import "stdlib/core.mcrs";\nfn b() { core_fn(); }`)
283
+ const isolated = writeFile(tmpDir, 'isolated.mcrs', 'fn isolated() { let x: int = 42; }')
284
+
285
+ const cache = new FileCache(cacheDir)
286
+ const depGraph = new DependencyGraph()
287
+
288
+ // Compile all three
289
+ compileIncremental([file1, file2, isolated], cache, depGraph, { output: outDir })
290
+
291
+ // Modify stdlib
292
+ modifyFile(stdlib, 'fn core_fn() { let v: int = 999; }')
293
+
294
+ // Both file1 and file2 depend on stdlib → recompile; isolated is unaffected
295
+ const r2 = compileIncremental([file1, file2, isolated], cache, depGraph, { output: outDir })
296
+ expect(r2.recompiled).toBe(2) // file1 + file2
297
+ expect(r2.cached).toBe(1) // isolated
298
+ expect(r2.errors.size).toBe(0)
299
+ })
300
+
301
+ test('stdlib module hash stays stable when content unchanged', () => {
302
+ const stdlib = writeFile(tmpDir, 'stdlib/stable.mcrs', 'fn stable_fn() { let s: int = 0; }')
303
+ const cache = new FileCache(tmpDir)
304
+
305
+ const h1 = hashFile(stdlib)
306
+ cache.update(stdlib)
307
+ // Reload from entry
308
+ const entry = cache.get(stdlib)
309
+ expect(entry?.hash).toBe(h1)
310
+
311
+ // No modification — hash should remain identical
312
+ const h2 = hashFile(stdlib)
313
+ expect(h1).toBe(h2)
314
+ expect(cache.hasChanged(stdlib)).toBe(false)
315
+ })
316
+ })
317
+
318
+ // ---------------------------------------------------------------------------
319
+ // DependencyGraph — uncovered branches (line 52, 125-130)
320
+ // ---------------------------------------------------------------------------
321
+
322
+ describe('DependencyGraph — edge cases', () => {
323
+ let tmpDir: string
324
+
325
+ beforeEach(() => {
326
+ tmpDir = makeTmpDir()
327
+ })
328
+
329
+ afterEach(() => {
330
+ fs.rmSync(tmpDir, { recursive: true, force: true })
331
+ })
332
+
333
+ test('removeFile removes the file from the graph', () => {
334
+ const a = writeFile(tmpDir, 'a.mcrs', 'fn a() {}')
335
+ const graph = new DependencyGraph()
336
+ graph.addFile(a)
337
+ expect(graph.getAllFiles()).toContain(path.resolve(a))
338
+ graph.removeFile(a)
339
+ expect(graph.getAllFiles()).not.toContain(path.resolve(a))
340
+ })
341
+
342
+ test('getAllFiles returns all tracked files', () => {
343
+ const a = writeFile(tmpDir, 'a.mcrs', 'fn a() {}')
344
+ const b = writeFile(tmpDir, 'b.mcrs', 'fn b() {}')
345
+ const graph = new DependencyGraph()
346
+ graph.addFile(a)
347
+ graph.addFile(b)
348
+ const files = graph.getAllFiles()
349
+ expect(files).toHaveLength(2)
350
+ expect(files).toContain(path.resolve(a))
351
+ expect(files).toContain(path.resolve(b))
352
+ })
353
+
354
+ test('clear empties the graph', () => {
355
+ const a = writeFile(tmpDir, 'a.mcrs', 'fn a() {}')
356
+ const graph = new DependencyGraph()
357
+ graph.addFile(a)
358
+ expect(graph.getAllFiles()).toHaveLength(1)
359
+ graph.clear()
360
+ expect(graph.getAllFiles()).toHaveLength(0)
361
+ })
362
+
363
+ test('getDirectDeps returns empty set for unknown file', () => {
364
+ const graph = new DependencyGraph()
365
+ const deps = graph.getDirectDeps('/nonexistent.mcrs')
366
+ expect(deps.size).toBe(0)
367
+ })
368
+
369
+ test('parseImports with inline source string (no disk read)', () => {
370
+ const source = `import "util.mcrs";\nimport "math.mcrs";\nfn main() {}`
371
+ const fakePath = path.join(tmpDir, 'main.mcrs')
372
+ const imports = parseImports(fakePath, source)
373
+ expect(imports).toHaveLength(2)
374
+ expect(imports[0]).toBe(path.resolve(tmpDir, 'util.mcrs'))
375
+ expect(imports[1]).toBe(path.resolve(tmpDir, 'math.mcrs'))
376
+ })
377
+
378
+ test('cyclic dependency does not cause infinite loop in getTransitiveDeps', () => {
379
+ // Simulate a cycle: a → b → a (shouldn't happen in practice but must be safe)
380
+ const a = path.resolve(tmpDir, 'a.mcrs')
381
+ const b = path.resolve(tmpDir, 'b.mcrs')
382
+ writeFile(tmpDir, 'a.mcrs', 'fn a() {}')
383
+ writeFile(tmpDir, 'b.mcrs', 'fn b() {}')
384
+
385
+ const graph = new DependencyGraph()
386
+ // Manually force a cycle via addFile with inline source
387
+ graph.addFile(a, `import "b.mcrs";\nfn a() {}`)
388
+ graph.addFile(b, `import "a.mcrs";\nfn b() {}`)
389
+
390
+ // Should not throw or loop forever
391
+ expect(() => graph.getTransitiveDeps(a)).not.toThrow()
392
+ const deps = graph.getTransitiveDeps(a)
393
+ expect(deps.has(b)).toBe(true)
394
+ })
395
+ })
396
+
397
+ // ---------------------------------------------------------------------------
398
+ // compileIncremental — uncovered branches (lines 67, 113)
399
+ // ---------------------------------------------------------------------------
400
+
401
+ describe('compileIncremental — edge cases', () => {
402
+ let tmpDir: string
403
+ let cacheDir: string
404
+ let outDir: string
405
+
406
+ beforeEach(() => {
407
+ tmpDir = makeTmpDir()
408
+ cacheDir = path.join(tmpDir, '.cache')
409
+ outDir = path.join(tmpDir, 'out')
410
+ resetCompileCache()
411
+ })
412
+
413
+ afterEach(() => {
414
+ fs.rmSync(tmpDir, { recursive: true, force: true })
415
+ })
416
+
417
+ test('deleted entry file is removed from dep graph gracefully', () => {
418
+ // Write a file, then delete it before compileIncremental runs
419
+ const ghost = path.join(tmpDir, 'ghost.mcrs')
420
+ fs.writeFileSync(ghost, 'fn ghost() {}')
421
+ fs.unlinkSync(ghost)
422
+
423
+ const cache = new FileCache(cacheDir)
424
+ const depGraph = new DependencyGraph()
425
+
426
+ // Should not throw; error captured in result
427
+ const r = compileIncremental([ghost], cache, depGraph, { output: outDir })
428
+ // Either an error is captured or the file is skipped
429
+ expect(r.recompiled + r.errors.size).toBeGreaterThanOrEqual(0)
430
+ })
431
+
432
+ test('dep count change triggers recompile (depHashes.size mismatch branch)', () => {
433
+ // Compile 'a' with no deps first
434
+ const a = writeFile(tmpDir, 'a.mcrs', 'fn a_func() { let x: int = 1; }')
435
+ const cache = new FileCache(cacheDir)
436
+ const depGraph = new DependencyGraph()
437
+
438
+ // First compile: a has no deps
439
+ const r1 = compileIncremental([a], cache, depGraph, { output: outDir })
440
+ expect(r1.recompiled).toBe(1)
441
+
442
+ // Now add a new dep to a — simulate by modifying a to import lib, then compile
443
+ const lib = writeFile(tmpDir, 'lib.mcrs', 'fn lib_fn() { let l: int = 0; }')
444
+ modifyFile(a, `import "lib.mcrs";\nfn a_func() { lib_fn(); }`)
445
+
446
+ // Second compile: a now has one dep → depHashes.size mismatch triggers recompile
447
+ const r2 = compileIncremental([a], cache, depGraph, { output: outDir })
448
+ expect(r2.recompiled).toBe(1)
449
+ expect(r2.errors.size).toBe(0)
450
+ })
451
+
452
+ test('multiple files: partial recompile on single change', () => {
453
+ const files = ['fn1', 'fn2', 'fn3'].map(name =>
454
+ writeFile(tmpDir, `${name}.mcrs`, `fn ${name}() { let x: int = 1; }`)
455
+ )
456
+
457
+ const cache = new FileCache(cacheDir)
458
+ const depGraph = new DependencyGraph()
459
+
460
+ // First compile — all miss
461
+ const r1 = compileIncremental(files, cache, depGraph, { output: outDir })
462
+ expect(r1.recompiled).toBe(3)
463
+
464
+ // Modify only fn2
465
+ modifyFile(files[1], 'fn fn2() { let x: int = 999; }')
466
+
467
+ const r2 = compileIncremental(files, cache, depGraph, { output: outDir })
468
+ expect(r2.recompiled).toBe(1)
469
+ expect(r2.cached).toBe(2)
470
+ })
471
+
472
+ test('empty file list returns zero counts', () => {
473
+ const cache = new FileCache(cacheDir)
474
+ const depGraph = new DependencyGraph()
475
+ const r = compileIncremental([], cache, depGraph, { output: outDir })
476
+ expect(r.recompiled).toBe(0)
477
+ expect(r.cached).toBe(0)
478
+ expect(r.errors.size).toBe(0)
479
+ })
480
+ })
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Extra coverage for src/cache/index.ts
3
+ * Targets: save/load, hasChanged with mtime, update branches, delete, clear.
4
+ */
5
+
6
+ import * as fs from 'fs'
7
+ import * as os from 'os'
8
+ import * as path from 'path'
9
+ import { FileCache, hashFile } from '../cache'
10
+
11
+ function makeTmpDir(): string {
12
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'rs-cache-test-'))
13
+ }
14
+
15
+ function writeTmpFile(dir: string, name: string, content: string): string {
16
+ const p = path.join(dir, name)
17
+ fs.writeFileSync(p, content)
18
+ return p
19
+ }
20
+
21
+ describe('FileCache — basic get/set', () => {
22
+ test('get returns undefined for unknown file', () => {
23
+ const cache = new FileCache('/tmp/test-cache-nonexistent')
24
+ expect(cache.get('/nonexistent/file.mcrs')).toBeUndefined()
25
+ })
26
+
27
+ test('set then get returns entry', () => {
28
+ const cache = new FileCache('/tmp/test-cache')
29
+ const filePath = '/tmp/fake.mcrs'
30
+ cache.set(filePath, { hash: 'abc123', mtime: 1000 })
31
+ const entry = cache.get(filePath)
32
+ expect(entry).toBeDefined()
33
+ expect(entry!.hash).toBe('abc123')
34
+ })
35
+
36
+ test('size reflects number of entries', () => {
37
+ const cache = new FileCache('/tmp/test-cache')
38
+ expect(cache.size).toBe(0)
39
+ cache.set('/tmp/a.mcrs', { hash: 'h1', mtime: 1 })
40
+ cache.set('/tmp/b.mcrs', { hash: 'h2', mtime: 2 })
41
+ expect(cache.size).toBe(2)
42
+ })
43
+
44
+ test('delete removes an entry', () => {
45
+ const cache = new FileCache('/tmp/test-cache')
46
+ cache.set('/tmp/a.mcrs', { hash: 'h1', mtime: 1 })
47
+ expect(cache.size).toBe(1)
48
+ cache.delete('/tmp/a.mcrs')
49
+ expect(cache.size).toBe(0)
50
+ })
51
+
52
+ test('clear removes all entries', () => {
53
+ const cache = new FileCache('/tmp/test-cache')
54
+ cache.set('/tmp/a.mcrs', { hash: 'h1', mtime: 1 })
55
+ cache.set('/tmp/b.mcrs', { hash: 'h2', mtime: 2 })
56
+ cache.clear()
57
+ expect(cache.size).toBe(0)
58
+ })
59
+ })
60
+
61
+ describe('FileCache — hasChanged', () => {
62
+ test('returns true for uncached file', () => {
63
+ const tmp = makeTmpDir()
64
+ const cache = new FileCache(tmp)
65
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'hello')
66
+ expect(cache.hasChanged(filePath)).toBe(true)
67
+ })
68
+
69
+ test('returns false when mtime matches', () => {
70
+ const tmp = makeTmpDir()
71
+ const cache = new FileCache(tmp)
72
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'hello')
73
+ const stat = fs.statSync(filePath)
74
+ cache.set(filePath, { hash: 'irrelevant', mtime: stat.mtimeMs })
75
+ expect(cache.hasChanged(filePath)).toBe(false)
76
+ })
77
+
78
+ test('returns true when content hash differs (mtime changed)', () => {
79
+ const tmp = makeTmpDir()
80
+ const cache = new FileCache(tmp)
81
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'hello')
82
+ // Cache with old mtime (0) and wrong hash
83
+ cache.set(filePath, { hash: 'wronghash', mtime: 0 })
84
+ // mtime is different → compare hash
85
+ expect(cache.hasChanged(filePath)).toBe(true)
86
+ })
87
+
88
+ test('returns true for nonexistent file', () => {
89
+ const cache = new FileCache('/tmp/nonexistent-cache')
90
+ expect(cache.hasChanged('/tmp/definitelynotafile.mcrs')).toBe(true)
91
+ })
92
+ })
93
+
94
+ describe('FileCache — update', () => {
95
+ test('update on changed file returns true and caches entry', () => {
96
+ const tmp = makeTmpDir()
97
+ const cache = new FileCache(tmp)
98
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'content')
99
+ const changed = cache.update(filePath)
100
+ expect(changed).toBe(true)
101
+ expect(cache.get(filePath)).toBeDefined()
102
+ expect(cache.get(filePath)!.hash).toBeTruthy()
103
+ })
104
+
105
+ test('update on unchanged file returns false', () => {
106
+ const tmp = makeTmpDir()
107
+ const cache = new FileCache(tmp)
108
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'content')
109
+ cache.update(filePath) // first call: changed
110
+ const changed2 = cache.update(filePath) // second call: not changed
111
+ expect(changed2).toBe(false)
112
+ })
113
+
114
+ test('update stores HIR when provided and content changed', () => {
115
+ const tmp = makeTmpDir()
116
+ const cache = new FileCache(tmp)
117
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'content')
118
+ const fakeHir = { namespace: 'test', functions: [], structs: [], enums: [], implBlocks: [], imports: [] } as any
119
+ cache.update(filePath, fakeHir)
120
+ expect(cache.get(filePath)!.hir).toBe(fakeHir)
121
+ })
122
+
123
+ test('update stores HIR when content unchanged but HIR provided', () => {
124
+ const tmp = makeTmpDir()
125
+ const cache = new FileCache(tmp)
126
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'content')
127
+ cache.update(filePath) // mark as cached
128
+ // Second update with HIR but no content change
129
+ const fakeHir = { namespace: 'test2', functions: [], structs: [], enums: [], implBlocks: [], imports: [] } as any
130
+ cache.update(filePath, fakeHir)
131
+ expect(cache.get(filePath)!.hir).toBe(fakeHir)
132
+ })
133
+ })
134
+
135
+ describe('FileCache — save/load', () => {
136
+ test('save and load round-trip persists entries', () => {
137
+ const tmp = makeTmpDir()
138
+ const cache = new FileCache(tmp)
139
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'hello world')
140
+ cache.update(filePath)
141
+ cache.save()
142
+
143
+ // Load into a new cache instance
144
+ const cache2 = new FileCache(tmp)
145
+ cache2.load()
146
+ const entry = cache2.get(filePath)
147
+ expect(entry).toBeDefined()
148
+ expect(entry!.hash).toBe(cache.get(filePath)!.hash)
149
+ expect(entry!.mtime).toBe(cache.get(filePath)!.mtime)
150
+ // HIR is NOT persisted
151
+ expect(entry!.hir).toBeUndefined()
152
+ })
153
+
154
+ test('load silently ignores missing cache file', () => {
155
+ const tmp = makeTmpDir()
156
+ const cache = new FileCache(tmp)
157
+ expect(() => cache.load()).not.toThrow()
158
+ expect(cache.size).toBe(0)
159
+ })
160
+
161
+ test('load silently ignores corrupt cache file', () => {
162
+ const tmp = makeTmpDir()
163
+ fs.writeFileSync(path.join(tmp, 'cache.json'), 'not valid json{{{')
164
+ const cache = new FileCache(tmp)
165
+ expect(() => cache.load()).not.toThrow()
166
+ expect(cache.size).toBe(0)
167
+ })
168
+
169
+ test('load ignores wrong version', () => {
170
+ const tmp = makeTmpDir()
171
+ fs.writeFileSync(path.join(tmp, 'cache.json'), JSON.stringify({ version: 99, entries: {} }))
172
+ const cache = new FileCache(tmp)
173
+ cache.load()
174
+ expect(cache.size).toBe(0)
175
+ })
176
+ })
177
+
178
+ describe('hashFile', () => {
179
+ test('returns a hex string', () => {
180
+ const tmp = makeTmpDir()
181
+ const filePath = writeTmpFile(tmp, 'test.mcrs', 'hello')
182
+ const hash = hashFile(filePath)
183
+ expect(hash).toMatch(/^[0-9a-f]{64}$/)
184
+ })
185
+
186
+ test('same content gives same hash', () => {
187
+ const tmp = makeTmpDir()
188
+ const f1 = writeTmpFile(tmp, 'a.mcrs', 'same content')
189
+ const f2 = writeTmpFile(tmp, 'b.mcrs', 'same content')
190
+ expect(hashFile(f1)).toBe(hashFile(f2))
191
+ })
192
+
193
+ test('different content gives different hash', () => {
194
+ const tmp = makeTmpDir()
195
+ const f1 = writeTmpFile(tmp, 'a.mcrs', 'hello')
196
+ const f2 = writeTmpFile(tmp, 'b.mcrs', 'world')
197
+ expect(hashFile(f1)).not.toBe(hashFile(f2))
198
+ })
199
+ })