fhirpath-ts 0.0.0-bootstrap.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 (414) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +391 -0
  3. package/THIRD-PARTY-NOTICES.md +296 -0
  4. package/dist/analyzer/analyze-dto.d.ts +45 -0
  5. package/dist/analyzer/analyze-dto.d.ts.map +1 -0
  6. package/dist/analyzer/analyze-dto.js +147 -0
  7. package/dist/analyzer/analyze-dto.js.map +1 -0
  8. package/dist/analyzer/analyze.d.ts +98 -0
  9. package/dist/analyzer/analyze.d.ts.map +1 -0
  10. package/dist/analyzer/analyze.js +925 -0
  11. package/dist/analyzer/analyze.js.map +1 -0
  12. package/dist/analyzer/expression-policy.d.ts +210 -0
  13. package/dist/analyzer/expression-policy.d.ts.map +1 -0
  14. package/dist/analyzer/expression-policy.js +254 -0
  15. package/dist/analyzer/expression-policy.js.map +1 -0
  16. package/dist/analyzer/index.d.ts +3 -0
  17. package/dist/analyzer/index.d.ts.map +1 -0
  18. package/dist/analyzer/index.js +6 -0
  19. package/dist/analyzer/index.js.map +1 -0
  20. package/dist/analyzer/regex-safety.d.ts +10 -0
  21. package/dist/analyzer/regex-safety.d.ts.map +1 -0
  22. package/dist/analyzer/regex-safety.js +84 -0
  23. package/dist/analyzer/regex-safety.js.map +1 -0
  24. package/dist/analyzer/signatures.d.ts +72 -0
  25. package/dist/analyzer/signatures.d.ts.map +1 -0
  26. package/dist/analyzer/signatures.js +195 -0
  27. package/dist/analyzer/signatures.js.map +1 -0
  28. package/dist/api/bundle.d.ts +29 -0
  29. package/dist/api/bundle.d.ts.map +1 -0
  30. package/dist/api/bundle.js +108 -0
  31. package/dist/api/bundle.js.map +1 -0
  32. package/dist/api/cache.d.ts +10 -0
  33. package/dist/api/cache.d.ts.map +1 -0
  34. package/dist/api/cache.js +38 -0
  35. package/dist/api/cache.js.map +1 -0
  36. package/dist/api/column-signature.d.ts +53 -0
  37. package/dist/api/column-signature.d.ts.map +1 -0
  38. package/dist/api/column-signature.js +43 -0
  39. package/dist/api/column-signature.js.map +1 -0
  40. package/dist/api/compile.d.ts +120 -0
  41. package/dist/api/compile.d.ts.map +1 -0
  42. package/dist/api/compile.js +167 -0
  43. package/dist/api/compile.js.map +1 -0
  44. package/dist/api/constraints.d.ts +48 -0
  45. package/dist/api/constraints.d.ts.map +1 -0
  46. package/dist/api/constraints.js +52 -0
  47. package/dist/api/constraints.js.map +1 -0
  48. package/dist/api/dto.d.ts +134 -0
  49. package/dist/api/dto.d.ts.map +1 -0
  50. package/dist/api/dto.js +295 -0
  51. package/dist/api/dto.js.map +1 -0
  52. package/dist/api/engine.d.ts +136 -0
  53. package/dist/api/engine.d.ts.map +1 -0
  54. package/dist/api/engine.js +183 -0
  55. package/dist/api/engine.js.map +1 -0
  56. package/dist/api/evaluate.d.ts +7 -0
  57. package/dist/api/evaluate.d.ts.map +1 -0
  58. package/dist/api/evaluate.js +10 -0
  59. package/dist/api/evaluate.js.map +1 -0
  60. package/dist/api/project.d.ts +89 -0
  61. package/dist/api/project.d.ts.map +1 -0
  62. package/dist/api/project.js +150 -0
  63. package/dist/api/project.js.map +1 -0
  64. package/dist/api/tagged.d.ts +14 -0
  65. package/dist/api/tagged.d.ts.map +1 -0
  66. package/dist/api/tagged.js +13 -0
  67. package/dist/api/tagged.js.map +1 -0
  68. package/dist/cli/dto-check.d.ts +25 -0
  69. package/dist/cli/dto-check.d.ts.map +1 -0
  70. package/dist/cli/dto-check.js +77 -0
  71. package/dist/cli/dto-check.js.map +1 -0
  72. package/dist/cli/fhirpath-check.d.ts +3 -0
  73. package/dist/cli/fhirpath-check.d.ts.map +1 -0
  74. package/dist/cli/fhirpath-check.js +149 -0
  75. package/dist/cli/fhirpath-check.js.map +1 -0
  76. package/dist/cli/ts-loader.d.mts +3 -0
  77. package/dist/cli/ts-loader.d.mts.map +1 -0
  78. package/dist/cli/ts-loader.mjs +48 -0
  79. package/dist/cli/ts-loader.mjs.map +1 -0
  80. package/dist/engine/context.d.ts +173 -0
  81. package/dist/engine/context.d.ts.map +1 -0
  82. package/dist/engine/context.js +118 -0
  83. package/dist/engine/context.js.map +1 -0
  84. package/dist/engine/evaluator.d.ts +7 -0
  85. package/dist/engine/evaluator.d.ts.map +1 -0
  86. package/dist/engine/evaluator.js +196 -0
  87. package/dist/engine/evaluator.js.map +1 -0
  88. package/dist/engine/navigation.d.ts +11 -0
  89. package/dist/engine/navigation.d.ts.map +1 -0
  90. package/dist/engine/navigation.js +102 -0
  91. package/dist/engine/navigation.js.map +1 -0
  92. package/dist/engine/operators/collections.d.ts +12 -0
  93. package/dist/engine/operators/collections.d.ts.map +1 -0
  94. package/dist/engine/operators/collections.js +31 -0
  95. package/dist/engine/operators/collections.js.map +1 -0
  96. package/dist/engine/operators/comparison.d.ts +13 -0
  97. package/dist/engine/operators/comparison.d.ts.map +1 -0
  98. package/dist/engine/operators/comparison.js +77 -0
  99. package/dist/engine/operators/comparison.js.map +1 -0
  100. package/dist/engine/operators/equality.d.ts +19 -0
  101. package/dist/engine/operators/equality.d.ts.map +1 -0
  102. package/dist/engine/operators/equality.js +203 -0
  103. package/dist/engine/operators/equality.js.map +1 -0
  104. package/dist/engine/operators/index.d.ts +18 -0
  105. package/dist/engine/operators/index.d.ts.map +1 -0
  106. package/dist/engine/operators/index.js +30 -0
  107. package/dist/engine/operators/index.js.map +1 -0
  108. package/dist/engine/operators/logic.d.ts +8 -0
  109. package/dist/engine/operators/logic.d.ts.map +1 -0
  110. package/dist/engine/operators/logic.js +38 -0
  111. package/dist/engine/operators/logic.js.map +1 -0
  112. package/dist/engine/operators/math.d.ts +13 -0
  113. package/dist/engine/operators/math.d.ts.map +1 -0
  114. package/dist/engine/operators/math.js +198 -0
  115. package/dist/engine/operators/math.js.map +1 -0
  116. package/dist/engine/operators/strings.d.ts +8 -0
  117. package/dist/engine/operators/strings.d.ts.map +1 -0
  118. package/dist/engine/operators/strings.js +22 -0
  119. package/dist/engine/operators/strings.js.map +1 -0
  120. package/dist/engine/operators/types.d.ts +3 -0
  121. package/dist/engine/operators/types.d.ts.map +1 -0
  122. package/dist/engine/operators/types.js +15 -0
  123. package/dist/engine/operators/types.js.map +1 -0
  124. package/dist/engine/type-matching.d.ts +20 -0
  125. package/dist/engine/type-matching.d.ts.map +1 -0
  126. package/dist/engine/type-matching.js +122 -0
  127. package/dist/engine/type-matching.js.map +1 -0
  128. package/dist/errors.d.ts +25 -0
  129. package/dist/errors.d.ts.map +1 -0
  130. package/dist/errors.js +21 -0
  131. package/dist/errors.js.map +1 -0
  132. package/dist/eslint/index.d.ts +12 -0
  133. package/dist/eslint/index.d.ts.map +1 -0
  134. package/dist/eslint/index.js +316 -0
  135. package/dist/eslint/index.js.map +1 -0
  136. package/dist/fhir/html-checks.d.ts +7 -0
  137. package/dist/fhir/html-checks.d.ts.map +1 -0
  138. package/dist/fhir/html-checks.js +256 -0
  139. package/dist/fhir/html-checks.js.map +1 -0
  140. package/dist/fhir/model-navigation.d.ts +11 -0
  141. package/dist/fhir/model-navigation.d.ts.map +1 -0
  142. package/dist/fhir/model-navigation.js +142 -0
  143. package/dist/fhir/model-navigation.js.map +1 -0
  144. package/dist/functions/aggregate.d.ts +2 -0
  145. package/dist/functions/aggregate.d.ts.map +1 -0
  146. package/dist/functions/aggregate.js +127 -0
  147. package/dist/functions/aggregate.js.map +1 -0
  148. package/dist/functions/boundary.d.ts +2 -0
  149. package/dist/functions/boundary.d.ts.map +1 -0
  150. package/dist/functions/boundary.js +189 -0
  151. package/dist/functions/boundary.js.map +1 -0
  152. package/dist/functions/combining.d.ts +2 -0
  153. package/dist/functions/combining.d.ts.map +1 -0
  154. package/dist/functions/combining.js +13 -0
  155. package/dist/functions/combining.js.map +1 -0
  156. package/dist/functions/control.d.ts +2 -0
  157. package/dist/functions/control.d.ts.map +1 -0
  158. package/dist/functions/control.js +25 -0
  159. package/dist/functions/control.js.map +1 -0
  160. package/dist/functions/conversion.d.ts +3 -0
  161. package/dist/functions/conversion.d.ts.map +1 -0
  162. package/dist/functions/conversion.js +316 -0
  163. package/dist/functions/conversion.js.map +1 -0
  164. package/dist/functions/date-components.d.ts +2 -0
  165. package/dist/functions/date-components.d.ts.map +1 -0
  166. package/dist/functions/date-components.js +81 -0
  167. package/dist/functions/date-components.js.map +1 -0
  168. package/dist/functions/existence.d.ts +4 -0
  169. package/dist/functions/existence.d.ts.map +1 -0
  170. package/dist/functions/existence.js +97 -0
  171. package/dist/functions/existence.js.map +1 -0
  172. package/dist/functions/fhir.d.ts +2 -0
  173. package/dist/functions/fhir.d.ts.map +1 -0
  174. package/dist/functions/fhir.js +222 -0
  175. package/dist/functions/fhir.js.map +1 -0
  176. package/dist/functions/filtering.d.ts +2 -0
  177. package/dist/functions/filtering.d.ts.map +1 -0
  178. package/dist/functions/filtering.js +112 -0
  179. package/dist/functions/filtering.js.map +1 -0
  180. package/dist/functions/install.d.ts +18 -0
  181. package/dist/functions/install.d.ts.map +1 -0
  182. package/dist/functions/install.js +20 -0
  183. package/dist/functions/install.js.map +1 -0
  184. package/dist/functions/iteration.d.ts +7 -0
  185. package/dist/functions/iteration.d.ts.map +1 -0
  186. package/dist/functions/iteration.js +9 -0
  187. package/dist/functions/iteration.js.map +1 -0
  188. package/dist/functions/math.d.ts +2 -0
  189. package/dist/functions/math.d.ts.map +1 -0
  190. package/dist/functions/math.js +152 -0
  191. package/dist/functions/math.js.map +1 -0
  192. package/dist/functions/reflection.d.ts +2 -0
  193. package/dist/functions/reflection.d.ts.map +1 -0
  194. package/dist/functions/reflection.js +23 -0
  195. package/dist/functions/reflection.js.map +1 -0
  196. package/dist/functions/registry.d.ts +28 -0
  197. package/dist/functions/registry.d.ts.map +1 -0
  198. package/dist/functions/registry.js +46 -0
  199. package/dist/functions/registry.js.map +1 -0
  200. package/dist/functions/sort.d.ts +2 -0
  201. package/dist/functions/sort.d.ts.map +1 -0
  202. package/dist/functions/sort.js +48 -0
  203. package/dist/functions/sort.js.map +1 -0
  204. package/dist/functions/string.d.ts +2 -0
  205. package/dist/functions/string.d.ts.map +1 -0
  206. package/dist/functions/string.js +353 -0
  207. package/dist/functions/string.js.map +1 -0
  208. package/dist/functions/subsetting.d.ts +2 -0
  209. package/dist/functions/subsetting.d.ts.map +1 -0
  210. package/dist/functions/subsetting.js +72 -0
  211. package/dist/functions/subsetting.js.map +1 -0
  212. package/dist/functions/tree.d.ts +8 -0
  213. package/dist/functions/tree.d.ts.map +1 -0
  214. package/dist/functions/tree.js +109 -0
  215. package/dist/functions/tree.js.map +1 -0
  216. package/dist/functions/type-specifier.d.ts +8 -0
  217. package/dist/functions/type-specifier.d.ts.map +1 -0
  218. package/dist/functions/type-specifier.js +27 -0
  219. package/dist/functions/type-specifier.js.map +1 -0
  220. package/dist/functions/utility.d.ts +2 -0
  221. package/dist/functions/utility.d.ts.map +1 -0
  222. package/dist/functions/utility.js +76 -0
  223. package/dist/functions/utility.js.map +1 -0
  224. package/dist/functions/variables.d.ts +2 -0
  225. package/dist/functions/variables.d.ts.map +1 -0
  226. package/dist/functions/variables.js +35 -0
  227. package/dist/functions/variables.js.map +1 -0
  228. package/dist/index.d.ts +25 -0
  229. package/dist/index.d.ts.map +1 -0
  230. package/dist/index.js +11 -0
  231. package/dist/index.js.map +1 -0
  232. package/dist/lexer/lexer.d.ts +4 -0
  233. package/dist/lexer/lexer.d.ts.map +1 -0
  234. package/dist/lexer/lexer.js +318 -0
  235. package/dist/lexer/lexer.js.map +1 -0
  236. package/dist/lexer/tokens.d.ts +23 -0
  237. package/dist/lexer/tokens.d.ts.map +1 -0
  238. package/dist/lexer/tokens.js +40 -0
  239. package/dist/lexer/tokens.js.map +1 -0
  240. package/dist/model/provider.d.ts +36 -0
  241. package/dist/model/provider.d.ts.map +1 -0
  242. package/dist/model/provider.js +2 -0
  243. package/dist/model/provider.js.map +1 -0
  244. package/dist/parser/ast.d.ts +104 -0
  245. package/dist/parser/ast.d.ts.map +1 -0
  246. package/dist/parser/ast.js +2 -0
  247. package/dist/parser/ast.js.map +1 -0
  248. package/dist/parser/parser.d.ts +4 -0
  249. package/dist/parser/parser.d.ts.map +1 -0
  250. package/dist/parser/parser.js +299 -0
  251. package/dist/parser/parser.js.map +1 -0
  252. package/dist/parser/precedence.d.ts +24 -0
  253. package/dist/parser/precedence.d.ts.map +1 -0
  254. package/dist/parser/precedence.js +52 -0
  255. package/dist/parser/precedence.js.map +1 -0
  256. package/dist/parser/printer.d.ts +4 -0
  257. package/dist/parser/printer.d.ts.map +1 -0
  258. package/dist/parser/printer.js +112 -0
  259. package/dist/parser/printer.js.map +1 -0
  260. package/dist/r4/generated/resources-data.d.ts +3 -0
  261. package/dist/r4/generated/resources-data.d.ts.map +1 -0
  262. package/dist/r4/generated/resources-data.js +28628 -0
  263. package/dist/r4/generated/resources-data.js.map +1 -0
  264. package/dist/r4/generated/type-maps.d.ts +43153 -0
  265. package/dist/r4/generated/type-maps.d.ts.map +1 -0
  266. package/dist/r4/generated/type-maps.js +4 -0
  267. package/dist/r4/generated/type-maps.js.map +1 -0
  268. package/dist/r4/generated/types-data.d.ts +3 -0
  269. package/dist/r4/generated/types-data.d.ts.map +1 -0
  270. package/dist/r4/generated/types-data.js +2205 -0
  271. package/dist/r4/generated/types-data.js.map +1 -0
  272. package/dist/r4/index.d.ts +9 -0
  273. package/dist/r4/index.d.ts.map +1 -0
  274. package/dist/r4/index.js +94 -0
  275. package/dist/r4/index.js.map +1 -0
  276. package/dist/r4/model-data.d.ts +17 -0
  277. package/dist/r4/model-data.d.ts.map +1 -0
  278. package/dist/r4/model-data.js +2 -0
  279. package/dist/r4/model-data.js.map +1 -0
  280. package/dist/sites/index.d.ts +31 -0
  281. package/dist/sites/index.d.ts.map +1 -0
  282. package/dist/sites/index.js +205 -0
  283. package/dist/sites/index.js.map +1 -0
  284. package/dist/typed/infer.d.ts +234 -0
  285. package/dist/typed/infer.d.ts.map +1 -0
  286. package/dist/typed/infer.js +85 -0
  287. package/dist/typed/infer.js.map +1 -0
  288. package/dist/typed/perf-fixture.types.d.ts +136 -0
  289. package/dist/typed/perf-fixture.types.d.ts.map +1 -0
  290. package/dist/typed/perf-fixture.types.js +2 -0
  291. package/dist/typed/perf-fixture.types.js.map +1 -0
  292. package/dist/values/collection.d.ts +24 -0
  293. package/dist/values/collection.d.ts.map +1 -0
  294. package/dist/values/collection.js +51 -0
  295. package/dist/values/collection.js.map +1 -0
  296. package/dist/values/datetime.d.ts +54 -0
  297. package/dist/values/datetime.d.ts.map +1 -0
  298. package/dist/values/datetime.js +226 -0
  299. package/dist/values/datetime.js.map +1 -0
  300. package/dist/values/decimal.d.ts +48 -0
  301. package/dist/values/decimal.d.ts.map +1 -0
  302. package/dist/values/decimal.js +188 -0
  303. package/dist/values/decimal.js.map +1 -0
  304. package/dist/values/numeric.d.ts +24 -0
  305. package/dist/values/numeric.d.ts.map +1 -0
  306. package/dist/values/numeric.js +53 -0
  307. package/dist/values/numeric.js.map +1 -0
  308. package/dist/values/quantity.d.ts +49 -0
  309. package/dist/values/quantity.d.ts.map +1 -0
  310. package/dist/values/quantity.js +297 -0
  311. package/dist/values/quantity.js.map +1 -0
  312. package/dist/values/temporal-arithmetic.d.ts +17 -0
  313. package/dist/values/temporal-arithmetic.d.ts.map +1 -0
  314. package/dist/values/temporal-arithmetic.js +183 -0
  315. package/dist/values/temporal-arithmetic.js.map +1 -0
  316. package/dist/values/temporal-compare.d.ts +9 -0
  317. package/dist/values/temporal-compare.d.ts.map +1 -0
  318. package/dist/values/temporal-compare.js +59 -0
  319. package/dist/values/temporal-compare.js.map +1 -0
  320. package/dist/values/type-compat.d.ts +47 -0
  321. package/dist/values/type-compat.d.ts.map +1 -0
  322. package/dist/values/type-compat.js +100 -0
  323. package/dist/values/type-compat.js.map +1 -0
  324. package/dist/values/typed-value.d.ts +48 -0
  325. package/dist/values/typed-value.d.ts.map +1 -0
  326. package/dist/values/typed-value.js +117 -0
  327. package/dist/values/typed-value.js.map +1 -0
  328. package/dist/values/ucum.d.ts +14 -0
  329. package/dist/values/ucum.d.ts.map +1 -0
  330. package/dist/values/ucum.js +146 -0
  331. package/dist/values/ucum.js.map +1 -0
  332. package/package.json +137 -0
  333. package/src/analyzer/analyze-dto.ts +195 -0
  334. package/src/analyzer/analyze.ts +1181 -0
  335. package/src/analyzer/expression-policy.ts +434 -0
  336. package/src/analyzer/index.ts +23 -0
  337. package/src/analyzer/regex-safety.ts +85 -0
  338. package/src/analyzer/signatures.ts +260 -0
  339. package/src/api/bundle.ts +132 -0
  340. package/src/api/cache.ts +40 -0
  341. package/src/api/column-signature.ts +71 -0
  342. package/src/api/compile.ts +308 -0
  343. package/src/api/constraints.ts +104 -0
  344. package/src/api/dto.ts +445 -0
  345. package/src/api/engine.ts +328 -0
  346. package/src/api/evaluate.ts +11 -0
  347. package/src/api/project.ts +255 -0
  348. package/src/api/tagged.ts +35 -0
  349. package/src/cli/dto-check.ts +94 -0
  350. package/src/cli/fhirpath-check.ts +168 -0
  351. package/src/cli/ts-loader.mjs +49 -0
  352. package/src/engine/context.ts +282 -0
  353. package/src/engine/evaluator.ts +246 -0
  354. package/src/engine/navigation.ts +106 -0
  355. package/src/engine/operators/collections.ts +34 -0
  356. package/src/engine/operators/comparison.ts +82 -0
  357. package/src/engine/operators/equality.ts +232 -0
  358. package/src/engine/operators/index.ts +65 -0
  359. package/src/engine/operators/logic.ts +53 -0
  360. package/src/engine/operators/math.ts +225 -0
  361. package/src/engine/operators/strings.ts +25 -0
  362. package/src/engine/operators/types.ts +16 -0
  363. package/src/engine/type-matching.ts +148 -0
  364. package/src/errors.ts +33 -0
  365. package/src/eslint/index.ts +388 -0
  366. package/src/fhir/html-checks.ts +266 -0
  367. package/src/fhir/model-navigation.ts +148 -0
  368. package/src/functions/aggregate.ts +136 -0
  369. package/src/functions/boundary.ts +209 -0
  370. package/src/functions/combining.ts +15 -0
  371. package/src/functions/control.ts +25 -0
  372. package/src/functions/conversion.ts +349 -0
  373. package/src/functions/date-components.ts +88 -0
  374. package/src/functions/existence.ts +107 -0
  375. package/src/functions/fhir.ts +247 -0
  376. package/src/functions/filtering.ts +120 -0
  377. package/src/functions/install.ts +19 -0
  378. package/src/functions/iteration.ts +21 -0
  379. package/src/functions/math.ts +167 -0
  380. package/src/functions/reflection.ts +24 -0
  381. package/src/functions/registry.ts +72 -0
  382. package/src/functions/sort.ts +68 -0
  383. package/src/functions/string.ts +413 -0
  384. package/src/functions/subsetting.ts +89 -0
  385. package/src/functions/tree.ts +112 -0
  386. package/src/functions/type-specifier.ts +29 -0
  387. package/src/functions/utility.ts +82 -0
  388. package/src/functions/variables.ts +36 -0
  389. package/src/index.ts +30 -0
  390. package/src/lexer/lexer.ts +341 -0
  391. package/src/lexer/tokens.ts +69 -0
  392. package/src/model/provider.ts +36 -0
  393. package/src/parser/ast.ts +164 -0
  394. package/src/parser/parser.ts +321 -0
  395. package/src/parser/precedence.ts +52 -0
  396. package/src/parser/printer.ts +114 -0
  397. package/src/r4/generated/resources-data.ts +28631 -0
  398. package/src/r4/generated/type-maps.ts +22145 -0
  399. package/src/r4/generated/types-data.ts +2208 -0
  400. package/src/r4/index.ts +107 -0
  401. package/src/r4/model-data.ts +17 -0
  402. package/src/sites/index.ts +285 -0
  403. package/src/typed/infer.ts +433 -0
  404. package/src/typed/perf-fixture.types.ts +143 -0
  405. package/src/values/collection.ts +57 -0
  406. package/src/values/datetime.ts +285 -0
  407. package/src/values/decimal.ts +213 -0
  408. package/src/values/numeric.ts +63 -0
  409. package/src/values/quantity.ts +358 -0
  410. package/src/values/temporal-arithmetic.ts +231 -0
  411. package/src/values/temporal-compare.ts +65 -0
  412. package/src/values/type-compat.ts +133 -0
  413. package/src/values/typed-value.ts +151 -0
  414. package/src/values/ucum.ts +181 -0
@@ -0,0 +1,925 @@
1
+ import "../functions/install.js";
2
+ import { BUILTIN_ENV_VARIABLE_NAMES } from "../engine/context.js";
3
+ import { FhirPathSyntaxError } from "../errors.js";
4
+ import { describeArity, functions } from "../functions/registry.js";
5
+ import { parse } from "../parser/parser.js";
6
+ import { unsatisfiedInput, valueKindOfTypeName } from "../values/type-compat.js";
7
+ import { FHIR_PRIMITIVE_TO_SYSTEM, typeLocalName } from "../values/typed-value.js";
8
+ import { hasNestedUnboundedQuantifier } from "./regex-safety.js";
9
+ import { FUNCTION_SIGNATURES, singleAnd, unionStates, withSingle, } from "./signatures.js";
10
+ const UNKNOWN = { types: undefined, single: undefined };
11
+ function emptyScope() {
12
+ return { vars: new Map(), hasDynamic: false };
13
+ }
14
+ function forkScope(scope) {
15
+ return { vars: new Map(scope.vars), hasDynamic: scope.hasDynamic };
16
+ }
17
+ /**
18
+ * Statically check one expression against the model: spec §11's strict-mode rules
19
+ * (singleton misuse, wrong operand and argument types, incomparable equality)
20
+ * plus unknown elements, functions, arities, type names, and variables.
21
+ * See: https://hl7.org/fhirpath/en/index.html#type-safety-and-strict-evaluation
22
+ */
23
+ export function analyzeExpression(expression, options) {
24
+ return analyzeExpressionDetailed(expression, options).diagnostics;
25
+ }
26
+ /**
27
+ * Analyzes one source site with only facts visible in that file. A declared root
28
+ * does not prove which variables the later call provides. DTO sites may also
29
+ * receive variables and functions from base classes, other modules, or
30
+ * `project()`. Such sites omit unknown-variable diagnostics and report an
31
+ * unknown function only when it resembles a local column. A DTO without a known
32
+ * root receives syntax checks only. Use `analyzeDto` when the class and engine
33
+ * are loaded.
34
+ */
35
+ export function analyzeSite(site, options) {
36
+ const declared = { ...site.functions, ...options?.functions };
37
+ const merged = {
38
+ ...options,
39
+ ...(site.inputType !== undefined && { inputType: site.inputType }),
40
+ ...(Object.keys(declared).length > 0 && { functions: declared }),
41
+ };
42
+ const diagnostics = analyzeExpression(site.expression, merged);
43
+ if (site.dto !== true) {
44
+ return site.inputType === undefined
45
+ ? diagnostics
46
+ : diagnostics.filter(diagnostic => diagnostic.code !== 'unknown-variable');
47
+ }
48
+ if (site.inputType === undefined) {
49
+ return diagnostics.filter(diagnostic => diagnostic.code === 'syntax');
50
+ }
51
+ const columns = Object.keys(site.functions ?? {});
52
+ return diagnostics.filter(diagnostic => {
53
+ if (diagnostic.code === 'unknown-variable') {
54
+ return false;
55
+ }
56
+ if (diagnostic.code !== 'unknown-function') {
57
+ return true;
58
+ }
59
+ return diagnostic.name !== undefined && nearestName(diagnostic.name, columns) !== undefined;
60
+ });
61
+ }
62
+ /** `analyzeExpression` plus the element paths the expression touches and its inferred result type. */
63
+ export function analyzeExpressionDetailed(expression, options) {
64
+ let ast;
65
+ try {
66
+ ast = parse(expression);
67
+ }
68
+ catch (error) {
69
+ /* v8 ignore start -- parse only throws syntax errors */
70
+ if (!(error instanceof FhirPathSyntaxError)) {
71
+ throw error;
72
+ }
73
+ /* v8 ignore stop */
74
+ return {
75
+ diagnostics: [{ severity: 'error', code: 'syntax', message: error.message, span: error.span }],
76
+ elementDependencies: [],
77
+ result: { types: undefined, single: undefined },
78
+ };
79
+ }
80
+ const analyzer = new Analyzer(options);
81
+ const state = analyzer.walk(ast, analyzer.rootState(), emptyScope());
82
+ return {
83
+ diagnostics: analyzer.diagnostics,
84
+ elementDependencies: [...analyzer.dependencies],
85
+ result: { types: state.types, single: state.single },
86
+ };
87
+ }
88
+ class Analyzer {
89
+ diagnostics = [];
90
+ dependencies = new Set();
91
+ model;
92
+ inputType;
93
+ frames = [];
94
+ /** Every declaration of each host-supplied name; one entry unless the name is overloaded. */
95
+ customFunctions;
96
+ declaredVariables;
97
+ constructor(options) {
98
+ this.model = options?.model;
99
+ const inputType = options?.inputType;
100
+ this.inputType = inputType === undefined ? undefined : (this.model?.resolveType(inputType) ?? inputType);
101
+ this.customFunctions = new Map(Object.entries(options?.functions ?? {}).map(([name, declared]) => [
102
+ name,
103
+ ('overloads' in declared ? declared.overloads : [declared]).map(resolvedDeclaration),
104
+ ]));
105
+ this.declaredVariables = new Map(Object.entries(options?.variables ?? {}).map(([name, variable]) => [
106
+ name.startsWith('%') ? name.slice(1) : name,
107
+ variable,
108
+ ]));
109
+ }
110
+ rootState() {
111
+ return this.inputType === undefined ? UNKNOWN : { types: [this.inputType], single: true, rawInput: true };
112
+ }
113
+ walk(node, input, scope) {
114
+ switch (node.kind) {
115
+ case 'null':
116
+ // The empty literal `{}` is statically at most one item, so it satisfies
117
+ // singleton operands (`{} + 1`, `{} and true` are spec-legal).
118
+ return { types: [], single: true };
119
+ case 'boolean':
120
+ return { types: ['System.Boolean'], single: true };
121
+ case 'string':
122
+ return { types: ['System.String'], single: true };
123
+ case 'number':
124
+ return {
125
+ types: [node.isLong ? 'System.Long' : node.isDecimal ? 'System.Decimal' : 'System.Integer'],
126
+ single: true,
127
+ };
128
+ case 'date':
129
+ return { types: ['System.Date'], single: true };
130
+ case 'dateTime':
131
+ return { types: ['System.DateTime'], single: true };
132
+ case 'time':
133
+ return { types: ['System.Time'], single: true };
134
+ case 'quantity':
135
+ return { types: ['System.Quantity'], single: true };
136
+ case 'external':
137
+ return this.walkExternal(node, scope);
138
+ case 'special':
139
+ return this.walkSpecial(node.name);
140
+ case 'identifier':
141
+ return this.walkIdentifier(node, input);
142
+ case 'dot':
143
+ // One scope threads the whole chain, so defineVariable() in an earlier
144
+ // link is visible to later links — exactly like the runtime.
145
+ return this.walk(node.right, this.walk(node.left, input, scope), scope);
146
+ case 'indexer': {
147
+ const target = this.walk(node.target, input, scope);
148
+ const index = this.walk(node.index, input, scope);
149
+ this.requireKind(index, 'Numeric', node.index.span, 'the indexer expects a single Integer');
150
+ return withSingle(target, true);
151
+ }
152
+ case 'call':
153
+ return this.walkCall(node, input, scope);
154
+ case 'unary': {
155
+ const operand = this.walk(node.operand, input, scope);
156
+ this.requireKind(operand, 'Numeric', node.operand.span, `unary '${node.operator}' expects a single number`);
157
+ return operand;
158
+ }
159
+ case 'binary':
160
+ return this.walkBinary(node, input, scope);
161
+ case 'typeOp':
162
+ return this.walkTypeOp(node, input, scope);
163
+ /* v8 ignore start -- exhaustive fallback */
164
+ default: {
165
+ const unreachable = node;
166
+ throw new Error(`Unhandled node ${String(unreachable)}`);
167
+ }
168
+ /* v8 ignore stop */
169
+ }
170
+ }
171
+ /**
172
+ * `%name`: defineVariable() bindings and built-in variables resolve with their
173
+ * known state; anything else is an undefined variable (spec §9), the same
174
+ * check the runtime applies. Host-supplied variables must be declared to the
175
+ * analyzer (AnalyzeOptions is the place this will grow).
176
+ */
177
+ walkExternal(node, scope) {
178
+ const defined = scope.vars.get(node.name);
179
+ if (defined !== undefined) {
180
+ return defined;
181
+ }
182
+ const declared = this.declaredVariables.get(node.name);
183
+ if (declared !== undefined) {
184
+ return { types: declared.types?.map(type => this.canonicalize(type)), single: declared.single };
185
+ }
186
+ switch (node.name) {
187
+ case 'context':
188
+ case 'resource':
189
+ case 'rootResource':
190
+ // Contained-resource re-rooting is a later refinement, mirroring the runtime.
191
+ return this.rootState();
192
+ case 'ucum':
193
+ case 'sct':
194
+ case 'loinc':
195
+ return { types: ['System.String'], single: true };
196
+ default:
197
+ break;
198
+ }
199
+ // FHIR-defined families expand to HL7 urls: %`vs-[name]`, %`ext-[name]`.
200
+ if (node.name.startsWith('vs-') || node.name.startsWith('ext-')) {
201
+ return { types: ['System.String'], single: true };
202
+ }
203
+ // A dynamically-named defineVariable() earlier in the chain may have bound
204
+ // this name, so reporting it as undefined could be wrong — stay quiet.
205
+ if (!scope.hasDynamic) {
206
+ this.report('unknown-variable', `Undefined environment variable %${node.name}`, node.span, 'error', node.name);
207
+ }
208
+ return UNKNOWN;
209
+ }
210
+ walkSpecial(name) {
211
+ if (name === 'this') {
212
+ return this.frames.at(-1) ?? this.rootState();
213
+ }
214
+ return name === 'index' ? { types: ['System.Integer'], single: true } : UNKNOWN;
215
+ }
216
+ /**
217
+ * A quantity's components (spec §4: `value` and `unit`), the one System type
218
+ * with navigable elements. The runtime reads them off the quantity's raw
219
+ * `{ value, unit }` shape (a `toQuantity()` result, a quantity literal), so
220
+ * the analyzer must know them too or flag working navigation.
221
+ */
222
+ static SYSTEM_QUANTITY_ELEMENTS = new Map([
223
+ ['value', { types: ['System.Decimal'], isCollection: false, isChoice: false }],
224
+ ['unit', { types: ['System.String'], isCollection: false, isChoice: false }],
225
+ ]);
226
+ walkIdentifier(node, input) {
227
+ // Navigating from a statically empty input yields empty — nothing to check.
228
+ if (input.types !== undefined && input.types.length === 0) {
229
+ return { types: [], single: true };
230
+ }
231
+ if (input.types === undefined) {
232
+ // Even with an unknown input, a root identifier naming a model type anchors
233
+ // the state — this is what checks `Patient.nope` without an inputType option.
234
+ const asType = this.model?.resolveType(node.name);
235
+ if (asType !== undefined) {
236
+ return { types: [asType], single: true };
237
+ }
238
+ return UNKNOWN;
239
+ }
240
+ // Root rule: an identifier naming the (super)type of the context is the context.
241
+ if (this.model) {
242
+ const asType = this.model.resolveType(node.name);
243
+ if (asType !== undefined && input.types.some(type => this.model?.isSubtypeOf(type, asType))) {
244
+ // The runtime matches a type name against the raw input only through the
245
+ // resourceType discriminator (values/typed-value.ts), so a non-resource
246
+ // name never matches there and the whole path navigates to empty.
247
+ if (input.rawInput === true && !this.isResourceType(asType)) {
248
+ this.report('datatype-root', `'${node.name}' is not a resource type, and a type-name root matches only a resource's resourceType, so this always evaluates to empty — navigate from the input with a relative path`, node.span);
249
+ }
250
+ return input;
251
+ }
252
+ }
253
+ const found = [];
254
+ let isCollection = false;
255
+ // Reference targets (Reference.targetProfile) accumulate so a later
256
+ // resolve() yields their union; one unconstrained reference makes the
257
+ // whole set unknown.
258
+ let targets = [];
259
+ for (const type of input.types) {
260
+ const element = type === 'System.Quantity'
261
+ ? Analyzer.SYSTEM_QUANTITY_ELEMENTS.get(node.name)
262
+ : this.model?.getElement(type, node.name);
263
+ if (element) {
264
+ // elementDependencies names model elements; System.Quantity's components
265
+ // are not ones (there is no FHIR `Quantity.value` dependency here).
266
+ if (!type.startsWith('System.')) {
267
+ this.dependencies.add(`${typeLocalName(type)}.${node.name}`);
268
+ }
269
+ isCollection = isCollection || element.isCollection;
270
+ for (const elementType of element.types) {
271
+ found.push(this.canonicalize(elementType));
272
+ }
273
+ if (element.types.some(elementType => typeLocalName(elementType) === 'Reference')) {
274
+ targets = this.mergeTargets(targets, element);
275
+ }
276
+ }
277
+ }
278
+ if (found.length === 0) {
279
+ if (this.model) {
280
+ const hint = this.isChoiceKeyMisuse(input.types, node.name)
281
+ ? '; choice elements use their stem name'
282
+ : didYouMean(node.name, this.elementNames(input.types));
283
+ this.report('unknown-element', `Element '${node.name}' is not defined on ${input.types.join(' | ')}${hint}`, node.span, 'error', node.name);
284
+ }
285
+ return UNKNOWN;
286
+ }
287
+ const state = { types: [...new Set(found)], single: singleAnd(input.single, !isCollection) };
288
+ if (targets !== undefined && targets.length > 0) {
289
+ state.targets = [...new Set(targets)];
290
+ }
291
+ return state;
292
+ }
293
+ /**
294
+ * True when `canonical` is the model's Resource base or derives from it —
295
+ * the types whose instances carry a resourceType discriminator. A model
296
+ * without a Resource base cannot make the distinction, so the check stays
297
+ * permissive there.
298
+ */
299
+ isResourceType(canonical) {
300
+ const base = this.model?.resolveType('Resource');
301
+ return base === undefined || this.model?.isSubtypeOf(canonical, base) === true;
302
+ }
303
+ canonicalize(elementType) {
304
+ if (elementType.startsWith('System.')) {
305
+ return elementType;
306
+ }
307
+ return this.model?.resolveType(elementType) ?? elementType;
308
+ }
309
+ /**
310
+ * A Reference-typed element's targets folded into the accumulated set.
311
+ * One unconstrained reference makes the whole set unknown, and unknown
312
+ * stays unknown; otherwise the result is the canonicalized union.
313
+ */
314
+ mergeTargets(current, element) {
315
+ if (current === undefined || element.referenceTargets === undefined) {
316
+ return undefined;
317
+ }
318
+ return [...current, ...element.referenceTargets.map(target => this.canonicalize(target))];
319
+ }
320
+ /**
321
+ * True for `valueQuantity`-style keys whose stem is a choice element on one of the
322
+ * input types — the same test the evaluator uses (engine/navigation.ts), so the
323
+ * static diagnostic and the runtime error carry the same guidance.
324
+ */
325
+ isChoiceKeyMisuse(types, name) {
326
+ if (!this.model) {
327
+ return false;
328
+ }
329
+ for (const type of types) {
330
+ for (let position = 1; position < name.length; position++) {
331
+ if (!/[A-Z]/.test(name[position])) {
332
+ continue;
333
+ }
334
+ const stem = name.slice(0, position);
335
+ if (this.model.getElement(type, stem)?.isChoice === true) {
336
+ return true;
337
+ }
338
+ }
339
+ }
340
+ return false;
341
+ }
342
+ /** Every element name the input types can offer, for typo suggestions. */
343
+ elementNames(types) {
344
+ if (this.model?.listElements === undefined) {
345
+ return [];
346
+ }
347
+ const names = [];
348
+ for (const type of types) {
349
+ const elements = this.model.listElements(type);
350
+ if (elements !== undefined) {
351
+ names.push(...elements);
352
+ }
353
+ }
354
+ return names;
355
+ }
356
+ /**
357
+ * A call: resolve the function (built-ins first — EvaluateOptions.functions
358
+ * cannot override them either), then hand each concern to its own step:
359
+ * arity, input shape, arguments, per-function effects, result.
360
+ */
361
+ walkCall(node, input, scope) {
362
+ const registered = functions.get(node.name);
363
+ const custom = registered === undefined ? this.declarationFor(node.name, input) : undefined;
364
+ const resolved = registered ?? custom;
365
+ if (resolved === undefined) {
366
+ this.report('unknown-function', `Unrecognized function '${node.name}'${didYouMean(node.name, [...functions.keys(), ...this.customFunctions.keys()])}`, node.span, 'error', node.name);
367
+ this.walkUncheckedArguments(node, input, scope);
368
+ return UNKNOWN;
369
+ }
370
+ this.checkArity(node, resolved);
371
+ const signature = registered !== undefined ? FUNCTION_SIGNATURES[node.name] : this.toSignature(custom?.signature);
372
+ if (!signature) {
373
+ this.walkUncheckedArguments(node, input, scope);
374
+ return UNKNOWN;
375
+ }
376
+ this.checkCallInput(node, signature, input);
377
+ const { argStates, typeTarget } = this.walkArguments(node, signature, input, scope);
378
+ if (node.name === 'defineVariable') {
379
+ this.registerVariable(node, input, argStates, scope);
380
+ }
381
+ this.checkRegexPattern(node);
382
+ // ofType(X) filters and as(X) casts: both narrow to the named type,
383
+ // intersected with the known candidates.
384
+ if ((node.name === 'ofType' || node.name === 'as') && typeTarget !== undefined) {
385
+ return { types: this.narrowTypes(input, typeTarget, node.span), single: input.single };
386
+ }
387
+ return signature.result(input, argStates);
388
+ }
389
+ /**
390
+ * Selects a host declaration from the focus. One match keeps its full
391
+ * signature. Several matches are merged conservatively. No match merges their
392
+ * input types so `checkCallInput` can report one error.
393
+ */
394
+ declarationFor(name, input) {
395
+ const candidates = this.customFunctions.get(name);
396
+ if (candidates === undefined || candidates.length <= 1) {
397
+ return candidates?.[0];
398
+ }
399
+ const focus = input.types ?? [];
400
+ const fitting = candidates.filter(candidate => unsatisfiedInput(this.model, candidate.signature?.input?.types, focus) === undefined);
401
+ return fitting.length === 1 ? fitting[0] : mergedDeclaration(fitting.length === 0 ? candidates : fitting);
402
+ }
403
+ /** Without a signature the arguments still walk (for their own diagnostics), each in a scope fork. */
404
+ walkUncheckedArguments(node, input, scope) {
405
+ for (const argument of node.args) {
406
+ this.walk(argument, input, forkScope(scope));
407
+ }
408
+ }
409
+ checkArity(node, arity) {
410
+ const minArity = arity.minArity ?? 0;
411
+ const maxArity = arity.maxArity ?? Number.POSITIVE_INFINITY;
412
+ if (node.args.length < minArity || node.args.length > maxArity) {
413
+ this.report('wrong-arity', `Function '${node.name}' expects ${describeArity(minArity, maxArity)}, got ${node.args.length}`, node.span);
414
+ }
415
+ }
416
+ /** The signature's input constraints: cardinality, value kind, and declared types. */
417
+ checkCallInput(node, signature, input) {
418
+ if (!signature.input) {
419
+ return;
420
+ }
421
+ // A function written for one type (a DTO's `@column`), called on a focus
422
+ // that can never be that type. `unsatisfiedInput` holds the same rule the
423
+ // engine applies, so the two halves agree on what counts as a mistake.
424
+ const unsatisfied = input.types === undefined ? undefined : unsatisfiedInput(this.model, signature.input.types, input.types);
425
+ if (unsatisfied !== undefined) {
426
+ this.report('input-type', `${node.name}() expects ${unsatisfied.wanted.join(' | ')} as input, found ${unsatisfied.found.join(' | ')}`, node.span);
427
+ }
428
+ if (signature.input.singleton && input.types !== undefined && input.single === false) {
429
+ this.report('singleton-required', `${node.name}() expects a single item as input, but this is a collection (spec §11)${NARROW_HINT}`, node.span);
430
+ }
431
+ if (signature.input.kind) {
432
+ this.requireKind({ types: input.types, single: true }, signature.input.kind, node.span, `${node.name}() expects a ${signature.input.kind} input`);
433
+ }
434
+ }
435
+ /**
436
+ * Walk every argument per its spec — lambdas against a $this frame,
437
+ * type names against the model, values against $this — collecting the
438
+ * analyzed state per position (undefined for type-name positions). Each
439
+ * argument gets its own scope copy, like the runtime's per-argument fork.
440
+ */
441
+ walkArguments(node, signature, input, scope) {
442
+ const argStates = [];
443
+ let typeTarget;
444
+ node.args.forEach((argument, index) => {
445
+ const spec = signature.args?.[index] ?? signature.args?.at(-1);
446
+ if (spec === 'expression' || spec === 'condition' || spec === 'sort-key') {
447
+ // A top-level unary '-' on a sort key marks descending order (any type),
448
+ // mirroring how sort() reads the AST; only the key itself is analyzed.
449
+ const body = spec === 'sort-key' && argument.kind === 'unary' && argument.operator === '-' ? argument.operand : argument;
450
+ // $this is one item of the input — same candidates and reference targets.
451
+ const itemState = withSingle(input, true);
452
+ this.frames.push(itemState);
453
+ const state = this.walk(body, itemState, forkScope(scope));
454
+ this.frames.pop();
455
+ argStates.push(state);
456
+ if (spec === 'condition') {
457
+ this.requireSingle(state, argument.span, `${node.name}() expects a single Boolean criterion`);
458
+ if (!isCollection(state)) {
459
+ this.requireKind(state, 'Boolean', argument.span, `${node.name}() expects a Boolean criterion`);
460
+ }
461
+ }
462
+ return;
463
+ }
464
+ if (spec === 'type-name') {
465
+ argStates.push(undefined);
466
+ const resolved = this.checkTypeArgument(node.name, argument);
467
+ if (index === 0) {
468
+ typeTarget = resolved;
469
+ }
470
+ return;
471
+ }
472
+ // Value arguments evaluate against $this, mirroring the runtime.
473
+ const argState = this.walk(argument, this.frames.at(-1) ?? this.rootState(), forkScope(scope));
474
+ argStates.push(argState);
475
+ if (spec !== undefined && spec !== 'any') {
476
+ if (argState.types !== undefined && argState.single === false) {
477
+ this.report('argument-singleton', `${node.name}() expects a single ${spec} argument, but this is a collection (spec §11)${NARROW_HINT}`, argument.span, 'warning');
478
+ }
479
+ else {
480
+ this.requireKind(argState, spec, argument.span, `${node.name}() expects a ${spec} argument`);
481
+ }
482
+ }
483
+ });
484
+ return { argStates, typeTarget };
485
+ }
486
+ /**
487
+ * The matches() family compiles its pattern with the backtracking JS RegExp,
488
+ * which cannot be timed out — flag exponential-shaped literal patterns.
489
+ */
490
+ checkRegexPattern(node) {
491
+ if (!REGEX_PATTERN_FUNCTIONS.has(node.name)) {
492
+ return;
493
+ }
494
+ const pattern = node.args[0];
495
+ if (pattern?.kind === 'string' && hasNestedUnboundedQuantifier(pattern.value)) {
496
+ this.report('regex-backtracking', `The regular expression nests unbounded repetition, which can backtrack catastrophically on non-matching input (ReDoS); rewrite it or supply a linear-time engine via EvaluateOptions.regex`, pattern.span, 'warning');
497
+ }
498
+ }
499
+ /**
500
+ * Track a defineVariable() binding in the chain's scope, with the same two
501
+ * rules the runtime enforces: no overriding environment variables, no
502
+ * redefining a name already in scope. Dynamic names cannot be tracked.
503
+ */
504
+ registerVariable(node, input, argStates, scope) {
505
+ const nameNode = node.args[0];
506
+ if (nameNode === undefined || nameNode.kind !== 'string') {
507
+ scope.hasDynamic = true;
508
+ return;
509
+ }
510
+ const name = nameNode.value;
511
+ if (BUILTIN_ENV_VARIABLE_NAMES.has(name) || this.declaredVariables.has(name)) {
512
+ this.report('variable-override', `Cannot override the environment variable %${name}`, nameNode.span);
513
+ return;
514
+ }
515
+ if (scope.vars.has(name)) {
516
+ this.report('variable-redefined', `Variable %${name} is already defined in this scope`, nameNode.span);
517
+ return;
518
+ }
519
+ // Without a value expression the variable holds the function's input.
520
+ scope.vars.set(name, argStates[1] ?? input);
521
+ }
522
+ /**
523
+ * Converts a host function's declared signature into the analyzer's internal
524
+ * shape. Result type names canonicalize once here, and a missing result stays
525
+ * unknown. The input passes through as declared, because `unsatisfiedInput`
526
+ * canonicalizes the names it compares, so a local name such as
527
+ * 'CodeableConcept' works without a second pass.
528
+ */
529
+ toSignature(declared) {
530
+ if (declared === undefined) {
531
+ return undefined;
532
+ }
533
+ const types = declared.result?.types?.map(type => this.canonicalize(type));
534
+ const single = declared.result?.single;
535
+ return {
536
+ ...(declared.input !== undefined && { input: declared.input }),
537
+ ...(declared.args !== undefined && { args: declared.args }),
538
+ result: () => ({ types, single }),
539
+ };
540
+ }
541
+ /**
542
+ * The candidate types that survive narrowing to `target`: subtypes of the
543
+ * target pass through unchanged, supertypes narrow to the target itself.
544
+ * Warns when no candidate can ever match — the result is provably empty.
545
+ */
546
+ narrowTypes(input, target, span) {
547
+ if (input.types === undefined) {
548
+ return [target];
549
+ }
550
+ const survivors = new Set();
551
+ for (const type of input.types) {
552
+ if (this.isTypeCompatible(type, target)) {
553
+ survivors.add(type);
554
+ }
555
+ else if (this.isTypeCompatible(target, type)) {
556
+ survivors.add(target);
557
+ }
558
+ }
559
+ if (survivors.size === 0 && input.types.length > 0) {
560
+ this.report('always-empty', `No candidate type (${input.types.join(' | ')}) can be a ${target}, so this is always empty`, span, 'warning');
561
+ }
562
+ return [...survivors];
563
+ }
564
+ /** True when every `type` value is a `base` value, including FHIR-primitive → System subtyping. */
565
+ isTypeCompatible(type, base) {
566
+ if (type === base || this.model?.isSubtypeOf(type, base) === true) {
567
+ return true;
568
+ }
569
+ return base.startsWith('System.') && FHIR_PRIMITIVE_TO_SYSTEM[typeLocalName(type)] === base;
570
+ }
571
+ /** Check a type-name argument; returns its canonical name, or undefined when unknown. */
572
+ checkTypeArgument(functionName, argument) {
573
+ const parts = typeSpecifierParts(argument);
574
+ if (parts === undefined) {
575
+ this.report('unknown-type', `${functionName}() expects a type name argument`, argument.span);
576
+ return undefined;
577
+ }
578
+ return this.checkTypeName(parts, argument.span);
579
+ }
580
+ /** Check a type name; returns its canonical form, or undefined when it was reported unknown. */
581
+ checkTypeName(parts, span) {
582
+ if (parts.length === 2 && parts[0] === 'System') {
583
+ if (!SYSTEM_TYPE_NAMES.has(parts[1])) {
584
+ this.report('unknown-type', `Unknown type 'System.${parts[1]}'`, span);
585
+ return undefined;
586
+ }
587
+ return `System.${parts[1]}`;
588
+ }
589
+ const name = parts.length === 2 ? parts[1] : parts[0];
590
+ if (parts.length === 2 && this.model && parts[0] !== this.model.namespace) {
591
+ this.report('unknown-type', `Unknown namespace '${parts[0]}'`, span);
592
+ return undefined;
593
+ }
594
+ const resolved = this.model?.resolveType(name);
595
+ if (this.model && resolved === undefined && !SYSTEM_TYPE_NAMES.has(name)) {
596
+ this.report('unknown-type', `Unknown type '${parts.join('.')}'`, span);
597
+ return undefined;
598
+ }
599
+ return resolved ?? (SYSTEM_TYPE_NAMES.has(name) ? `System.${name}` : name);
600
+ }
601
+ walkBinary(node, input, scope) {
602
+ // Operator operands are separate chains: each analyzes against its own scope
603
+ // copy, so defineVariable() in one side is invisible to the other, exactly
604
+ // like the runtime.
605
+ const left = this.walk(node.left, input, forkScope(scope));
606
+ const right = this.walk(node.right, input, forkScope(scope));
607
+ switch (node.operator) {
608
+ case '+':
609
+ case '-':
610
+ case '*':
611
+ case '/':
612
+ case 'div':
613
+ case 'mod': {
614
+ this.checkArithmetic(node.operator, left, right, node.span);
615
+ // Quantity arithmetic yields Quantity (4.0 'g' / 2.0 'm' is 2 'g/m');
616
+ // plain division yields Decimal; everything else keeps the operand type.
617
+ const quantity = (node.operator === '*' || node.operator === '/') &&
618
+ (kindOf(left) === 'Quantity' || kindOf(right) === 'Quantity');
619
+ const types = quantity
620
+ ? ['System.Quantity']
621
+ : node.operator === '/'
622
+ ? ['System.Decimal']
623
+ : (left.types ?? right.types);
624
+ return { types, single: true };
625
+ }
626
+ case '&':
627
+ this.requireSingle(left, node.left.span, "'&' expects single-item operands");
628
+ this.requireSingle(right, node.right.span, "'&' expects single-item operands");
629
+ this.requireKind(left, 'String', node.left.span, "'&' expects String operands");
630
+ this.requireKind(right, 'String', node.right.span, "'&' expects String operands");
631
+ return { types: ['System.String'], single: true };
632
+ case '<':
633
+ case '>':
634
+ case '<=':
635
+ case '>=':
636
+ this.requireSingle(left, node.left.span, `Operator '${node.operator}' expects single-item operands`);
637
+ this.requireSingle(right, node.right.span, `Operator '${node.operator}' expects single-item operands`);
638
+ this.checkComparable(left, right, node.span);
639
+ return { types: ['System.Boolean'], single: true };
640
+ case '=':
641
+ case '!=':
642
+ case '~':
643
+ case '!~':
644
+ this.checkEquality(left, right, node.span);
645
+ return { types: ['System.Boolean'], single: true };
646
+ case 'and':
647
+ case 'or':
648
+ case 'xor':
649
+ case 'implies': {
650
+ // Any single item satisfies a Boolean operand through the implicit-exists
651
+ // rule, so only cardinality is checkable here.
652
+ this.requireSingle(left, node.left.span, `'${node.operator}' expects single-item operands`);
653
+ this.requireSingle(right, node.right.span, `'${node.operator}' expects single-item operands`);
654
+ return { types: ['System.Boolean'], single: true };
655
+ }
656
+ case '|': {
657
+ // A statically empty side contributes nothing: `{} | true` is one item.
658
+ if (left.types?.length === 0) {
659
+ return right;
660
+ }
661
+ if (right.types?.length === 0) {
662
+ return left;
663
+ }
664
+ return withSingle(unionStates([left, right]), false);
665
+ }
666
+ case 'in':
667
+ case 'contains': {
668
+ const singletonSide = node.operator === 'in' ? left : right;
669
+ const singletonSpan = node.operator === 'in' ? node.left.span : node.right.span;
670
+ this.requireSingle(singletonSide, singletonSpan, `The ${node.operator === 'in' ? 'left' : 'right'} operand of '${node.operator}' must be a single item`);
671
+ return { types: ['System.Boolean'], single: true };
672
+ }
673
+ /* v8 ignore start -- the parser produces no other binary operators */
674
+ default:
675
+ return UNKNOWN;
676
+ /* v8 ignore stop */
677
+ }
678
+ }
679
+ /** Report a singleton violation when the state is statically known to be a collection. */
680
+ requireSingle(state, span, message) {
681
+ if (isCollection(state)) {
682
+ this.report('singleton-required', `${message}${NARROW_HINT}`, span);
683
+ }
684
+ }
685
+ walkTypeOp(node, input, scope) {
686
+ const operand = this.walk(node.operand, input, scope);
687
+ this.requireSingle(operand, node.operand.span, `'${node.operator}' expects a single item operand`);
688
+ const resolved = this.checkTypeName(node.type.parts, node.type.span);
689
+ if (node.operator === 'is') {
690
+ return { types: ['System.Boolean'], single: true };
691
+ }
692
+ if (resolved === undefined) {
693
+ return UNKNOWN;
694
+ }
695
+ return { types: this.narrowTypes(operand, resolved, node.span), single: true };
696
+ }
697
+ checkArithmetic(operator, left, right, span) {
698
+ if (isCollection(left) || isCollection(right)) {
699
+ this.report('singleton-required', `Operator '${operator}' expects single-item operands${NARROW_HINT}`, span);
700
+ return;
701
+ }
702
+ const leftKind = kindOf(left);
703
+ const rightKind = kindOf(right);
704
+ if (leftKind === undefined || rightKind === undefined || leftKind === 'Complex' || rightKind === 'Complex') {
705
+ return;
706
+ }
707
+ const scalars = ['Numeric', 'Quantity'];
708
+ const valid = (operator === '+' && leftKind === 'String' && rightKind === 'String') ||
709
+ ((operator === '+' || operator === '-') &&
710
+ ((leftKind === 'Numeric' && rightKind === 'Numeric') ||
711
+ (leftKind === 'Quantity' && rightKind === 'Quantity') ||
712
+ (leftKind === 'Temporal' && rightKind === 'Quantity'))) ||
713
+ ((operator === '*' || operator === '/') && scalars.includes(leftKind) && scalars.includes(rightKind)) ||
714
+ ((operator === 'div' || operator === 'mod') && leftKind === 'Numeric' && rightKind === 'Numeric');
715
+ if (!valid) {
716
+ this.report('operand-type', `Operator '${operator}' is not defined for these operand types`, span);
717
+ }
718
+ }
719
+ checkComparable(left, right, span) {
720
+ const leftKind = kindOf(left);
721
+ const rightKind = kindOf(right);
722
+ if (leftKind === undefined || rightKind === undefined) {
723
+ return;
724
+ }
725
+ if (leftKind === 'Boolean' || rightKind === 'Boolean' || leftKind !== rightKind) {
726
+ this.report('operand-type', 'Comparison operands must be single values of comparable types', span);
727
+ }
728
+ }
729
+ checkEquality(left, right, span) {
730
+ const leftKind = kindOf(left);
731
+ const rightKind = kindOf(right);
732
+ if (leftKind === undefined || rightKind === undefined || leftKind === 'Complex' || rightKind === 'Complex') {
733
+ return;
734
+ }
735
+ if (leftKind !== rightKind) {
736
+ this.report('equality-incompatible', `${leftKind} and ${rightKind} operands can never be equal (spec §11)`, span);
737
+ }
738
+ }
739
+ requireKind(state, kind, span, message) {
740
+ const actual = kindOf(state);
741
+ if (actual === undefined) {
742
+ return;
743
+ }
744
+ const compatible = actual === kind || (kind === 'Numeric' && actual === 'Quantity');
745
+ if (!compatible) {
746
+ this.report('operand-type', `${message}, found ${state.types?.join(' | ') ?? 'unknown'}`, span);
747
+ }
748
+ }
749
+ report(code, message, span, severity = 'error', name) {
750
+ this.diagnostics.push({ severity, code, message, span, ...(name !== undefined && { name }) });
751
+ }
752
+ }
753
+ /** Functions whose first argument is a regular expression pattern. */
754
+ const REGEX_PATTERN_FUNCTIONS = new Set(['matches', 'matchesFull', 'replaceMatches']);
755
+ const SYSTEM_TYPE_NAMES = new Set([
756
+ 'Any',
757
+ 'Boolean',
758
+ 'String',
759
+ 'Integer',
760
+ 'Long',
761
+ 'Decimal',
762
+ 'Date',
763
+ 'DateTime',
764
+ 'Time',
765
+ 'Quantity',
766
+ ]);
767
+ /** Appended to singleton-misuse messages so the fix is spelled out, not just the rule. */
768
+ const NARROW_HINT = ' — narrow it to one item with first(), last(), or single()';
769
+ /** Statically known to possibly hold more than one item (types known, not a singleton). */
770
+ function isCollection(state) {
771
+ return state.types !== undefined && state.single === false;
772
+ }
773
+ function resolvedDeclaration(declared) {
774
+ if (declared.expression === undefined) {
775
+ return declared;
776
+ }
777
+ // An expression-defined function takes no arguments, which is how the runtime
778
+ // calls it (`evaluateHostFunction`).
779
+ return { minArity: 0, maxArity: 0, ...(declared.signature !== undefined && { signature: declared.signature }) };
780
+ }
781
+ /**
782
+ * What several declarations of one name say together, for a call none of them
783
+ * answers alone. Everything here widens: the arities span all of them, the
784
+ * input is every type any of them accepts, and the result is the union of
785
+ * theirs — unknown as soon as one declaration leaves it unknown. Arguments are
786
+ * dropped, since checking them against the wrong declaration would report valid
787
+ * code.
788
+ */
789
+ function mergedDeclaration(candidates) {
790
+ const maxArity = Math.max(...candidates.map(candidate => candidate.maxArity ?? Number.POSITIVE_INFINITY));
791
+ const input = mergedInput(candidates);
792
+ const result = mergedResult(candidates);
793
+ return {
794
+ minArity: Math.min(...candidates.map(candidate => candidate.minArity ?? 0)),
795
+ ...(Number.isFinite(maxArity) && { maxArity }),
796
+ ...((input !== undefined || result !== undefined) && {
797
+ signature: { ...(input !== undefined && { input }), ...(result !== undefined && { result }) },
798
+ }),
799
+ };
800
+ }
801
+ /** Every type any declaration accepts, or undefined when one of them accepts anything. */
802
+ function mergedInput(candidates) {
803
+ const declared = candidates.map(candidate => candidate.signature?.input?.types);
804
+ if (declared.some(types => types === undefined)) {
805
+ return undefined;
806
+ }
807
+ return { types: [...new Set(declared.flatMap(types => types ?? []))] };
808
+ }
809
+ /** The union of the declarations' results, unknown as soon as one of them is. */
810
+ function mergedResult(candidates) {
811
+ const union = unionStates(candidates.map(candidate => ({
812
+ types: candidate.signature?.result?.types,
813
+ single: candidate.signature?.result?.single,
814
+ })));
815
+ if (union.types === undefined && union.single === undefined) {
816
+ return undefined;
817
+ }
818
+ return {
819
+ ...(union.types !== undefined && { types: union.types }),
820
+ ...(union.single !== undefined && { single: union.single }),
821
+ };
822
+ }
823
+ /** The behavior kind shared by every candidate type, or undefined when mixed/unknown. */
824
+ function kindOf(state) {
825
+ if (state.types === undefined || state.types.length === 0) {
826
+ return undefined;
827
+ }
828
+ let kind;
829
+ for (const type of state.types) {
830
+ const typeKind = valueKindOfTypeName(type);
831
+ if (kind === undefined) {
832
+ kind = typeKind;
833
+ }
834
+ else if (kind !== typeKind) {
835
+ return undefined;
836
+ }
837
+ }
838
+ return kind;
839
+ }
840
+ function typeSpecifierParts(node) {
841
+ if (node.kind === 'identifier') {
842
+ return [node.name];
843
+ }
844
+ if (node.kind === 'dot' && node.left.kind === 'identifier' && node.right.kind === 'identifier') {
845
+ return [node.left.name, node.right.name];
846
+ }
847
+ return undefined;
848
+ }
849
+ /**
850
+ * `— did you mean 'X'?` for a mistyped name, or `''` when nothing is close enough.
851
+ */
852
+ function didYouMean(target, candidates) {
853
+ const nearest = nearestName(target, candidates);
854
+ return nearest === undefined ? '' : ` — did you mean '${nearest}'?`;
855
+ }
856
+ /**
857
+ * The candidate `target` most plausibly misspells, or undefined when none is
858
+ * close enough: a small edit budget scaled to the name's length, so a genuine
859
+ * typo (`gven` → `given`, `lengthx` → `length`) matches while an unrelated name
860
+ * (`nope`) does not. Both the `did you mean` suggestions and `analyzeSite`
861
+ * weighing an unresolved column name go through here, so one budget decides what
862
+ * counts as a plausible misspelling.
863
+ */
864
+ function nearestName(target, candidates) {
865
+ // Budget grows with the name: 1 edit for short names (<=4), up to 3 for long ones.
866
+ // Keeps suggestions high-precision — a real typo, not any name that happens to be near.
867
+ const budget = Math.min(3, Math.ceil(target.length / 4));
868
+ let best;
869
+ let bestDistance = budget + 1;
870
+ for (const candidate of candidates) {
871
+ if (candidate === target) {
872
+ continue;
873
+ }
874
+ // Only a strictly better distance matters, so the cap tightens as matches are found.
875
+ const limit = bestDistance - 1;
876
+ const distance = boundedEditDistance(target, candidate, limit);
877
+ if (distance <= limit) {
878
+ bestDistance = distance;
879
+ best = candidate;
880
+ if (bestDistance === 1) {
881
+ break; // Unbeatable: distance 0 would mean an exact match, which is skipped.
882
+ }
883
+ }
884
+ }
885
+ return best;
886
+ }
887
+ /**
888
+ * Levenshtein distance capped at `limit`: exact when the distance is <= limit,
889
+ * otherwise returns limit + 1. Only the |i - j| <= limit diagonal band of each
890
+ * DP row is computed (cells outside it can never end <= limit), and the scan
891
+ * bails as soon as a whole row exceeds the cap — so a non-match costs
892
+ * O(limit^2) instead of a full O(len(a) * len(b)) table.
893
+ */
894
+ function boundedEditDistance(a, b, limit) {
895
+ if (Math.abs(a.length - b.length) > limit) {
896
+ return limit + 1;
897
+ }
898
+ const previous = new Array(b.length + 1);
899
+ for (let j = 0; j <= b.length; j++) {
900
+ previous[j] = j;
901
+ }
902
+ for (let i = 1; i <= a.length; i++) {
903
+ const from = Math.max(1, i - limit);
904
+ const to = Math.min(b.length, i + limit);
905
+ let diagonal = previous[from - 1];
906
+ previous[from - 1] = from === 1 ? i : limit + 1;
907
+ let rowBest = previous[from - 1];
908
+ for (let j = from; j <= to; j++) {
909
+ // The cell above sits outside the previous row's band when j = i + limit.
910
+ const above = j > i - 1 + limit ? limit + 1 : previous[j];
911
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
912
+ const value = Math.min(above + 1, previous[j - 1] + 1, diagonal + cost);
913
+ previous[j] = value;
914
+ diagonal = above;
915
+ if (value < rowBest) {
916
+ rowBest = value;
917
+ }
918
+ }
919
+ if (rowBest > limit) {
920
+ return limit + 1;
921
+ }
922
+ }
923
+ return Math.min(previous[b.length], limit + 1);
924
+ }
925
+ //# sourceMappingURL=analyze.js.map