@typecad/cuttlefish 0.1.0-alpha.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 (377) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/api/board-types.d.ts +1 -0
  4. package/dist/api/board-types.js +4 -0
  5. package/dist/api/capabilities.d.ts +12 -0
  6. package/dist/api/capabilities.js +1 -0
  7. package/dist/api/config.d.ts +120 -0
  8. package/dist/api/config.js +7 -0
  9. package/dist/api/index.d.ts +5 -0
  10. package/dist/api/index.js +2 -0
  11. package/dist/api/schema/builder.d.ts +14 -0
  12. package/dist/api/schema/builder.js +15 -0
  13. package/dist/api/schema/index.d.ts +2 -0
  14. package/dist/api/schema/index.js +1 -0
  15. package/dist/api/schema/types.d.ts +257 -0
  16. package/dist/api/schema/types.js +4 -0
  17. package/dist/api/shared/async-runtime-static.d.ts +8 -0
  18. package/dist/api/shared/async-runtime-static.js +201 -0
  19. package/dist/api/shared/async-types.d.ts +42 -0
  20. package/dist/api/shared/async-types.js +8 -0
  21. package/dist/api/shared/board-resolver.d.ts +12 -0
  22. package/dist/api/shared/board-resolver.js +7 -0
  23. package/dist/api/shared/cpp-type-ir.d.ts +229 -0
  24. package/dist/api/shared/cpp-type-ir.js +672 -0
  25. package/dist/api/shared/display-adapter.d.ts +12 -0
  26. package/dist/api/shared/display-adapter.js +145 -0
  27. package/dist/api/shared/display-adapters/eink-mono.d.ts +2 -0
  28. package/dist/api/shared/display-adapters/eink-mono.js +53 -0
  29. package/dist/api/shared/display-adapters/sdl.d.ts +2 -0
  30. package/dist/api/shared/display-adapters/sdl.js +452 -0
  31. package/dist/api/shared/display-adapters/ssd1309.d.ts +2 -0
  32. package/dist/api/shared/display-adapters/ssd1309.js +136 -0
  33. package/dist/api/shared/display-adapters/st7796.d.ts +2 -0
  34. package/dist/api/shared/display-adapters/st7796.js +132 -0
  35. package/dist/api/shared/display-capabilities.d.ts +45 -0
  36. package/dist/api/shared/display-capabilities.js +54 -0
  37. package/dist/api/shared/display-op-ir.d.ts +62 -0
  38. package/dist/api/shared/display-op-ir.js +19 -0
  39. package/dist/api/shared/display-profile.d.ts +265 -0
  40. package/dist/api/shared/display-profile.js +338 -0
  41. package/dist/api/shared/graphics-strategy.d.ts +29 -0
  42. package/dist/api/shared/graphics-strategy.js +11 -0
  43. package/dist/api/shared/hal-op-ir.d.ts +393 -0
  44. package/dist/api/shared/hal-op-ir.js +18 -0
  45. package/dist/api/shared/index.d.ts +26 -0
  46. package/dist/api/shared/index.js +21 -0
  47. package/dist/api/shared/ir-core.d.ts +363 -0
  48. package/dist/api/shared/ir-core.js +8 -0
  49. package/dist/api/shared/ir-declarations.d.ts +201 -0
  50. package/dist/api/shared/ir-declarations.js +26 -0
  51. package/dist/api/shared/ir.d.ts +85 -0
  52. package/dist/api/shared/ir.js +13 -0
  53. package/dist/api/shared/platform-strategy.d.ts +402 -0
  54. package/dist/api/shared/platform-strategy.js +14 -0
  55. package/dist/api/shared/polyfill-helper-registry.d.ts +11 -0
  56. package/dist/api/shared/polyfill-helper-registry.js +86 -0
  57. package/dist/api/shared/polyfill-types.d.ts +33 -0
  58. package/dist/api/shared/polyfill-types.js +76 -0
  59. package/dist/api/shared/promise-runtime.d.ts +10 -0
  60. package/dist/api/shared/promise-runtime.js +244 -0
  61. package/dist/api/shared/snprintf-types.d.ts +29 -0
  62. package/dist/api/shared/snprintf-types.js +8 -0
  63. package/dist/api/shared/string-method-registry.d.ts +53 -0
  64. package/dist/api/shared/string-method-registry.js +168 -0
  65. package/dist/api/shared/toolchain-types.d.ts +63 -0
  66. package/dist/api/shared/toolchain-types.js +71 -0
  67. package/dist/api/shared/types.d.ts +50 -0
  68. package/dist/api/shared/types.js +6 -0
  69. package/dist/ast/parse.d.ts +2 -0
  70. package/dist/ast/parse.js +4 -0
  71. package/dist/cache.d.ts +26 -0
  72. package/dist/cache.js +95 -0
  73. package/dist/cli-utils.d.ts +37 -0
  74. package/dist/cli-utils.js +250 -0
  75. package/dist/cli.d.ts +2 -0
  76. package/dist/cli.js +635 -0
  77. package/dist/config-loader.d.ts +78 -0
  78. package/dist/config-loader.js +508 -0
  79. package/dist/config-schema.d.ts +233 -0
  80. package/dist/config-schema.js +83 -0
  81. package/dist/create/board-checklist.d.ts +2 -0
  82. package/dist/create/board-checklist.js +52 -0
  83. package/dist/create/board-codegen.d.ts +9 -0
  84. package/dist/create/board-codegen.js +238 -0
  85. package/dist/create/board-generators.d.ts +12 -0
  86. package/dist/create/board-generators.js +652 -0
  87. package/dist/create/board-spec.d.ts +640 -0
  88. package/dist/create/board-spec.js +215 -0
  89. package/dist/create/eslint-rules-template.d.ts +2 -0
  90. package/dist/create/eslint-rules-template.js +672 -0
  91. package/dist/create/index.d.ts +11 -0
  92. package/dist/create/index.js +8 -0
  93. package/dist/create/init-scaffold.d.ts +27 -0
  94. package/dist/create/init-scaffold.js +150 -0
  95. package/dist/create/init-templates.d.ts +24 -0
  96. package/dist/create/init-templates.js +379 -0
  97. package/dist/create/init-wizard.d.ts +8 -0
  98. package/dist/create/init-wizard.js +168 -0
  99. package/dist/debug/breakpoint-loader.d.ts +24 -0
  100. package/dist/debug/breakpoint-loader.js +137 -0
  101. package/dist/debug/index.d.ts +3 -0
  102. package/dist/debug/index.js +7 -0
  103. package/dist/debug/preprocessor.d.ts +9 -0
  104. package/dist/debug/preprocessor.js +214 -0
  105. package/dist/debug/types.d.ts +32 -0
  106. package/dist/debug/types.js +6 -0
  107. package/dist/diagnostics/diagnostics-report.d.ts +51 -0
  108. package/dist/diagnostics/diagnostics-report.js +333 -0
  109. package/dist/diagnostics/json-schema.d.ts +138 -0
  110. package/dist/diagnostics/json-schema.js +27 -0
  111. package/dist/diagnostics/md-writer.d.ts +10 -0
  112. package/dist/diagnostics/md-writer.js +336 -0
  113. package/dist/diagnostics/mermaid-builder.d.ts +65 -0
  114. package/dist/diagnostics/mermaid-builder.js +681 -0
  115. package/dist/emit/cpp-emitter.d.ts +6 -0
  116. package/dist/emit/cpp-emitter.js +48 -0
  117. package/dist/emit/emitters/class-emitter.d.ts +2 -0
  118. package/dist/emit/emitters/class-emitter.js +523 -0
  119. package/dist/emit/emitters/emitter-context.d.ts +174 -0
  120. package/dist/emit/emitters/emitter-context.js +1 -0
  121. package/dist/emit/emitters/entrypoint-synthesizer.d.ts +2 -0
  122. package/dist/emit/emitters/entrypoint-synthesizer.js +175 -0
  123. package/dist/emit/emitters/function-emitter-impl.d.ts +19 -0
  124. package/dist/emit/emitters/function-emitter-impl.js +428 -0
  125. package/dist/emit/emitters/line-appender.d.ts +15 -0
  126. package/dist/emit/emitters/line-appender.js +344 -0
  127. package/dist/emit/emitters/namespace-emitter.d.ts +2 -0
  128. package/dist/emit/emitters/namespace-emitter.js +324 -0
  129. package/dist/emit/emitters/output-finalizer.d.ts +4 -0
  130. package/dist/emit/emitters/output-finalizer.js +267 -0
  131. package/dist/emit/emitters/setup.d.ts +3 -0
  132. package/dist/emit/emitters/setup.js +851 -0
  133. package/dist/emit/emitters/top-level-prep.d.ts +2 -0
  134. package/dist/emit/emitters/top-level-prep.js +706 -0
  135. package/dist/emit/emitters/type-decl-emitter.d.ts +2 -0
  136. package/dist/emit/emitters/type-decl-emitter.js +251 -0
  137. package/dist/emit/emitters/ui-emitter.d.ts +21 -0
  138. package/dist/emit/emitters/ui-emitter.js +564 -0
  139. package/dist/emit/enum-emitter.d.ts +1 -0
  140. package/dist/emit/enum-emitter.js +1 -0
  141. package/dist/emit/expression-renderer.d.ts +283 -0
  142. package/dist/emit/expression-renderer.js +1477 -0
  143. package/dist/emit/native-helpers-emitter.d.ts +11 -0
  144. package/dist/emit/native-helpers-emitter.js +34 -0
  145. package/dist/emit/route-hal-op.d.ts +5 -0
  146. package/dist/emit/route-hal-op.js +13 -0
  147. package/dist/emit/snprintf-helpers.d.ts +17 -0
  148. package/dist/emit/snprintf-helpers.js +462 -0
  149. package/dist/emit/statement-renderer.d.ts +189 -0
  150. package/dist/emit/statement-renderer.js +948 -0
  151. package/dist/emit/utils/async-state-machine.d.ts +42 -0
  152. package/dist/emit/utils/async-state-machine.js +343 -0
  153. package/dist/emit/utils/comment-helpers.d.ts +20 -0
  154. package/dist/emit/utils/comment-helpers.js +31 -0
  155. package/dist/emit/utils/cpp-helpers.d.ts +47 -0
  156. package/dist/emit/utils/cpp-helpers.js +85 -0
  157. package/dist/emit/utils/include-resolver.d.ts +58 -0
  158. package/dist/emit/utils/include-resolver.js +155 -0
  159. package/dist/emit/utils/index.d.ts +8 -0
  160. package/dist/emit/utils/index.js +12 -0
  161. package/dist/emit/utils/type-inference.d.ts +159 -0
  162. package/dist/emit/utils/type-inference.js +559 -0
  163. package/dist/eslint-check.d.ts +10 -0
  164. package/dist/eslint-check.js +102 -0
  165. package/dist/framework-package.d.ts +10 -0
  166. package/dist/framework-package.js +58 -0
  167. package/dist/framework-registry.d.ts +39 -0
  168. package/dist/framework-registry.js +30 -0
  169. package/dist/incremental-cache.d.ts +116 -0
  170. package/dist/incremental-cache.js +335 -0
  171. package/dist/ir/adc-range-validation.d.ts +11 -0
  172. package/dist/ir/adc-range-validation.js +250 -0
  173. package/dist/ir/ast-node-utils.d.ts +8 -0
  174. package/dist/ir/ast-node-utils.js +32 -0
  175. package/dist/ir/board-pin-utils.d.ts +13 -0
  176. package/dist/ir/board-pin-utils.js +65 -0
  177. package/dist/ir/board-resolver.d.ts +36 -0
  178. package/dist/ir/board-resolver.js +437 -0
  179. package/dist/ir/build-ir-state.d.ts +167 -0
  180. package/dist/ir/build-ir-state.js +306 -0
  181. package/dist/ir/build-ir.d.ts +2 -0
  182. package/dist/ir/build-ir.js +808 -0
  183. package/dist/ir/call-graph.d.ts +35 -0
  184. package/dist/ir/call-graph.js +194 -0
  185. package/dist/ir/compile-time-only.d.ts +2 -0
  186. package/dist/ir/compile-time-only.js +16 -0
  187. package/dist/ir/declaration-builders.d.ts +9 -0
  188. package/dist/ir/declaration-builders.js +608 -0
  189. package/dist/ir/entry-points.d.ts +30 -0
  190. package/dist/ir/entry-points.js +191 -0
  191. package/dist/ir/expression-to-ir.d.ts +5 -0
  192. package/dist/ir/expression-to-ir.js +2249 -0
  193. package/dist/ir/extract-property-chain.d.ts +10 -0
  194. package/dist/ir/extract-property-chain.js +30 -0
  195. package/dist/ir/feature-prescan.d.ts +3 -0
  196. package/dist/ir/feature-prescan.js +43 -0
  197. package/dist/ir/feature-registry.d.ts +30 -0
  198. package/dist/ir/feature-registry.js +816 -0
  199. package/dist/ir/filter.d.ts +7 -0
  200. package/dist/ir/filter.js +85 -0
  201. package/dist/ir/function-builder.d.ts +19 -0
  202. package/dist/ir/function-builder.js +670 -0
  203. package/dist/ir/hal/hal-emitter.d.ts +62 -0
  204. package/dist/ir/hal/hal-emitter.js +735 -0
  205. package/dist/ir/hal/hal-parser.d.ts +61 -0
  206. package/dist/ir/hal/hal-parser.js +473 -0
  207. package/dist/ir/hal/hal-plugins.d.ts +34 -0
  208. package/dist/ir/hal/hal-plugins.js +706 -0
  209. package/dist/ir/hal-resolver.d.ts +5 -0
  210. package/dist/ir/hal-resolver.js +5 -0
  211. package/dist/ir/heap-analysis.d.ts +8 -0
  212. package/dist/ir/heap-analysis.js +350 -0
  213. package/dist/ir/heap-array-validation.d.ts +24 -0
  214. package/dist/ir/heap-array-validation.js +29 -0
  215. package/dist/ir/identifier-collector.d.ts +18 -0
  216. package/dist/ir/identifier-collector.js +439 -0
  217. package/dist/ir/interrupt-analysis.d.ts +39 -0
  218. package/dist/ir/interrupt-analysis.js +467 -0
  219. package/dist/ir/memory-budget-validation.d.ts +10 -0
  220. package/dist/ir/memory-budget-validation.js +86 -0
  221. package/dist/ir/namespace-builder.d.ts +7 -0
  222. package/dist/ir/namespace-builder.js +152 -0
  223. package/dist/ir/ownership-analysis.d.ts +9 -0
  224. package/dist/ir/ownership-analysis.js +1207 -0
  225. package/dist/ir/peripheral-ownership.d.ts +3 -0
  226. package/dist/ir/peripheral-ownership.js +152 -0
  227. package/dist/ir/peripheral-symbols.d.ts +7 -0
  228. package/dist/ir/peripheral-symbols.js +57 -0
  229. package/dist/ir/peripheral-usage.d.ts +54 -0
  230. package/dist/ir/peripheral-usage.js +691 -0
  231. package/dist/ir/peripheral-validation.d.ts +8 -0
  232. package/dist/ir/peripheral-validation.js +77 -0
  233. package/dist/ir/pin-alias-conflict.d.ts +4 -0
  234. package/dist/ir/pin-alias-conflict.js +34 -0
  235. package/dist/ir/pin-capability-validation.d.ts +6 -0
  236. package/dist/ir/pin-capability-validation.js +413 -0
  237. package/dist/ir/pin-mode-validation.d.ts +10 -0
  238. package/dist/ir/pin-mode-validation.js +301 -0
  239. package/dist/ir/pin-safety.d.ts +11 -0
  240. package/dist/ir/pin-safety.js +58 -0
  241. package/dist/ir/program-analysis.d.ts +44 -0
  242. package/dist/ir/program-analysis.js +589 -0
  243. package/dist/ir/pulldown-validation.d.ts +7 -0
  244. package/dist/ir/pulldown-validation.js +49 -0
  245. package/dist/ir/pwm-timer-sharing.d.ts +4 -0
  246. package/dist/ir/pwm-timer-sharing.js +93 -0
  247. package/dist/ir/reachability.d.ts +68 -0
  248. package/dist/ir/reachability.js +224 -0
  249. package/dist/ir/register-decorators.d.ts +6 -0
  250. package/dist/ir/register-decorators.js +49 -0
  251. package/dist/ir/render-expr.d.ts +4 -0
  252. package/dist/ir/render-expr.js +127 -0
  253. package/dist/ir/resource-analysis.d.ts +19 -0
  254. package/dist/ir/resource-analysis.js +153 -0
  255. package/dist/ir/statement-to-ir.d.ts +23 -0
  256. package/dist/ir/statement-to-ir.js +349 -0
  257. package/dist/ir/symbol-table.d.ts +68 -0
  258. package/dist/ir/symbol-table.js +179 -0
  259. package/dist/ir/symbol-types.d.ts +56 -0
  260. package/dist/ir/symbol-types.js +107 -0
  261. package/dist/ir/timer0-pwm-timing-conflict.d.ts +4 -0
  262. package/dist/ir/timer0-pwm-timing-conflict.js +71 -0
  263. package/dist/ir/timing-validation.d.ts +7 -0
  264. package/dist/ir/timing-validation.js +95 -0
  265. package/dist/ir/transformers/array-methods.d.ts +8 -0
  266. package/dist/ir/transformers/array-methods.js +493 -0
  267. package/dist/ir/transformers/call-statement.d.ts +5 -0
  268. package/dist/ir/transformers/call-statement.js +758 -0
  269. package/dist/ir/transformers/callback-context-registry.d.ts +20 -0
  270. package/dist/ir/transformers/callback-context-registry.js +55 -0
  271. package/dist/ir/transformers/canvas-lowering.d.ts +42 -0
  272. package/dist/ir/transformers/canvas-lowering.js +249 -0
  273. package/dist/ir/transformers/control-flow.d.ts +8 -0
  274. package/dist/ir/transformers/control-flow.js +508 -0
  275. package/dist/ir/transformers/expressions.d.ts +7 -0
  276. package/dist/ir/transformers/expressions.js +406 -0
  277. package/dist/ir/transformers/hal-call-resolver.d.ts +29 -0
  278. package/dist/ir/transformers/hal-call-resolver.js +274 -0
  279. package/dist/ir/transformers/hal-emit-helpers.d.ts +14 -0
  280. package/dist/ir/transformers/hal-emit-helpers.js +63 -0
  281. package/dist/ir/transformers/namespace-methods.d.ts +20 -0
  282. package/dist/ir/transformers/namespace-methods.js +110 -0
  283. package/dist/ir/transformers/register-assignment.d.ts +5 -0
  284. package/dist/ir/transformers/register-assignment.js +56 -0
  285. package/dist/ir/transformers/ui-call-resolver.d.ts +149 -0
  286. package/dist/ir/transformers/ui-call-resolver.js +1136 -0
  287. package/dist/ir/transformers/ui-callback-lowering.d.ts +62 -0
  288. package/dist/ir/transformers/ui-callback-lowering.js +623 -0
  289. package/dist/ir/transformers/ui-mount.d.ts +20 -0
  290. package/dist/ir/transformers/ui-mount.js +35 -0
  291. package/dist/ir/transformers/ui-reactive.d.ts +68 -0
  292. package/dist/ir/transformers/ui-reactive.js +129 -0
  293. package/dist/ir/transformers/variables.d.ts +14 -0
  294. package/dist/ir/transformers/variables.js +1045 -0
  295. package/dist/ir/try-catch-validation.d.ts +26 -0
  296. package/dist/ir/try-catch-validation.js +128 -0
  297. package/dist/ir/type-resolution.d.ts +31 -0
  298. package/dist/ir/type-resolution.js +977 -0
  299. package/dist/ir/ui-element-auto-wire.d.ts +50 -0
  300. package/dist/ir/ui-element-auto-wire.js +218 -0
  301. package/dist/ir/unit-suspicion-validation.d.ts +12 -0
  302. package/dist/ir/unit-suspicion-validation.js +296 -0
  303. package/dist/ir/utils/walk-ir.d.ts +6 -0
  304. package/dist/ir/utils/walk-ir.js +162 -0
  305. package/dist/ir/validation-orchestrator.d.ts +4 -0
  306. package/dist/ir/validation-orchestrator.js +45 -0
  307. package/dist/libdef/base-class-resolver.d.ts +23 -0
  308. package/dist/libdef/base-class-resolver.js +50 -0
  309. package/dist/libdef/cpp-to-decl.d.ts +34 -0
  310. package/dist/libdef/cpp-to-decl.js +584 -0
  311. package/dist/libdef/header-parser.d.ts +53 -0
  312. package/dist/libdef/header-parser.js +269 -0
  313. package/dist/libdef/registry.d.ts +10 -0
  314. package/dist/libdef/registry.js +142 -0
  315. package/dist/mapping/peripheral-names.d.ts +23 -0
  316. package/dist/mapping/peripheral-names.js +63 -0
  317. package/dist/mapping/source-map.d.ts +46 -0
  318. package/dist/mapping/source-map.js +207 -0
  319. package/dist/orchestrator/dts-generator.d.ts +17 -0
  320. package/dist/orchestrator/dts-generator.js +83 -0
  321. package/dist/orchestrator/graph-builder.d.ts +18 -0
  322. package/dist/orchestrator/graph-builder.js +218 -0
  323. package/dist/orchestrator/semantic-facts-verifier.d.ts +38 -0
  324. package/dist/orchestrator/semantic-facts-verifier.js +121 -0
  325. package/dist/orchestrator/semantic-facts.d.ts +141 -0
  326. package/dist/orchestrator/semantic-facts.js +652 -0
  327. package/dist/orchestrator/type-checker.d.ts +39 -0
  328. package/dist/orchestrator/type-checker.js +760 -0
  329. package/dist/platform/async-runtime.d.ts +8 -0
  330. package/dist/platform/async-runtime.js +26 -0
  331. package/dist/platform/generic-strategy.d.ts +101 -0
  332. package/dist/platform/generic-strategy.js +289 -0
  333. package/dist/platform/index.d.ts +4 -0
  334. package/dist/platform/index.js +9 -0
  335. package/dist/platform/registry.d.ts +21 -0
  336. package/dist/platform/registry.js +42 -0
  337. package/dist/platform/toolchain.d.ts +4 -0
  338. package/dist/platform/toolchain.js +43 -0
  339. package/dist/preview/client.d.ts +1 -0
  340. package/dist/preview/client.js +137 -0
  341. package/dist/preview/server.d.ts +5 -0
  342. package/dist/preview/server.js +199 -0
  343. package/dist/profiler/index.d.ts +28 -0
  344. package/dist/profiler/index.js +28 -0
  345. package/dist/profiler/profiler.d.ts +95 -0
  346. package/dist/profiler/profiler.js +338 -0
  347. package/dist/profiler/types.d.ts +87 -0
  348. package/dist/profiler/types.js +12 -0
  349. package/dist/stores/display-profile-store.d.ts +16 -0
  350. package/dist/stores/display-profile-store.js +48 -0
  351. package/dist/stores/theme-store.d.ts +13 -0
  352. package/dist/stores/theme-store.js +30 -0
  353. package/dist/testing.d.ts +45 -0
  354. package/dist/testing.js +47 -0
  355. package/dist/transpile/resolution.d.ts +29 -0
  356. package/dist/transpile/resolution.js +282 -0
  357. package/dist/transpile.d.ts +3 -0
  358. package/dist/transpile.js +722 -0
  359. package/dist/types.d.ts +217 -0
  360. package/dist/types.js +1 -0
  361. package/dist/ui/ui-bridge.d.ts +4 -0
  362. package/dist/ui/ui-bridge.js +34 -0
  363. package/dist/ui-hook.d.ts +84 -0
  364. package/dist/ui-hook.js +45 -0
  365. package/dist/utils/cli.d.ts +3 -0
  366. package/dist/utils/cli.js +463 -0
  367. package/dist/utils/fs.d.ts +4 -0
  368. package/dist/utils/fs.js +23 -0
  369. package/dist/utils/logger.d.ts +2 -0
  370. package/dist/utils/logger.js +8 -0
  371. package/dist/utils/strings.d.ts +35 -0
  372. package/dist/utils/strings.js +142 -0
  373. package/dist/utils/ui.d.ts +66 -0
  374. package/dist/utils/ui.js +149 -0
  375. package/dist/watch.d.ts +35 -0
  376. package/dist/watch.js +111 -0
  377. package/package.json +127 -0
@@ -0,0 +1,760 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import ts from "typescript";
4
+ import { makeDiagnostic } from "../ir/ast-node-utils.js";
5
+ import { canonicalize, buildSemanticFacts } from "./semantic-facts.js";
6
+ import { verifyFacts } from "./semantic-facts-verifier.js";
7
+ import { requireUIHook } from "../ui-hook.js";
8
+ /**
9
+ * Type-checks TypeScript files using the TypeScript compiler.
10
+ * Returns early if any errors are found.
11
+ *
12
+ * @param files List of TypeScript files to type-check
13
+ * @param _boardPackage Optional board package for resolving @typecad/board imports
14
+ * @param entryFile Entrypoint sketch/main file
15
+ * @returns TypeCheckResult with success status and any error messages
16
+ */
17
+ export function typeCheckFiles(files, _boardPackage, entryFile) {
18
+ // Find the nearest tsconfig.json by walking up from the entry file (preferred)
19
+ // or the first file in the graph. Using the entry file ensures we pick up the
20
+ // user's tsconfig (with path mappings) rather than a dependency's tsconfig.
21
+ let configPath;
22
+ let currentDir = path.dirname(entryFile ?? files[0]);
23
+ while (currentDir !== path.dirname(currentDir)) {
24
+ const candidate = path.join(currentDir, "tsconfig.json");
25
+ if (fs.existsSync(candidate)) {
26
+ configPath = candidate;
27
+ break;
28
+ }
29
+ currentDir = path.dirname(currentDir);
30
+ }
31
+ // Read compiler options from tsconfig.json if found
32
+ let compilerOptions = {
33
+ noEmit: true,
34
+ strict: true,
35
+ skipLibCheck: true,
36
+ esModuleInterop: true,
37
+ moduleResolution: ts.ModuleResolutionKind.Node10,
38
+ };
39
+ let rootNames = [...files];
40
+ if (configPath) {
41
+ const configResult = ts.readConfigFile(configPath, (path) => fs.readFileSync(path, "utf8"));
42
+ if (!configResult.error) {
43
+ const parsedConfig = ts.parseJsonConfigFileContent(configResult.config, ts.sys, path.dirname(configPath));
44
+ if (!parsedConfig.errors.length) {
45
+ // `allowArbitraryExtensions` lets Node16 module resolution type-check
46
+ // non-JS module imports such as `.ui.html` via generated
47
+ // `<base>.d.<ext>.ts` declarations.
48
+ compilerOptions = { ...parsedConfig.options, noEmit: true, allowArbitraryExtensions: true };
49
+ // Include cuttlefish-env.d.ts so module augmentations are visible to the type-checker
50
+ const envDts = path.join(path.dirname(configPath), ".cuttlefish", "cuttlefish-env.d.ts");
51
+ if (fs.existsSync(envDts) && !rootNames.includes(envDts)) {
52
+ rootNames.push(envDts);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ const uiModules = requireUIHook().allUIModules();
58
+ if (uiModules.length > 0) {
59
+ compilerOptions.allowArbitraryExtensions = true;
60
+ const rootDirs = new Set((compilerOptions.rootDirs ?? []).map((dir) => path.resolve(dir)));
61
+ for (const mod of uiModules) {
62
+ rootDirs.add(mod.typeDeclSourceRoot);
63
+ rootDirs.add(mod.typeDeclRoot);
64
+ if (fs.existsSync(mod.typeDeclPath) && !rootNames.includes(mod.typeDeclPath)) {
65
+ rootNames.push(mod.typeDeclPath);
66
+ }
67
+ }
68
+ compilerOptions.rootDirs = [...rootDirs];
69
+ }
70
+ // Create a TypeScript program with the transpile graph files, using compiler options from tsconfig
71
+ const program = ts.createProgram(rootNames, compilerOptions);
72
+ // Collect all diagnostics
73
+ const allDiagnostics = [
74
+ ...program.getSyntacticDiagnostics(),
75
+ ...program.getSemanticDiagnostics(),
76
+ ...program.getGlobalDiagnostics(),
77
+ ];
78
+ // Filter to only errors (ignore suggestions and hints)
79
+ // Also skip errors from files in node_modules or packages directories (not user code)
80
+ const errors = allDiagnostics.filter(d => {
81
+ if (d.category !== ts.DiagnosticCategory.Error) {
82
+ return false;
83
+ }
84
+ // Include errors without a file (global errors)
85
+ if (!d.file) {
86
+ return true;
87
+ }
88
+ const filePath = d.file.fileName.replace(/\\/g, "/");
89
+ // Skip errors from node_modules and internal packages
90
+ if (filePath.includes("/node_modules/") || filePath.includes("/packages/")) {
91
+ return false;
92
+ }
93
+ return true;
94
+ });
95
+ if (errors.length === 0) {
96
+ return { success: true, errors: [], generatedDecls: [], program };
97
+ }
98
+ // Format error messages
99
+ const formattedErrors = [];
100
+ for (const error of errors) {
101
+ const message = ts.flattenDiagnosticMessageText(error.messageText, "\n");
102
+ if (error.file && error.start !== undefined) {
103
+ const { line, character } = error.file.getLineAndCharacterOfPosition(error.start);
104
+ const relativePath = path.relative(process.cwd(), error.file.fileName);
105
+ formattedErrors.push(`${relativePath}(${line + 1}:${character + 1}): ${message}`);
106
+ }
107
+ else {
108
+ formattedErrors.push(message);
109
+ }
110
+ }
111
+ return { success: false, errors: formattedErrors, generatedDecls: [] };
112
+ }
113
+ // ---------------------------------------------------------------------------
114
+ // Semantic gates (Phase 3)
115
+ //
116
+ // These checks require a TypeChecker and therefore cannot live in the
117
+ // syntactic feature-prescan. They run after a successful typeCheckFiles and
118
+ // emit structured Diagnostic[] (severity "error") that flow through the
119
+ // normal ProgramIR.diagnostics channel. They never throw.
120
+ // ---------------------------------------------------------------------------
121
+ /**
122
+ * Bucket a resolved type into a coarse "kind" used to detect heterogeneous
123
+ * array literals. Numeric and boolean collapse to one bucket because the
124
+ * transpiler widens bool to int silently (an accepted trade-off — see Q2 of
125
+ * the semantic-gate plan). `any`, nullish types, and `void` are skipped.
126
+ */
127
+ function classifyElementType(type) {
128
+ const flags = type.flags;
129
+ // Skip `any`/`unknown` — the explicit-any gate (Phase 2) already flags the
130
+ // source. Skip null/undefined/void — they do not conflict with primitives.
131
+ if (flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Null |
132
+ ts.TypeFlags.Undefined | ts.TypeFlags.Void | ts.TypeFlags.Never)) {
133
+ return null;
134
+ }
135
+ if (flags & (ts.TypeFlags.NumberLike | ts.TypeFlags.BooleanLike)) {
136
+ return "numeric";
137
+ }
138
+ if (flags & (ts.TypeFlags.StringLike)) {
139
+ return "string";
140
+ }
141
+ // BigInt widens to a distinct C++ type — treat like numeric for the
142
+ // widening-friendly case but as its own signal if it's the only object-ish
143
+ // thing? Keep it simple: bigint is numeric-compatible.
144
+ if (flags & ts.TypeFlags.BigIntLike) {
145
+ return "numeric";
146
+ }
147
+ // Anything else (object, class, enum, union of distinct primitives, array,
148
+ // function, etc.) is an object bucket.
149
+ return "object";
150
+ }
151
+ const FUNCTIONAL_METHOD_NAMES = new Set([
152
+ "forEach",
153
+ "map",
154
+ "filter",
155
+ "reduce",
156
+ "reduceRight",
157
+ "find",
158
+ "findIndex",
159
+ "findLast",
160
+ "some",
161
+ "every",
162
+ "flatMap",
163
+ ]);
164
+ const MUTATING_ARRAY_METHOD_NAMES = new Set([
165
+ "push",
166
+ "pop",
167
+ "shift",
168
+ "unshift",
169
+ "splice",
170
+ "sort",
171
+ "reverse",
172
+ "fill",
173
+ "copyWithin",
174
+ ]);
175
+ function createChildScope(parent, overrides = {}) {
176
+ return {
177
+ parent,
178
+ functionNode: parent?.functionNode,
179
+ inClassMethod: parent?.inClassMethod ?? false,
180
+ ...overrides,
181
+ };
182
+ }
183
+ function isFunctionLikeWithBody(node) {
184
+ return ((ts.isFunctionDeclaration(node) ||
185
+ ts.isMethodDeclaration(node) ||
186
+ ts.isConstructorDeclaration(node) ||
187
+ ts.isGetAccessorDeclaration(node) ||
188
+ ts.isSetAccessorDeclaration(node) ||
189
+ ts.isFunctionExpression(node) ||
190
+ ts.isArrowFunction(node)) &&
191
+ !!node.body);
192
+ }
193
+ function isClassMethodLike(node) {
194
+ return ((ts.isMethodDeclaration(node) ||
195
+ ts.isConstructorDeclaration(node) ||
196
+ ts.isGetAccessorDeclaration(node) ||
197
+ ts.isSetAccessorDeclaration(node)) &&
198
+ (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent)));
199
+ }
200
+ function unwrapExpression(expr) {
201
+ let current = expr;
202
+ while (true) {
203
+ if (ts.isParenthesizedExpression(current) ||
204
+ ts.isNonNullExpression(current) ||
205
+ ts.isAsExpression(current) ||
206
+ ts.isTypeAssertionExpression(current)) {
207
+ current = current.expression;
208
+ continue;
209
+ }
210
+ if (ts.isSatisfiesExpression?.(current)) {
211
+ current = current.expression;
212
+ continue;
213
+ }
214
+ return current;
215
+ }
216
+ }
217
+ // Type classification now lives in semantic-facts.ts#canonicalize, which is
218
+ // the single source of truth for "what kind of C++ concept does this type
219
+ // lower to?" These wrappers preserve the existing call sites exactly while
220
+ // routing through canonicalize(). Flag-based helpers (isStringLikeType,
221
+ // typeIncludesNullish) stay here because they test bit flags, not categories.
222
+ function isMapLikeType(checker, type) {
223
+ return canonicalize(checker, type) === "map";
224
+ }
225
+ function isSetLikeType(checker, type) {
226
+ return canonicalize(checker, type) === "set";
227
+ }
228
+ function isTypedArrayType(checker, type) {
229
+ return canonicalize(checker, type) === "typed-array";
230
+ }
231
+ function isArrayLikeType(checker, type) {
232
+ return canonicalize(checker, type) === "array";
233
+ }
234
+ function isStringLikeType(type) {
235
+ return (type.flags & ts.TypeFlags.StringLike) !== 0;
236
+ }
237
+ function isContainerType(checker, type) {
238
+ const c = canonicalize(checker, type);
239
+ return c === "map" || c === "set";
240
+ }
241
+ function typeIncludesNullish(type) {
242
+ if (type.isUnion())
243
+ return type.types.some(typeIncludesNullish);
244
+ return (type.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void)) !== 0;
245
+ }
246
+ function isNullishExpression(node) {
247
+ const expr = unwrapExpression(node);
248
+ return expr.kind === ts.SyntaxKind.NullKeyword ||
249
+ (ts.isIdentifier(expr) && expr.text === "undefined");
250
+ }
251
+ function isNullishCompareOperator(kind) {
252
+ return kind === ts.SyntaxKind.EqualsEqualsToken ||
253
+ kind === ts.SyntaxKind.EqualsEqualsEqualsToken ||
254
+ kind === ts.SyntaxKind.ExclamationEqualsToken ||
255
+ kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
256
+ }
257
+ function isAssignmentOperatorKind(kind) {
258
+ return kind === ts.SyntaxKind.EqualsToken ||
259
+ kind === ts.SyntaxKind.PlusEqualsToken ||
260
+ kind === ts.SyntaxKind.MinusEqualsToken ||
261
+ kind === ts.SyntaxKind.AsteriskEqualsToken ||
262
+ kind === ts.SyntaxKind.AsteriskAsteriskEqualsToken ||
263
+ kind === ts.SyntaxKind.SlashEqualsToken ||
264
+ kind === ts.SyntaxKind.PercentEqualsToken ||
265
+ kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||
266
+ kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||
267
+ kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken ||
268
+ kind === ts.SyntaxKind.AmpersandEqualsToken ||
269
+ kind === ts.SyntaxKind.BarEqualsToken ||
270
+ kind === ts.SyntaxKind.CaretEqualsToken ||
271
+ kind === ts.SyntaxKind.AmpersandAmpersandEqualsToken ||
272
+ kind === ts.SyntaxKind.BarBarEqualsToken ||
273
+ kind === ts.SyntaxKind.QuestionQuestionEqualsToken;
274
+ }
275
+ function getRootIdentifierFromAccess(expr) {
276
+ const current = unwrapExpression(expr);
277
+ if (ts.isIdentifier(current))
278
+ return current;
279
+ if (ts.isPropertyAccessExpression(current) || ts.isElementAccessExpression(current)) {
280
+ return getRootIdentifierFromAccess(current.expression);
281
+ }
282
+ return undefined;
283
+ }
284
+ function isCompoundAccess(expr) {
285
+ const current = unwrapExpression(expr);
286
+ return ts.isPropertyAccessExpression(current) || ts.isElementAccessExpression(current);
287
+ }
288
+ function isContainerLookupCall(node, checker) {
289
+ const expr = unwrapExpression(node);
290
+ if (!ts.isCallExpression(expr))
291
+ return false;
292
+ const callee = unwrapExpression(expr.expression);
293
+ if (!ts.isPropertyAccessExpression(callee))
294
+ return false;
295
+ const methodName = callee.name.text;
296
+ if (methodName !== "get" && methodName !== "at")
297
+ return false;
298
+ const receiverType = checker.getTypeAtLocation(callee.expression);
299
+ if (methodName === "get") {
300
+ return isMapLikeType(checker, receiverType);
301
+ }
302
+ return isMapLikeType(checker, receiverType) ||
303
+ isArrayLikeType(checker, receiverType) ||
304
+ isTypedArrayType(checker, receiverType) ||
305
+ isStringLikeType(receiverType);
306
+ }
307
+ function getContainerLookupMethodName(node) {
308
+ const expr = unwrapExpression(node);
309
+ if (!ts.isCallExpression(expr))
310
+ return undefined;
311
+ const callee = unwrapExpression(expr.expression);
312
+ return ts.isPropertyAccessExpression(callee) ? callee.name.text : undefined;
313
+ }
314
+ function isOptionalFieldAccess(node, checker) {
315
+ const expr = unwrapExpression(node);
316
+ if (!ts.isPropertyAccessExpression(expr))
317
+ return false;
318
+ const symbol = checker.getSymbolAtLocation(expr.name);
319
+ const declarations = symbol?.declarations ?? [];
320
+ if (declarations.some(decl => (ts.isPropertySignature(decl) || ts.isPropertyDeclaration(decl) || ts.isParameter(decl)) &&
321
+ !!decl.questionToken)) {
322
+ return true;
323
+ }
324
+ return typeIncludesNullish(checker.getTypeAtLocation(expr));
325
+ }
326
+ function getFunctionReturnTypeNode(node) {
327
+ if (ts.isConstructorDeclaration(node))
328
+ return undefined;
329
+ return node.type;
330
+ }
331
+ function getCallbackFunction(node) {
332
+ const expr = unwrapExpression(node);
333
+ if ((ts.isArrowFunction(expr) || ts.isFunctionExpression(expr)) && expr.body) {
334
+ return expr;
335
+ }
336
+ return undefined;
337
+ }
338
+ function nodeContains(container, candidate) {
339
+ return candidate.pos >= container.pos && candidate.end <= container.end;
340
+ }
341
+ function isCaptureInsideCallback(callback, enclosingFunction, checker) {
342
+ let captured = false;
343
+ const visit = (node) => {
344
+ if (captured)
345
+ return;
346
+ if (node.kind === ts.SyntaxKind.ThisKeyword) {
347
+ captured = true;
348
+ return;
349
+ }
350
+ if (ts.isIdentifier(node)) {
351
+ const symbol = checker.getSymbolAtLocation(node);
352
+ const declarations = symbol?.declarations ?? [];
353
+ if (declarations.some(decl => nodeContains(enclosingFunction, decl) &&
354
+ !nodeContains(callback, decl) &&
355
+ decl.getSourceFile() === callback.getSourceFile())) {
356
+ captured = true;
357
+ return;
358
+ }
359
+ }
360
+ ts.forEachChild(node, visit);
361
+ };
362
+ ts.forEachChild(callback.body, visit);
363
+ return captured;
364
+ }
365
+ function makeSemanticGateDiagnostic(sourceText, node, message, code, hint) {
366
+ const diag = makeDiagnostic(sourceText, node.getStart(), message, "error", code);
367
+ diag.hint = hint;
368
+ diag.sourceLine = extractLine(sourceText, diag.line);
369
+ diag.source = "semantic-gate";
370
+ return diag;
371
+ }
372
+ /**
373
+ * Run semantic gates against the user files of a type-checked program.
374
+ *
375
+ * @param program The TypeScript Program built by typeCheckFiles.
376
+ * @param userFiles Absolute paths of user (transpile-graph) files to scan.
377
+ * Files outside this set (node_modules, internal packages)
378
+ * are skipped even if present in the program.
379
+ * @returns Diagnostic[] — never throws.
380
+ */
381
+ export function runSemanticGates(program, userFiles) {
382
+ const diagnostics = [];
383
+ const checker = program.getTypeChecker();
384
+ const seenDiagnostics = new Set();
385
+ // Build the SemanticFacts store + binding resolver once. The mutation gates
386
+ // (TS2CPP_MAP_VALUE_COPY_MUTATION, TS2CPP_ARRAY_PARAM_MUTATION,
387
+ // TS2CPP_TYPED_ARRAY_PARAM_LENGTH) consume the resolver instead of the old
388
+ // name-based scope-chain Sets, which were shadowing-fragile.
389
+ const { facts, diagnostics: analysisDiagnostics, resolver } = buildSemanticFacts(program, userFiles);
390
+ diagnostics.push(...analysisDiagnostics);
391
+ // Phase 3 completeness verifier. Walks every expression and reports any
392
+ // whose canonical type is "unknown" (any/unknown leak or an unclassified
393
+ // type). Severity defaults to "warning" so existing builds aren't broken —
394
+ // see semantic-facts-verifier.ts header for the rationale. This is the last
395
+ // producer of analysis diagnostics; the gates below produce rule diagnostics.
396
+ //
397
+ // The FactStore is passed in so the verifier's existing walk also POPULATES
398
+ // the concrete per-expression type facts (SemanticFacts.type + cppType) —
399
+ // Phase 3 of the type-resolution consolidation. Gate rules can then read
400
+ // facts.cppType without re-deriving it from the ts.TypeChecker.
401
+ const verifierResult = verifyFacts(program, userFiles, { facts });
402
+ diagnostics.push(...verifierResult.diagnostics);
403
+ // Normalise the user-file set for membership lookup.
404
+ const userFileSet = new Set(userFiles.map(f => f.replace(/\\/g, "/")));
405
+ // Pre-compute the set of interface names across all user files. Interfaces
406
+ // are type-only: `getSymbolAtLocation` on `new IFoo()` returns undefined
407
+ // (interfaces have no value symbol), so we resolve the name against this set
408
+ // instead. Names are unique enough for diagnostic purposes; if a class and
409
+ // an interface share a name in the same scope, TS itself errors.
410
+ const interfaceNames = new Set();
411
+ for (const sf of program.getSourceFiles()) {
412
+ const p = sf.fileName.replace(/\\/g, "/");
413
+ if (!userFileSet.has(p))
414
+ continue;
415
+ if (p.includes("/node_modules/") || p.includes("/packages/"))
416
+ continue;
417
+ const collect = (n) => {
418
+ if (ts.isInterfaceDeclaration(n) && n.name) {
419
+ interfaceNames.add(n.name.text);
420
+ }
421
+ ts.forEachChild(n, collect);
422
+ };
423
+ ts.forEachChild(sf, collect);
424
+ }
425
+ // ── Global name-collision pre-scan (demo #25 Finding B) ──────────────────
426
+ // A user `class Node {}` (or interface/enum/type alias) whose name collides
427
+ // with a globally-visible lib declaration (the DOM `Node`, `Element`,
428
+ // `Event`, etc.) is shadowed by that global at every unqualified use site,
429
+ // producing a cascade of spurious "property does not exist" / "duplicate
430
+ // identifier" errors from the TS type-checker that obscure the real cause.
431
+ // Detect the collision up front and report one clear diagnostic per
432
+ // colliding declaration.
433
+ //
434
+ // We build the set of global names declared across the non-user program
435
+ // files (lib.d.ts, lib.dom.d.ts, ambient .d.ts) and flag any user top-level
436
+ // type declaration (class / interface / enum / type alias) whose name
437
+ // appears in that set. This is deterministic and does not depend on the
438
+ // checker's name-resolution quirks. (The scaffolded tsconfig no longer
439
+ // ships with "dom" in lib, so new projects never collide; this check is
440
+ // defense-in-depth for projects that add it back or otherwise pull in DOM
441
+ // globals.)
442
+ const globalNames = new Set();
443
+ for (const sf of program.getSourceFiles()) {
444
+ const p = sf.fileName.replace(/\\/g, "/");
445
+ // Only NON-user files contribute globals (lib + ambient declarations).
446
+ if (userFileSet.has(p))
447
+ continue;
448
+ // Demo #27 Finding F — third-party `@types/*` packages (e.g.
449
+ // `@types/node`) are loaded into the TS Program from the repo-root
450
+ // `node_modules` even when the user's tsconfig sets `"types": []` (TS
451
+ // still *loads* the files for transitive resolution; `types: []` only
452
+ // suppresses their automatic global-visibility). Without this filter,
453
+ // common short names declared in `@types/node` (`Mode`, `CipherMode`,
454
+ // `Direction`, `Event`, ...) leak into `globalNames` and false-trip this
455
+ // gate on idiomatic user enums.
456
+ //
457
+ // We exclude ONLY `/node_modules/@types/` — NOT all of `node_modules`,
458
+ // because TypeScript's own default-lib files (`lib.dom.d.ts`,
459
+ // `lib.es2021.d.ts`, ...) live under `node_modules/typescript/lib/` and
460
+ // DEFINE the globals this gate exists to catch (the DOM `Node`/`Element`
461
+ // /`Event` that shadow a user class when a project adds `"dom"` back to
462
+ // lib). Internal `packages/` are also excluded (they ship the transpiler
463
+ // itself, not user-visible globals).
464
+ if (p.includes("/node_modules/@types/") || p.includes("/packages/"))
465
+ continue;
466
+ const collectGlobals = (n) => {
467
+ if (ts.isVariableStatement(n)) {
468
+ for (const d of n.declarationList.declarations) {
469
+ if (ts.isIdentifier(d.name))
470
+ globalNames.add(d.name.text);
471
+ }
472
+ }
473
+ else if ((ts.isInterfaceDeclaration(n) ||
474
+ ts.isClassDeclaration(n) ||
475
+ ts.isEnumDeclaration(n) ||
476
+ ts.isTypeAliasDeclaration(n) ||
477
+ ts.isFunctionDeclaration(n)) &&
478
+ n.name) {
479
+ globalNames.add(n.name.text);
480
+ }
481
+ ts.forEachChild(n, collectGlobals);
482
+ };
483
+ ts.forEachChild(sf, collectGlobals);
484
+ }
485
+ const reportedCollisions = new Set();
486
+ const reportNameCollision = (nameNode, kindLabel) => {
487
+ const name = nameNode.text;
488
+ if (!globalNames.has(name))
489
+ return;
490
+ const filePath = nameNode.getSourceFile().fileName.replace(/\\/g, "/");
491
+ if (!userFileSet.has(filePath))
492
+ return;
493
+ const key = `${filePath}:${name}`;
494
+ if (reportedCollisions.has(key))
495
+ return;
496
+ reportedCollisions.add(key);
497
+ const sourceText = nameNode.getSourceFile().getFullText();
498
+ diagnostics.push(makeSemanticGateDiagnostic(sourceText, nameNode, `${kindLabel} '${name}' collides with a global type of the same name from a lib/ambient declaration. The global shadows this declaration at every unqualified use site, producing spurious type errors.`, "TS2CPP_GLOBAL_NAME_COLLISION", "Rename the declaration, or remove the colliding lib (e.g. drop \"dom\" from tsconfig \"lib\" — the scaffolded console typings live in cuttlefish-env.d.ts)."));
499
+ };
500
+ for (const sourceFile of program.getSourceFiles()) {
501
+ const filePath = sourceFile.fileName.replace(/\\/g, "/");
502
+ // Only scan user code — skip lib.d.ts, node_modules, and internal packages.
503
+ if (!userFileSet.has(filePath))
504
+ continue;
505
+ if (filePath.includes("/node_modules/") || filePath.includes("/packages/")) {
506
+ continue;
507
+ }
508
+ const sourceText = sourceFile.getFullText();
509
+ const pushDiag = (node, message, code, hint) => {
510
+ const key = `${filePath}:${code}:${node.getStart(sourceFile)}`;
511
+ if (seenDiagnostics.has(key))
512
+ return;
513
+ seenDiagnostics.add(key);
514
+ diagnostics.push(makeSemanticGateDiagnostic(sourceText, node, message, code, hint));
515
+ };
516
+ const visit = (node, scope) => {
517
+ if (ts.isSourceFile(node)) {
518
+ ts.forEachChild(node, child => visit(child, scope));
519
+ return;
520
+ }
521
+ // Global name-collision pre-scan: a user class/interface/enum/type alias
522
+ // whose name matches a lib global is shadowed at every use site.
523
+ if (ts.isClassDeclaration(node) && node.name)
524
+ reportNameCollision(node.name, "Class");
525
+ else if (ts.isInterfaceDeclaration(node) && node.name)
526
+ reportNameCollision(node.name, "Interface");
527
+ else if (ts.isEnumDeclaration(node) && node.name)
528
+ reportNameCollision(node.name, "Enum");
529
+ else if (ts.isTypeAliasDeclaration(node) && node.name)
530
+ reportNameCollision(node.name, "Type alias");
531
+ if (isFunctionLikeWithBody(node)) {
532
+ const functionScope = createChildScope(scope, {
533
+ functionNode: node,
534
+ inClassMethod: isClassMethodLike(node),
535
+ });
536
+ const returnTypeNode = getFunctionReturnTypeNode(node);
537
+ if (returnTypeNode && isTypedArrayType(checker, checker.getTypeFromTypeNode(returnTypeNode))) {
538
+ pushDiag(returnTypeNode, "Returning a typed array is not supported because typed arrays lower to pointer-like storage in C++.", "TS2CPP_TYPED_ARRAY_RETURN", "Use an out-parameter plus an explicit length, or return a fixed-shape struct that owns its storage.");
539
+ }
540
+ // Function-parameter origins (array-param / typed-array-param) are
541
+ // now recorded by the SemanticFacts binding pass — no scope tracking.
542
+ visit(node.body, functionScope);
543
+ return;
544
+ }
545
+ if (ts.isBlock(node)) {
546
+ const blockScope = createChildScope(scope);
547
+ ts.forEachChild(node, child => visit(child, blockScope));
548
+ return;
549
+ }
550
+ // 1. Heterogeneous array literals.
551
+ if (ts.isArrayLiteralExpression(node)) {
552
+ const contextual = checker.getContextualType(node);
553
+ // Tuple contextual types are intentionally heterogeneous — skip.
554
+ const isTupleContext = contextual && checker.isTupleType(contextual);
555
+ if (!isTupleContext && node.elements.length > 1) {
556
+ const buckets = new Set();
557
+ for (const element of node.elements) {
558
+ // Spread elements ([...x]) — skip rather than guess.
559
+ if (ts.isSpreadElement(element))
560
+ continue;
561
+ const t = checker.getTypeAtLocation(element);
562
+ const bucket = classifyElementType(t);
563
+ if (bucket)
564
+ buckets.add(bucket);
565
+ }
566
+ if (buckets.size > 1) {
567
+ pushDiag(node, "Heterogeneous array literal has no single C++ element type.", "TS2CPP_HETEROGENEOUS_ARRAY", "Use a uniform element type (all numbers, all strings, or all objects), or declare an explicit tuple type: [number, string].");
568
+ }
569
+ }
570
+ }
571
+ // 2. new on interface (symbol-based — supersedes the Phase 2 heuristic
572
+ // by resolving across files).
573
+ if (ts.isNewExpression(node)) {
574
+ const target = node.expression;
575
+ if (ts.isIdentifier(target)) {
576
+ const name = target.text;
577
+ // Resolve the value symbol (classes have one; interfaces do not).
578
+ let sym = checker.getSymbolAtLocation(target);
579
+ if (sym && (sym.flags & ts.SymbolFlags.Alias)) {
580
+ try {
581
+ sym = checker.getAliasedSymbol(sym);
582
+ }
583
+ catch { /* keep sym */ }
584
+ }
585
+ const decl = sym?.valueDeclaration ?? sym?.declarations?.[0];
586
+ // Flag if the resolved declaration is an interface, OR if there is no
587
+ // value symbol (interface used as a value) and the name matches a
588
+ // known interface declaration anywhere in the program.
589
+ const isInterfaceDecl = !!decl && ts.isInterfaceDeclaration(decl);
590
+ const isInterfaceByName = !sym && interfaceNames.has(name);
591
+ if (isInterfaceDecl || isInterfaceByName) {
592
+ pushDiag(node, `Cannot instantiate interface '${name}' — only class constructors are supported in C++.`, "TS2CPP_NEW_ON_INTERFACE", `Change 'interface ${name}' to 'class ${name}', or call a factory that returns a concrete class instance.`);
593
+ }
594
+ }
595
+ }
596
+ // 3. for...in over a Map / Record. `for (const k in m)` where `m` is a
597
+ // Map or Record lowers to iterating std::map pairs and indexing by a
598
+ // pair (malformed). Reject it at the semantic gate so users get a
599
+ // clear error before the broken C++ is emitted. Plain-object for...in
600
+ // is a separate path and is allowed.
601
+ if (ts.isForInStatement(node)) {
602
+ const iterType = checker.getTypeAtLocation(node.expression);
603
+ if (isMapLikeType(checker, iterType)) {
604
+ const typeStr = checker.typeToString(iterType);
605
+ pushDiag(node, `for...in over a ${typeStr} is not supported — the Map lowering iterates key-value pairs, not keys.`, "TS2CPP_FORIN_ON_MAP", "Use a for...of loop over Object.keys(m), Object.values(m), or Object.entries(m), depending on which part of the Map you need.");
606
+ }
607
+ }
608
+ // 4. Member access on a discriminated-union (std::variant) type.
609
+ // `m.kind` / `m.payload` where `m: A | B` lowers to a std::variant,
610
+ // but member access doesn't lower to std::get_if/std::holds_alternative,
611
+ // so dispatch is broken end-to-end. Reject it with a clear message.
612
+ // (Nullable unions `T | null` are excluded — they erase to T and the
613
+ // value-type null comparison is handled separately.)
614
+ if (ts.isPropertyAccessExpression(node)) {
615
+ const baseType = checker.getTypeAtLocation(node.expression);
616
+ const typeStr = checker.typeToString(baseType);
617
+ // A non-nullable union: either the typeStr shows "A | B", or the TS
618
+ // Type object is a union whose constituents aren't null/undefined.
619
+ const isNonNullableUnion = (baseType.isUnion() && baseType.types.every(t => {
620
+ const s = checker.typeToString(t);
621
+ return s !== "null" && s !== "undefined";
622
+ })) || (typeStr.includes(" | ") && !/\b(null|undefined)\b/.test(typeStr));
623
+ if (isNonNullableUnion) {
624
+ pushDiag(node, `Member access '${node.getText()}' on a union type '${typeStr}' is not supported — the union lowers to std::variant, but member access doesn't lower to std::get_if/std::holds_alternative.`, "TS2CPP_UNION_MEMBER_ACCESS", "Use a struct with a discriminator field, or narrow via a type guard before access.");
625
+ }
626
+ }
627
+ // 5. (Removed) Locals initialized from a Map/Record value were tracked
628
+ // here by name into scope.mapValueCopyBindings. That origin is now
629
+ // recorded on the binding node by the SemanticFacts binding pass and
630
+ // consumed via resolver.resolveOrigin() in gate 7 below.
631
+ // 6. Nullish checks on container lookup calls. `.get()` / `.at()` lower
632
+ // to presence-asserting lookups (for example `std::map::at`), not an
633
+ // optional value.
634
+ if (ts.isBinaryExpression(node) && isNullishCompareOperator(node.operatorToken.kind)) {
635
+ const leftNullish = isNullishExpression(node.left);
636
+ const rightNullish = isNullishExpression(node.right);
637
+ const otherSide = leftNullish ? node.right : rightNullish ? node.left : undefined;
638
+ if (otherSide && isContainerLookupCall(otherSide, checker)) {
639
+ const methodName = getContainerLookupMethodName(otherSide);
640
+ pushDiag(node, `Comparing .${methodName ?? "get"}() to null or undefined is not supported by the C++ lowering.`, "TS2CPP_GET_NULLISH_COMPARE", "Use .has(key) before .get(key), or restructure the value as an explicit { present, value } result.");
641
+ }
642
+ if (otherSide && isOptionalFieldAccess(otherSide, checker)) {
643
+ pushDiag(node, "Comparing an optional struct/interface field to null or undefined is not supported because optionality is flattened in C++.", "TS2CPP_OPTIONAL_FIELD_NULLISH", "Use an explicit boolean flag, sentinel enum, or separate Map/Set membership check instead of relying on x?: T.");
644
+ }
645
+ }
646
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
647
+ if (isContainerLookupCall(node.left, checker)) {
648
+ pushDiag(node, "Nullish coalescing on .get()/.at() is not supported by the C++ lowering.", "TS2CPP_GET_NULLISH_COMPARE", "Use .has(key) before .get(key), or restructure the value as an explicit { present, value } result.");
649
+ }
650
+ if (isOptionalFieldAccess(node.left, checker)) {
651
+ pushDiag(node, "Nullish coalescing on an optional struct/interface field is not supported because optionality is flattened in C++.", "TS2CPP_OPTIONAL_FIELD_NULLISH", "Use an explicit boolean flag, sentinel enum, or separate Map/Set membership check instead of relying on x?: T.");
652
+ }
653
+ }
654
+ // 7. Mutating fields of a Map/Record-fetched copy, or of a by-value
655
+ // array/typed-array parameter. One rule over the root identifier's
656
+ // resolved origin — covers assignment (=, op=, ??=, ...) and
657
+ // prefix/postfix ++/--, instead of two near-identical branches.
658
+ // Origin comes from the SemanticFacts binding pass, not scope Sets.
659
+ const reportMutationOnLValue = (lvalue) => {
660
+ const root = getRootIdentifierFromAccess(lvalue);
661
+ if (!root)
662
+ return;
663
+ const origin = resolver.resolveOrigin(root);
664
+ if (origin === "map-value-lookup") {
665
+ pushDiag(lvalue, `'${root.text}' is a value copy fetched from a Map/Record; mutating a field on it will not update the container.`, "TS2CPP_MAP_VALUE_COPY_MUTATION", "Store primitive mutable state in a separate Map and .set() it back, or replace the whole struct entry with .set(key, nextValue).");
666
+ }
667
+ else if (origin === "array-param") {
668
+ pushDiag(lvalue, `Mutating '${root.text}' through an indexed/property access mutates only the C++ parameter copy.`, "TS2CPP_ARRAY_PARAM_MUTATION", "Return the updated array, pass a mutable owner object, or move the mutation to the caller-side container.");
669
+ }
670
+ };
671
+ if (ts.isBinaryExpression(node) && isAssignmentOperatorKind(node.operatorToken.kind) && isCompoundAccess(node.left)) {
672
+ reportMutationOnLValue(node.left);
673
+ }
674
+ if ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) &&
675
+ (node.operator === ts.SyntaxKind.PlusPlusToken || node.operator === ts.SyntaxKind.MinusMinusToken) &&
676
+ isCompoundAccess(node.operand)) {
677
+ reportMutationOnLValue(node.operand);
678
+ }
679
+ // 8. Typed-array parameters lower pointer-like; their length is not
680
+ // recoverable from the parameter expression. Origin comes from the
681
+ // SemanticFacts binding pass.
682
+ if (ts.isPropertyAccessExpression(node) && node.name.text === "length") {
683
+ const receiver = unwrapExpression(node.expression);
684
+ if (ts.isIdentifier(receiver) && resolver.resolveOrigin(receiver) === "typed-array-param") {
685
+ pushDiag(node, `.${node.name.text} on typed-array parameter '${receiver.text}' has no valid C++ lowering.`, "TS2CPP_TYPED_ARRAY_PARAM_LENGTH", "Pass the length as a separate parameter, or wrap the buffer and size in an explicit struct.");
686
+ }
687
+ }
688
+ // 9. Returning typed arrays is unsafe because storage ownership is not
689
+ // represented in the emitted pointer-like C++ type.
690
+ if (ts.isReturnStatement(node) && node.expression) {
691
+ const returnType = checker.getTypeAtLocation(node.expression);
692
+ if (isTypedArrayType(checker, returnType)) {
693
+ pushDiag(node, "Returning a typed array is not supported because typed arrays lower to pointer-like storage in C++.", "TS2CPP_TYPED_ARRAY_RETURN", "Use an out-parameter plus an explicit length, or return a fixed-shape struct that owns its storage.");
694
+ }
695
+ }
696
+ // 10. String-key computed access is only deterministic on Map/Record.
697
+ if (ts.isElementAccessExpression(node) && node.argumentExpression) {
698
+ const argument = unwrapExpression(node.argumentExpression);
699
+ const argumentType = checker.getTypeAtLocation(argument);
700
+ const isStringKey = ts.isStringLiteralLike(argument) ||
701
+ (argumentType.flags & ts.TypeFlags.StringLike) !== 0;
702
+ const isNumericKey = ts.isNumericLiteral(argument) ||
703
+ (argumentType.flags & ts.TypeFlags.NumberLike) !== 0;
704
+ const objectType = checker.getTypeAtLocation(node.expression);
705
+ if (isStringKey && !isNumericKey &&
706
+ !isMapLikeType(checker, objectType) &&
707
+ !isArrayLikeType(checker, objectType) &&
708
+ !isTypedArrayType(checker, objectType)) {
709
+ pushDiag(node, "Dynamic string-key access on a non-map value is not supported by the C++ lowering.", "TS2CPP_DYNAMIC_OBJECT_KEY", "Use property access for fixed struct fields (obj.field), or use Map<string, T>/Record<string, T> for dynamic keys.");
710
+ }
711
+ }
712
+ // 11. Array parameters are by-value std::vector copies in the current
713
+ // lowering, so content mutation does not affect the caller.
714
+ if (ts.isCallExpression(node)) {
715
+ const callee = unwrapExpression(node.expression);
716
+ if (ts.isPropertyAccessExpression(callee)) {
717
+ const receiver = unwrapExpression(callee.expression);
718
+ const methodName = callee.name.text;
719
+ if (MUTATING_ARRAY_METHOD_NAMES.has(methodName) && ts.isIdentifier(receiver) && resolver.resolveOrigin(receiver) === "array-param") {
720
+ pushDiag(node, `Calling .${methodName}() on array parameter '${receiver.text}' mutates only the C++ parameter copy.`, "TS2CPP_ARRAY_PARAM_MUTATION", "Return the updated array, pass a mutable owner object, or move the mutation to the caller-side container.");
721
+ }
722
+ const receiverType = checker.getTypeAtLocation(callee.expression);
723
+ if (FUNCTIONAL_METHOD_NAMES.has(methodName) && isContainerType(checker, receiverType)) {
724
+ pushDiag(node, `.${methodName}() is not supported on Map/Set/Record in the C++ lowering.`, "TS2CPP_CONTAINER_FUNCTIONAL_METHOD", "Use a manual for...of loop over keys/values/entries, or convert to an array before applying functional array methods.");
725
+ }
726
+ if (scope.inClassMethod && FUNCTIONAL_METHOD_NAMES.has(methodName) && isArrayLikeType(checker, receiverType)) {
727
+ const callback = node.arguments.map(getCallbackFunction).find((arg) => Boolean(arg));
728
+ if (callback && scope.functionNode && isCaptureInsideCallback(callback, scope.functionNode, checker)) {
729
+ pushDiag(callback, "Capturing callbacks inside class-method functional array calls are not supported by the current C++ lowering.", "TS2CPP_CALLBACK_CAPTURE_UNSUPPORTED", "Move the aggregation into a manual loop in the method, or call a module-level helper that does not capture method locals or this.");
730
+ }
731
+ }
732
+ }
733
+ }
734
+ // 12. Typed-array class fields have no safe C++ lowering. A typed array
735
+ // lowers to pointer-like storage (uint8_t*), but a class field needs
736
+ // owned, copyable storage with a valid initializer — a raw pointer
737
+ // member has neither (its `new Uint8Array(N)` initializer lowers to a
738
+ // brace-init-list that cannot initialize a pointer, and the field has
739
+ // no new[]/delete[] lifecycle). Typed arrays are supported only as
740
+ // function-local stack buffers; see also gates 8 (param .length) and
741
+ // 9 (return). This is the same ownership boundary, applied to fields.
742
+ if (ts.isPropertyDeclaration(node) && node.type) {
743
+ const fieldType = checker.getTypeFromTypeNode(node.type);
744
+ if (isTypedArrayType(checker, fieldType)) {
745
+ pushDiag(node.type, "A typed-array class field is not supported because typed arrays lower to pointer-like storage with no owned backing buffer in C++.", "TS2CPP_TYPED_ARRAY_FIELD", "Use a function-local typed array for a stack buffer, or store the buffer in a std::vector field (number[]/int8_t[]) that owns its storage.");
746
+ }
747
+ }
748
+ ts.forEachChild(node, child => visit(child, scope));
749
+ };
750
+ visit(sourceFile, createChildScope());
751
+ }
752
+ return diagnostics;
753
+ }
754
+ function extractLine(sourceText, line1Indexed) {
755
+ if (line1Indexed == null)
756
+ return "";
757
+ const lines = sourceText.split("\n");
758
+ const idx = line1Indexed - 1;
759
+ return idx >= 0 && idx < lines.length ? lines[idx] : "";
760
+ }