@sapui5/sap.fe.core 1.96.4 → 1.99.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 (253) hide show
  1. package/package.json +6 -5
  2. package/src/sap/fe/core/.library +1 -1
  3. package/src/sap/fe/core/AnnotationHelper.js +377 -405
  4. package/src/sap/fe/core/AnnotationHelper.ts +385 -0
  5. package/src/sap/fe/core/AppComponent.js +5 -3
  6. package/src/sap/fe/core/AppStateHandler.js +229 -195
  7. package/src/sap/fe/core/AppStateHandler.ts +171 -0
  8. package/src/sap/fe/core/BaseController.js +3 -2
  9. package/src/sap/fe/core/BusyLocker.js +105 -121
  10. package/src/sap/fe/core/BusyLocker.ts +98 -0
  11. package/src/sap/fe/core/CommonUtils.js +2073 -2606
  12. package/src/sap/fe/core/CommonUtils.ts +2078 -0
  13. package/src/sap/fe/core/ExtensionAPI.js +16 -7
  14. package/src/sap/fe/core/PageController.js +84 -119
  15. package/src/sap/fe/core/PageController.ts +101 -0
  16. package/src/sap/fe/core/RouterProxy.js +986 -809
  17. package/src/sap/fe/core/RouterProxy.ts +838 -0
  18. package/src/sap/fe/core/Synchronization.js +51 -35
  19. package/src/sap/fe/core/Synchronization.ts +29 -0
  20. package/src/sap/fe/core/TemplateComponent.js +173 -155
  21. package/src/sap/fe/core/TemplateComponent.ts +166 -0
  22. package/src/sap/fe/core/TemplateModel.js +97 -54
  23. package/src/sap/fe/core/TemplateModel.ts +63 -0
  24. package/src/sap/fe/core/TransactionHelper.js +1576 -1627
  25. package/src/sap/fe/core/TransactionHelper.ts +1706 -0
  26. package/src/sap/fe/core/actions/draft.js +559 -581
  27. package/src/sap/fe/core/actions/draft.ts +593 -0
  28. package/src/sap/fe/core/actions/messageHandling.js +545 -435
  29. package/src/sap/fe/core/actions/messageHandling.ts +532 -0
  30. package/src/sap/fe/core/actions/nonDraft.js +17 -19
  31. package/src/sap/fe/core/actions/nonDraft.ts +12 -0
  32. package/src/sap/fe/core/actions/operations.js +1074 -1192
  33. package/src/sap/fe/core/actions/operations.ts +1162 -0
  34. package/src/sap/fe/core/actions/sticky.js +102 -105
  35. package/src/sap/fe/core/actions/sticky.ts +102 -0
  36. package/src/sap/fe/core/controllerextensions/ControllerExtensionMetadata.js +3 -2
  37. package/src/sap/fe/core/controllerextensions/EditFlow.js +235 -264
  38. package/src/sap/fe/core/controllerextensions/IntentBasedNavigation.js +3 -2
  39. package/src/sap/fe/core/controllerextensions/InternalEditFlow.js +288 -13
  40. package/src/sap/fe/core/controllerextensions/InternalIntentBasedNavigation.js +35 -25
  41. package/src/sap/fe/core/controllerextensions/InternalRouting.js +79 -46
  42. package/src/sap/fe/core/controllerextensions/KPIManagement.js +86 -10
  43. package/src/sap/fe/core/controllerextensions/KPIManagement.ts +109 -31
  44. package/src/sap/fe/core/controllerextensions/MassEdit.js +172 -0
  45. package/src/sap/fe/core/controllerextensions/MessageHandler.js +237 -104
  46. package/src/sap/fe/core/controllerextensions/PageReady.js +3 -3
  47. package/src/sap/fe/core/controllerextensions/PageReady.ts +12 -8
  48. package/src/sap/fe/core/controllerextensions/Paginator.js +37 -9
  49. package/src/sap/fe/core/controllerextensions/Placeholder.js +8 -4
  50. package/src/sap/fe/core/controllerextensions/Routing.js +25 -5
  51. package/src/sap/fe/core/controllerextensions/RoutingListener.js +3 -2
  52. package/src/sap/fe/core/controllerextensions/Share.js +3 -2
  53. package/src/sap/fe/core/controllerextensions/SideEffects.js +9 -9
  54. package/src/sap/fe/core/controllerextensions/SideEffects.ts +22 -21
  55. package/src/sap/fe/core/controllerextensions/ViewState.js +36 -8
  56. package/src/sap/fe/core/controls/ActionParameterDialog.fragment.xml +2 -1
  57. package/src/sap/fe/core/controls/CommandExecution.js +3 -2
  58. package/src/sap/fe/core/controls/ConditionalWrapper.js +3 -2
  59. package/src/sap/fe/core/controls/CustomQuickViewPage.js +112 -113
  60. package/src/sap/fe/core/controls/DataLossOrDraftDiscard/DataLossOrDraftDiscardHandler.js +9 -5
  61. package/src/sap/fe/core/controls/FieldWrapper.js +18 -23
  62. package/src/sap/fe/core/controls/FilterBar.js +3 -2
  63. package/src/sap/fe/core/controls/FormElementWrapper.js +3 -7
  64. package/src/sap/fe/core/controls/MultiValueParameterDelegate.js +3 -2
  65. package/src/sap/fe/core/controls/NonComputedVisibleKeyFieldsDialog.fragment.xml +2 -1
  66. package/src/sap/fe/core/controls/filterbar/FilterContainer.js +3 -2
  67. package/src/sap/fe/core/controls/filterbar/VisualFilter.js +5 -4
  68. package/src/sap/fe/core/controls/filterbar/VisualFilterContainer.js +3 -2
  69. package/src/sap/fe/core/controls/filterbar/utils/VisualFilterUtils.js +3 -2
  70. package/src/sap/fe/core/controls/massEdit/MassEditDialog.fragment.xml +106 -0
  71. package/src/sap/fe/core/controls/massEdit/MassEditHandlers.js +79 -0
  72. package/src/sap/fe/core/converters/ConverterContext.js +14 -2
  73. package/src/sap/fe/core/converters/ConverterContext.ts +14 -3
  74. package/src/sap/fe/core/converters/ManifestSettings.js +2 -1
  75. package/src/sap/fe/core/converters/ManifestSettings.ts +8 -1
  76. package/src/sap/fe/core/converters/ManifestWrapper.js +56 -32
  77. package/src/sap/fe/core/converters/ManifestWrapper.ts +24 -6
  78. package/src/sap/fe/core/converters/MetaModelConverter.js +76 -6
  79. package/src/sap/fe/core/converters/MetaModelConverter.ts +79 -15
  80. package/src/sap/fe/core/converters/TemplateConverter.js +1 -1
  81. package/src/sap/fe/core/converters/TemplateConverter.ts +2 -2
  82. package/src/sap/fe/core/converters/annotations/DataField.js +3 -3
  83. package/src/sap/fe/core/converters/annotations/DataField.ts +1 -1
  84. package/src/sap/fe/core/converters/common/AnnotationConverter.js +85 -46
  85. package/src/sap/fe/core/converters/controls/Common/Action.js +16 -4
  86. package/src/sap/fe/core/converters/controls/Common/Action.ts +14 -5
  87. package/src/sap/fe/core/converters/controls/Common/Chart.js +5 -3
  88. package/src/sap/fe/core/converters/controls/Common/Chart.ts +11 -3
  89. package/src/sap/fe/core/converters/controls/Common/DataVisualization.js +3 -3
  90. package/src/sap/fe/core/converters/controls/Common/DataVisualization.ts +2 -2
  91. package/src/sap/fe/core/converters/controls/Common/Form.js +60 -19
  92. package/src/sap/fe/core/converters/controls/Common/Form.ts +66 -14
  93. package/src/sap/fe/core/converters/controls/Common/KPI.js +69 -4
  94. package/src/sap/fe/core/converters/controls/Common/KPI.ts +72 -0
  95. package/src/sap/fe/core/converters/controls/Common/Table.js +503 -399
  96. package/src/sap/fe/core/converters/controls/Common/Table.ts +615 -480
  97. package/src/sap/fe/core/converters/controls/Common/table/StandardActions.js +527 -0
  98. package/src/sap/fe/core/converters/controls/Common/table/StandardActions.ts +655 -0
  99. package/src/sap/fe/core/converters/controls/ListReport/FilterBar.js +489 -37
  100. package/src/sap/fe/core/converters/controls/ListReport/FilterBar.ts +506 -58
  101. package/src/sap/fe/core/converters/controls/ListReport/VisualFilters.js +2 -2
  102. package/src/sap/fe/core/converters/controls/ListReport/VisualFilters.ts +1 -1
  103. package/src/sap/fe/core/converters/controls/ObjectPage/SubSection.js +6 -6
  104. package/src/sap/fe/core/converters/controls/ObjectPage/SubSection.ts +9 -15
  105. package/src/sap/fe/core/converters/helpers/Aggregation.js +18 -3
  106. package/src/sap/fe/core/converters/helpers/Aggregation.ts +28 -5
  107. package/src/sap/fe/core/converters/helpers/ID.js +9 -1
  108. package/src/sap/fe/core/converters/helpers/ID.ts +4 -0
  109. package/src/sap/fe/core/converters/helpers/IssueManager.js +7 -1
  110. package/src/sap/fe/core/converters/helpers/IssueManager.ts +6 -0
  111. package/src/sap/fe/core/converters/objectPage/HeaderAndFooterAction.js +4 -4
  112. package/src/sap/fe/core/converters/objectPage/HeaderAndFooterAction.ts +3 -3
  113. package/src/sap/fe/core/converters/templates/ListReportConverter.js +29 -11
  114. package/src/sap/fe/core/converters/templates/ListReportConverter.ts +36 -20
  115. package/src/sap/fe/core/designtime/AppComponent.designtime.js +13 -3
  116. package/src/sap/fe/core/formatters/CriticalityFormatter.js +1 -1
  117. package/src/sap/fe/core/formatters/CriticalityFormatter.ts +1 -1
  118. package/src/sap/fe/core/formatters/FPMFormatter.js +1 -1
  119. package/src/sap/fe/core/formatters/FPMFormatter.ts +4 -10
  120. package/src/sap/fe/core/formatters/KPIFormatter.js +1 -1
  121. package/src/sap/fe/core/formatters/KPIFormatter.ts +3 -1
  122. package/src/sap/fe/core/formatters/TableFormatter.js +65 -31
  123. package/src/sap/fe/core/formatters/TableFormatter.ts +62 -30
  124. package/src/sap/fe/core/formatters/ValueFormatter.js +30 -5
  125. package/src/sap/fe/core/formatters/ValueFormatter.ts +30 -7
  126. package/src/sap/fe/core/fpm/Component.js +3 -2
  127. package/src/sap/fe/core/helpers/AppStartupHelper.js +359 -0
  128. package/src/sap/fe/core/helpers/AppStartupHelper.ts +391 -0
  129. package/src/sap/fe/core/helpers/BindingExpression.js +479 -464
  130. package/src/sap/fe/core/helpers/BindingExpression.ts +477 -483
  131. package/src/sap/fe/core/helpers/ClassSupport.js +27 -15
  132. package/src/sap/fe/core/helpers/ClassSupport.ts +31 -20
  133. package/src/sap/fe/core/helpers/DynamicAnnotationPathHelper.js +63 -59
  134. package/src/sap/fe/core/helpers/DynamicAnnotationPathHelper.ts +56 -0
  135. package/src/sap/fe/core/helpers/EditState.js +81 -84
  136. package/src/sap/fe/core/helpers/EditState.ts +81 -0
  137. package/src/sap/fe/core/helpers/ExcelFormatHelper.js +62 -48
  138. package/src/sap/fe/core/helpers/ExcelFormatHelper.ts +49 -0
  139. package/src/sap/fe/core/helpers/FPMHelper.js +52 -56
  140. package/src/sap/fe/core/helpers/FPMHelper.ts +62 -0
  141. package/src/sap/fe/core/helpers/KeepAliveHelper.js +210 -0
  142. package/src/sap/fe/core/helpers/KeepAliveHelper.ts +202 -0
  143. package/src/sap/fe/core/helpers/KeepAliveRefreshTypes.js +32 -0
  144. package/src/sap/fe/core/helpers/KeepAliveRefreshTypes.ts +36 -0
  145. package/src/sap/fe/core/helpers/MassEditHelper.js +687 -0
  146. package/src/sap/fe/core/helpers/ModelHelper.js +229 -174
  147. package/src/sap/fe/core/helpers/ModelHelper.ts +227 -0
  148. package/src/sap/fe/core/helpers/PasteHelper.js +210 -132
  149. package/src/sap/fe/core/helpers/PasteHelper.ts +196 -0
  150. package/src/sap/fe/core/helpers/SemanticDateOperators.js +332 -313
  151. package/src/sap/fe/core/helpers/SemanticDateOperators.ts +330 -0
  152. package/src/sap/fe/core/helpers/SemanticKeyHelper.js +66 -67
  153. package/src/sap/fe/core/helpers/SemanticKeyHelper.ts +73 -0
  154. package/src/sap/fe/core/helpers/StableIdHelper.js +4 -7
  155. package/src/sap/fe/core/helpers/StableIdHelper.ts +2 -6
  156. package/src/sap/fe/core/jsx-runtime/jsx.js +1 -1
  157. package/src/sap/fe/core/jsx-runtime/jsx.ts +1 -1
  158. package/src/sap/fe/core/library.js +30 -3
  159. package/src/sap/fe/core/library.support.js +12 -5
  160. package/src/sap/fe/core/manifestMerger/ChangePageConfiguration.js +62 -0
  161. package/src/sap/fe/core/manifestMerger/ChangePageConfiguration.ts +66 -0
  162. package/src/sap/fe/core/messagebundle.properties +72 -10
  163. package/src/sap/fe/core/messagebundle_ar.properties +41 -0
  164. package/src/sap/fe/core/messagebundle_bg.properties +41 -0
  165. package/src/sap/fe/core/messagebundle_ca.properties +41 -0
  166. package/src/sap/fe/core/messagebundle_cs.properties +42 -1
  167. package/src/sap/fe/core/messagebundle_cy.properties +41 -0
  168. package/src/sap/fe/core/messagebundle_da.properties +41 -0
  169. package/src/sap/fe/core/messagebundle_de.properties +41 -0
  170. package/src/sap/fe/core/messagebundle_el.properties +41 -0
  171. package/src/sap/fe/core/messagebundle_en.properties +41 -0
  172. package/src/sap/fe/core/messagebundle_en_GB.properties +41 -0
  173. package/src/sap/fe/core/messagebundle_en_US_sappsd.properties +41 -0
  174. package/src/sap/fe/core/messagebundle_en_US_saprigi.properties +35 -0
  175. package/src/sap/fe/core/messagebundle_en_US_saptrc.properties +41 -0
  176. package/src/sap/fe/core/messagebundle_es.properties +41 -0
  177. package/src/sap/fe/core/messagebundle_es_MX.properties +41 -0
  178. package/src/sap/fe/core/messagebundle_et.properties +41 -0
  179. package/src/sap/fe/core/messagebundle_fi.properties +41 -0
  180. package/src/sap/fe/core/messagebundle_fr.properties +46 -5
  181. package/src/sap/fe/core/messagebundle_fr_CA.properties +41 -0
  182. package/src/sap/fe/core/messagebundle_hi.properties +41 -0
  183. package/src/sap/fe/core/messagebundle_hr.properties +41 -0
  184. package/src/sap/fe/core/messagebundle_hu.properties +42 -1
  185. package/src/sap/fe/core/messagebundle_id.properties +45 -4
  186. package/src/sap/fe/core/messagebundle_it.properties +41 -0
  187. package/src/sap/fe/core/messagebundle_iw.properties +41 -0
  188. package/src/sap/fe/core/messagebundle_ja.properties +50 -9
  189. package/src/sap/fe/core/messagebundle_kk.properties +41 -0
  190. package/src/sap/fe/core/messagebundle_ko.properties +41 -0
  191. package/src/sap/fe/core/messagebundle_lt.properties +41 -0
  192. package/src/sap/fe/core/messagebundle_lv.properties +41 -0
  193. package/src/sap/fe/core/messagebundle_ms.properties +41 -0
  194. package/src/sap/fe/core/messagebundle_nl.properties +41 -0
  195. package/src/sap/fe/core/messagebundle_no.properties +41 -0
  196. package/src/sap/fe/core/messagebundle_pl.properties +41 -0
  197. package/src/sap/fe/core/messagebundle_pt.properties +43 -2
  198. package/src/sap/fe/core/messagebundle_pt_PT.properties +41 -0
  199. package/src/sap/fe/core/messagebundle_ro.properties +41 -0
  200. package/src/sap/fe/core/messagebundle_ru.properties +41 -0
  201. package/src/sap/fe/core/messagebundle_sh.properties +41 -0
  202. package/src/sap/fe/core/messagebundle_sk.properties +41 -0
  203. package/src/sap/fe/core/messagebundle_sl.properties +41 -0
  204. package/src/sap/fe/core/messagebundle_sv.properties +42 -1
  205. package/src/sap/fe/core/messagebundle_th.properties +41 -0
  206. package/src/sap/fe/core/messagebundle_tr.properties +41 -0
  207. package/src/sap/fe/core/messagebundle_uk.properties +41 -0
  208. package/src/sap/fe/core/messagebundle_vi.properties +41 -0
  209. package/src/sap/fe/core/messagebundle_zh_CN.properties +41 -0
  210. package/src/sap/fe/core/messagebundle_zh_TW.properties +41 -0
  211. package/src/sap/fe/core/services/AsyncComponentServiceFactory.js +2 -2
  212. package/src/sap/fe/core/services/AsyncComponentServiceFactory.ts +3 -1
  213. package/src/sap/fe/core/services/CacheHandlerServiceFactory.js +269 -202
  214. package/src/sap/fe/core/services/CacheHandlerServiceFactory.ts +212 -0
  215. package/src/sap/fe/core/services/EnvironmentServiceFactory.js +4 -3
  216. package/src/sap/fe/core/services/EnvironmentServiceFactory.ts +9 -5
  217. package/src/sap/fe/core/services/NavigationServiceFactory.js +406 -300
  218. package/src/sap/fe/core/services/NavigationServiceFactory.ts +316 -0
  219. package/src/sap/fe/core/services/ResourceModelServiceFactory.js +149 -81
  220. package/src/sap/fe/core/services/ResourceModelServiceFactory.ts +80 -0
  221. package/src/sap/fe/core/services/RoutingServiceFactory.js +987 -1166
  222. package/src/sap/fe/core/services/RoutingServiceFactory.ts +898 -0
  223. package/src/sap/fe/core/services/ShellServicesFactory.js +31 -2
  224. package/src/sap/fe/core/services/ShellServicesFactory.ts +45 -11
  225. package/src/sap/fe/core/services/SideEffectsServiceFactory.js +41 -84
  226. package/src/sap/fe/core/services/SideEffectsServiceFactory.ts +56 -99
  227. package/src/sap/fe/core/services/TemplatedViewServiceFactory.js +461 -478
  228. package/src/sap/fe/core/services/TemplatedViewServiceFactory.ts +453 -0
  229. package/src/sap/fe/core/services/view/TemplatingErrorPage.controller.js +10 -8
  230. package/src/sap/fe/core/services/view/TemplatingErrorPage.controller.ts +8 -0
  231. package/src/sap/fe/core/support/AnnotationIssue.support.js +15 -3
  232. package/src/sap/fe/core/support/AnnotationIssue.support.ts +16 -2
  233. package/src/sap/fe/core/support/CollectionFacetUnsupportedLevel.support.js +2 -2
  234. package/src/sap/fe/core/support/CollectionFacetUnsupportedLevel.support.ts +1 -1
  235. package/src/sap/fe/core/support/InvalidAnnotationColumnKey.support.js +38 -0
  236. package/src/sap/fe/core/support/InvalidAnnotationColumnKey.support.ts +18 -0
  237. package/src/sap/fe/core/templating/DataModelPathHelper.js +6 -24
  238. package/src/sap/fe/core/templating/DataModelPathHelper.ts +10 -23
  239. package/src/sap/fe/core/templating/DisplayModeFormatter.js +114 -0
  240. package/src/sap/fe/core/templating/DisplayModeFormatter.ts +86 -0
  241. package/src/sap/fe/core/templating/EntitySetHelper.js +80 -7
  242. package/src/sap/fe/core/templating/EntitySetHelper.ts +42 -2
  243. package/src/sap/fe/core/templating/FieldControlHelper.js +8 -8
  244. package/src/sap/fe/core/templating/FieldControlHelper.ts +25 -7
  245. package/src/sap/fe/core/templating/FilterHelper.js +139 -70
  246. package/src/sap/fe/core/templating/FilterHelper.ts +140 -70
  247. package/src/sap/fe/core/templating/PropertyHelper.js +2 -2
  248. package/src/sap/fe/core/templating/PropertyHelper.ts +1 -1
  249. package/src/sap/fe/core/templating/UIFormatters.js +45 -113
  250. package/src/sap/fe/core/templating/UIFormatters.ts +39 -76
  251. package/src/sap/fe/core/type/Email.js +1 -1
  252. package/src/sap/fe/core/type/Email.ts +4 -6
  253. package/ui5.yaml +0 -3
@@ -28,7 +28,7 @@ import { ApplyAnnotationExpression, PathAnnotationExpression } from "@sap-ux/voc
28
28
  import { EntitySet } from "@sap-ux/vocabularies-types/dist/Converter";
29
29
  import { resolveEnumValue } from "./AnnotationEnum";
30
30
 
31
- type PrimitiveType = string | number | boolean | object | null | undefined;
31
+ type PrimitiveType = string | number | bigint | boolean | object | null | undefined;
32
32
 
33
33
  type BaseExpression<T> = {
34
34
  _type: string;
@@ -87,6 +87,11 @@ export type ConcatExpression = BaseExpression<string> & {
87
87
  expressions: Expression<string>[];
88
88
  };
89
89
 
90
+ export type LengthExpression = BaseExpression<string> & {
91
+ _type: "Length";
92
+ bindingExpression: BindingExpressionExpression<any>;
93
+ };
94
+
90
95
  export type UnresolveableBindingExpression = BaseExpression<string> & {
91
96
  _type: "Unresolveable";
92
97
  };
@@ -154,6 +159,7 @@ export type Expression<T> =
154
159
  | NotExpression
155
160
  | TruthyExpression
156
161
  | ConcatExpression
162
+ | LengthExpression
157
163
  | BindingExpressionExpression<T>
158
164
  | EmbeddedBindingExpression<T>
159
165
  | EmbeddedExpressionBindingExpression<T>
@@ -175,14 +181,7 @@ export const unresolveableExpression: UnresolveableBindingExpression = {
175
181
  };
176
182
 
177
183
  function escapeXmlAttribute(inputString: string) {
178
- return inputString.replace(/[']/g, function(c: string) {
179
- switch (c) {
180
- case "'":
181
- return "\\'";
182
- default:
183
- return c;
184
- }
185
- });
184
+ return inputString.replace(/'/g, "\\'");
186
185
  }
187
186
 
188
187
  export function hasUnresolveableExpression(...expressions: Expression<any>[]): boolean {
@@ -230,7 +229,7 @@ export function _checkExpressionsAreEqual<T>(a: Expression<T>, b: Expression<T>)
230
229
 
231
230
  case "Comparison":
232
231
  return (
233
- a.operator == (b as ComparisonExpression).operator &&
232
+ a.operator === (b as ComparisonExpression).operator &&
234
233
  _checkExpressionsAreEqual(a.operand1, (b as ComparisonExpression).operand1) &&
235
234
  _checkExpressionsAreEqual(a.operand2, (b as ComparisonExpression).operand2)
236
235
  );
@@ -245,6 +244,9 @@ export function _checkExpressionsAreEqual<T>(a: Expression<T>, b: Expression<T>)
245
244
  return _checkExpressionsAreEqual(expression, bExpressions[index]);
246
245
  });
247
246
 
247
+ case "Length":
248
+ return _checkExpressionsAreEqual(a.bindingExpression, (b as LengthExpression).bindingExpression);
249
+
248
250
  case "Binding":
249
251
  return (
250
252
  a.modelName === (b as BindingExpressionExpression<T>).modelName &&
@@ -288,6 +290,7 @@ export function _checkExpressionsAreEqual<T>(a: Expression<T>, b: Expression<T>)
288
290
  case "Ref":
289
291
  return a.ref === (b as ReferenceExpression).ref;
290
292
  }
293
+ return false;
291
294
  }
292
295
 
293
296
  /**
@@ -319,21 +322,15 @@ function flattenSetExpression(expression: SetExpression): SetExpression {
319
322
  * @returns {boolean} `true` if the set of expressions contains an expression and its negation
320
323
  */
321
324
  function hasOppositeExpressions(expressions: Expression<boolean>[]): boolean {
322
- if (expressions.length < 2) {
323
- return false;
324
- }
325
-
326
- let i = expressions.length;
327
- while (i--) {
328
- const expression = expressions[i];
329
- const negatedExpression = not(expression);
330
- for (let j = 0; j < i; j++) {
331
- if (_checkExpressionsAreEqual(expressions[j], negatedExpression)) {
325
+ const negatedExpressions = expressions.map(not);
326
+ return expressions.some((expression, index) => {
327
+ for (let i = index + 1; i < negatedExpressions.length; i++) {
328
+ if (_checkExpressionsAreEqual(expression, negatedExpressions[i])) {
332
329
  return true;
333
330
  }
334
331
  }
335
- }
336
- return false;
332
+ return false;
333
+ });
337
334
  }
338
335
 
339
336
  /**
@@ -355,9 +352,9 @@ export function and(...operands: ExpressionOrPrimitive<boolean>[]): Expression<b
355
352
  if (hasUnresolveableExpression(...expressions)) {
356
353
  return unresolveableExpression;
357
354
  }
358
- let isStaticFalse: boolean = false;
355
+ let isStaticFalse = false;
359
356
  const nonTrivialExpression = expressions.filter(expression => {
360
- if (isConstant(expression) && !expression.value) {
357
+ if (isFalse(expression)) {
361
358
  isStaticFalse = true;
362
359
  }
363
360
  return !isConstant(expression);
@@ -366,9 +363,7 @@ export function and(...operands: ExpressionOrPrimitive<boolean>[]): Expression<b
366
363
  return constant(false);
367
364
  } else if (nonTrivialExpression.length === 0) {
368
365
  // Resolve the constant then
369
- const isValid = expressions.reduce((isValid, expression) => {
370
- return isValid && isConstant(expression) && expression.value;
371
- }, true);
366
+ const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
372
367
  return constant(isValid);
373
368
  } else if (nonTrivialExpression.length === 1) {
374
369
  return nonTrivialExpression[0];
@@ -401,9 +396,9 @@ export function or(...operands: ExpressionOrPrimitive<boolean>[]): Expression<bo
401
396
  if (hasUnresolveableExpression(...expressions)) {
402
397
  return unresolveableExpression;
403
398
  }
404
- let isStaticTrue: boolean = false;
399
+ let isStaticTrue = false;
405
400
  const nonTrivialExpression = expressions.filter(expression => {
406
- if (isConstant(expression) && expression.value) {
401
+ if (isTrue(expression)) {
407
402
  isStaticTrue = true;
408
403
  }
409
404
  return !isConstant(expression) || expression.value;
@@ -412,9 +407,7 @@ export function or(...operands: ExpressionOrPrimitive<boolean>[]): Expression<bo
412
407
  return constant(true);
413
408
  } else if (nonTrivialExpression.length === 0) {
414
409
  // Resolve the constant then
415
- const isValid = expressions.reduce((isValid, expression) => {
416
- return isValid && isConstant(expression) && expression.value;
417
- }, true);
410
+ const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
418
411
  return constant(isValid);
419
412
  } else if (nonTrivialExpression.length === 1) {
420
413
  return nonTrivialExpression[0];
@@ -459,26 +452,26 @@ export function not(operand: ExpressionOrPrimitive<boolean>): Expression<boolean
459
452
  // Create the reverse comparison
460
453
  switch (operand.operator) {
461
454
  case "!==":
462
- return equal(operand.operand1, operand.operand2);
455
+ return { ...operand, operator: "===" };
463
456
  case "<":
464
- return greaterOrEqual(operand.operand1, operand.operand2);
457
+ return { ...operand, operator: ">=" };
465
458
  case "<=":
466
- return greaterThan(operand.operand1, operand.operand2);
459
+ return { ...operand, operator: ">" };
467
460
  case "===":
468
- return notEqual(operand.operand1, operand.operand2);
461
+ return { ...operand, operator: "!==" };
469
462
  case ">":
470
- return lessOrEqual(operand.operand1, operand.operand2);
463
+ return { ...operand, operator: "<=" };
471
464
  case ">=":
472
- return lessThan(operand.operand1, operand.operand2);
465
+ return { ...operand, operator: "<" };
473
466
  }
474
467
  } else if (operand._type === "Not") {
475
468
  return operand.operand;
476
- } else {
477
- return {
478
- _type: "Not",
479
- operand: operand
480
- };
481
469
  }
470
+
471
+ return {
472
+ _type: "Not",
473
+ operand: operand
474
+ };
482
475
  }
483
476
 
484
477
  /**
@@ -550,19 +543,16 @@ export function constant<T extends PrimitiveType>(value: T): ConstantExpression<
550
543
  if (typeof value === "object" && value !== null && value !== undefined) {
551
544
  if (Array.isArray(value)) {
552
545
  constantValue = value.map(wrapPrimitive) as T;
553
- } else if (isPrimitiveObject(value as object)) {
546
+ } else if (isPrimitiveObject(value)) {
554
547
  constantValue = value.valueOf() as T;
555
548
  } else {
556
- const val = value as { [name: string]: ExpressionOrPrimitive<any> };
557
- const obj = Object.keys(val).reduce((obj, key) => {
558
- const value = wrapPrimitive(val[key]);
559
- if (value._type !== "Constant" || value.value !== undefined) {
560
- obj[key] = value;
549
+ constantValue = Object.entries(value).reduce((plainExpression, [key, val]) => {
550
+ const wrappedValue = wrapPrimitive(val);
551
+ if (wrappedValue._type !== "Constant" || wrappedValue.value !== undefined) {
552
+ plainExpression[key] = wrappedValue;
561
553
  }
562
- return obj;
563
- }, {} as PlainExpressionObject);
564
-
565
- constantValue = obj as T;
554
+ return plainExpression;
555
+ }, {} as PlainExpressionObject) as T;
566
556
  }
567
557
  } else {
568
558
  constantValue = value;
@@ -589,16 +579,10 @@ export function resolveBindingString<T extends PrimitiveType>(
589
579
  value: value
590
580
  };
591
581
  }
582
+ } else if (targetType === "boolean" && typeof value === "string" && (value === "true" || value === "false")) {
583
+ return constant(value === "true") as ConstantExpression<T>;
592
584
  } else {
593
- switch (targetType) {
594
- case "boolean":
595
- if (typeof value === "string" && (value === "true" || value === "false")) {
596
- return constant(value === "true") as ConstantExpression<T>;
597
- }
598
- return constant(value) as ConstantExpression<T>;
599
- default:
600
- return constant(value) as ConstantExpression<T>;
601
- }
585
+ return constant(value) as ConstantExpression<T>;
602
586
  }
603
587
  }
604
588
 
@@ -607,11 +591,11 @@ export function resolveBindingString<T extends PrimitiveType>(
607
591
  *
608
592
  * @see fn
609
593
  *
610
- * @param ref Reference
594
+ * @param reference Reference
611
595
  * @returns {ReferenceExpression} The object reference binding part
612
596
  */
613
- export function ref(ref: string | null): ReferenceExpression {
614
- return { _type: "Ref", ref };
597
+ export function ref(reference: string | null): ReferenceExpression {
598
+ return { _type: "Ref", ref: reference };
615
599
  }
616
600
 
617
601
  /**
@@ -653,6 +637,14 @@ export function isConstant<T extends PrimitiveType>(maybeConstant: ExpressionOrP
653
637
  return typeof maybeConstant !== "object" || (maybeConstant as BaseExpression<T>)._type === "Constant";
654
638
  }
655
639
 
640
+ function isTrue(expression: Expression<PrimitiveType>) {
641
+ return isConstant(expression) && expression.value === true;
642
+ }
643
+
644
+ function isFalse(expression: Expression<PrimitiveType>) {
645
+ return isConstant(expression) && expression.value === false;
646
+ }
647
+
656
648
  /**
657
649
  * Checks if the expression or value provided is binding or not.
658
650
  *
@@ -700,97 +692,97 @@ function isPrimitiveObject(objectType: object): boolean {
700
692
  }
701
693
  }
702
694
  /**
703
- * Check if the passed annotation expression is a ComplexAnnotationExpression.
695
+ * Check if the passed annotation annotationValue is a ComplexAnnotationExpression.
704
696
  *
705
697
  * @template T The target type
706
- * @param annotationExpression The annotation expression to evaluate
698
+ * @param annotationValue The annotation annotationValue to evaluate
707
699
  * @returns {boolean} `true` if the object is a {ComplexAnnotationExpression}
708
700
  */
709
- function isComplexAnnotationExpression<T>(
710
- annotationExpression: PropertyAnnotationValue<T>
711
- ): annotationExpression is ComplexAnnotationExpression<T> {
712
- return typeof annotationExpression === "object" && !isPrimitiveObject(annotationExpression as object);
701
+ function isComplexAnnotationExpression<T>(annotationValue: PropertyAnnotationValue<T>): annotationValue is ComplexAnnotationExpression<T> {
702
+ return typeof annotationValue === "object" && !isPrimitiveObject(annotationValue as object);
713
703
  }
714
704
 
715
705
  /**
716
- * Generate the corresponding expression for a given annotation expression.
706
+ * Generate the corresponding annotationValue for a given annotation annotationValue.
717
707
  *
718
708
  * @template T The target type
719
- * @param annotationExpression The source annotation expression
709
+ * @param annotationValue The source annotation annotationValue
720
710
  * @param visitedNavigationPaths The path from the root entity set
721
- * @param defaultValue Default value if the annotationExpression is undefined
711
+ * @param defaultValue Default value if the annotationValue is undefined
722
712
  * @param pathVisitor A function to modify the resulting path
723
- * @returns {Expression<T>} The expression equivalent to that annotation expression
713
+ * @returns {Expression<T>} The annotationValue equivalent to that annotation annotationValue
724
714
  */
725
715
  export function annotationExpression<T extends PrimitiveType>(
726
- annotationExpression: PropertyAnnotationValue<T>,
716
+ annotationValue: PropertyAnnotationValue<T>,
727
717
  visitedNavigationPaths: string[] = [],
728
718
  defaultValue?: ExpressionOrPrimitive<T>,
729
719
  pathVisitor?: Function
730
720
  ): Expression<T> {
731
- if (annotationExpression === undefined) {
721
+ if (annotationValue === undefined) {
732
722
  return wrapPrimitive(defaultValue as T);
733
723
  }
734
- if (!isComplexAnnotationExpression(annotationExpression)) {
735
- return constant(annotationExpression);
736
- } else {
737
- switch (annotationExpression.type) {
738
- case "Path":
739
- return bindingExpression(annotationExpression.path, undefined, visitedNavigationPaths, pathVisitor);
740
- case "If":
741
- return annotationIfExpression(annotationExpression.If, visitedNavigationPaths, pathVisitor);
742
- case "Not":
743
- return not(parseAnnotationCondition(annotationExpression.Not, visitedNavigationPaths, pathVisitor)) as Expression<T>;
744
- case "Eq":
745
- return equal(
746
- parseAnnotationCondition(annotationExpression.Eq[0], visitedNavigationPaths, pathVisitor),
747
- parseAnnotationCondition(annotationExpression.Eq[1], visitedNavigationPaths, pathVisitor)
748
- ) as Expression<T>;
749
- case "Ne":
750
- return notEqual(
751
- parseAnnotationCondition(annotationExpression.Ne[0], visitedNavigationPaths, pathVisitor),
752
- parseAnnotationCondition(annotationExpression.Ne[1], visitedNavigationPaths, pathVisitor)
753
- ) as Expression<T>;
754
- case "Gt":
755
- return greaterThan(
756
- parseAnnotationCondition(annotationExpression.Gt[0], visitedNavigationPaths, pathVisitor),
757
- parseAnnotationCondition(annotationExpression.Gt[1], visitedNavigationPaths, pathVisitor)
758
- ) as Expression<T>;
759
- case "Ge":
760
- return greaterOrEqual(
761
- parseAnnotationCondition(annotationExpression.Ge[0], visitedNavigationPaths, pathVisitor),
762
- parseAnnotationCondition(annotationExpression.Ge[1], visitedNavigationPaths, pathVisitor)
763
- ) as Expression<T>;
764
- case "Lt":
765
- return lessThan(
766
- parseAnnotationCondition(annotationExpression.Lt[0], visitedNavigationPaths, pathVisitor),
767
- parseAnnotationCondition(annotationExpression.Lt[1], visitedNavigationPaths, pathVisitor)
768
- ) as Expression<T>;
769
- case "Le":
770
- return lessOrEqual(
771
- parseAnnotationCondition(annotationExpression.Le[0], visitedNavigationPaths, pathVisitor),
772
- parseAnnotationCondition(annotationExpression.Le[1], visitedNavigationPaths, pathVisitor)
773
- ) as Expression<T>;
774
- case "Or":
775
- return or(
776
- ...(annotationExpression.Or.map(function(orCondition) {
777
- return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor);
778
- }) as Expression<boolean>[])
779
- ) as Expression<T>;
780
- case "And":
781
- return and(
782
- ...(annotationExpression.And.map(function(andCondition) {
783
- return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor);
784
- }) as Expression<boolean>[])
785
- ) as Expression<T>;
786
- case "Apply":
787
- return annotationApplyExpression(
788
- annotationExpression as ApplyAnnotationExpression<string>,
789
- visitedNavigationPaths,
790
- pathVisitor
791
- ) as Expression<T>;
792
- }
724
+
725
+ if (!isComplexAnnotationExpression(annotationValue)) {
726
+ return constant(annotationValue);
727
+ }
728
+
729
+ switch (annotationValue.type) {
730
+ case "Path":
731
+ return bindingExpression(annotationValue.path, undefined, visitedNavigationPaths, pathVisitor);
732
+ case "If":
733
+ return annotationIfExpression(annotationValue.If, visitedNavigationPaths, pathVisitor);
734
+ case "Not":
735
+ return not(parseAnnotationCondition(annotationValue.Not, visitedNavigationPaths, pathVisitor)) as Expression<T>;
736
+ case "Eq":
737
+ return equal(
738
+ parseAnnotationCondition(annotationValue.Eq[0], visitedNavigationPaths, pathVisitor),
739
+ parseAnnotationCondition(annotationValue.Eq[1], visitedNavigationPaths, pathVisitor)
740
+ ) as Expression<T>;
741
+ case "Ne":
742
+ return notEqual(
743
+ parseAnnotationCondition(annotationValue.Ne[0], visitedNavigationPaths, pathVisitor),
744
+ parseAnnotationCondition(annotationValue.Ne[1], visitedNavigationPaths, pathVisitor)
745
+ ) as Expression<T>;
746
+ case "Gt":
747
+ return greaterThan(
748
+ parseAnnotationCondition(annotationValue.Gt[0], visitedNavigationPaths, pathVisitor),
749
+ parseAnnotationCondition(annotationValue.Gt[1], visitedNavigationPaths, pathVisitor)
750
+ ) as Expression<T>;
751
+ case "Ge":
752
+ return greaterOrEqual(
753
+ parseAnnotationCondition(annotationValue.Ge[0], visitedNavigationPaths, pathVisitor),
754
+ parseAnnotationCondition(annotationValue.Ge[1], visitedNavigationPaths, pathVisitor)
755
+ ) as Expression<T>;
756
+ case "Lt":
757
+ return lessThan(
758
+ parseAnnotationCondition(annotationValue.Lt[0], visitedNavigationPaths, pathVisitor),
759
+ parseAnnotationCondition(annotationValue.Lt[1], visitedNavigationPaths, pathVisitor)
760
+ ) as Expression<T>;
761
+ case "Le":
762
+ return lessOrEqual(
763
+ parseAnnotationCondition(annotationValue.Le[0], visitedNavigationPaths, pathVisitor),
764
+ parseAnnotationCondition(annotationValue.Le[1], visitedNavigationPaths, pathVisitor)
765
+ ) as Expression<T>;
766
+ case "Or":
767
+ return or(
768
+ ...(annotationValue.Or.map(function(orCondition) {
769
+ return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor);
770
+ }) as Expression<boolean>[])
771
+ ) as Expression<T>;
772
+ case "And":
773
+ return and(
774
+ ...(annotationValue.And.map(function(andCondition) {
775
+ return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor);
776
+ }) as Expression<boolean>[])
777
+ ) as Expression<T>;
778
+ case "Apply":
779
+ return annotationApplyExpression(
780
+ annotationValue as ApplyAnnotationExpression<string>,
781
+ visitedNavigationPaths,
782
+ pathVisitor
783
+ ) as Expression<T>;
793
784
  }
785
+ return unresolveableExpression;
794
786
  }
795
787
 
796
788
  /**
@@ -880,41 +872,40 @@ function parseAnnotationCondition<T extends PrimitiveType>(
880
872
  );
881
873
  } else if (annotationValue.hasOwnProperty("$EnumMember")) {
882
874
  return constant(resolveEnumValue((annotationValue as any).$EnumMember) as T);
883
- } else {
884
- return constant(false as T);
885
875
  }
876
+ return constant(false as T);
886
877
  }
887
878
 
888
879
  /**
889
880
  * Process the {IfAnnotationExpressionValue} into an expression.
890
881
  *
891
882
  * @template T The target type
892
- * @param annotationIfExpression An If expression returning the type T
883
+ * @param annotationValue An If expression returning the type T
893
884
  * @param visitedNavigationPaths The path from the root entity set
894
885
  * @param pathVisitor A function to modify the resulting path
895
886
  * @returns {Expression<T>} The equivalent ifElse expression
896
887
  */
897
888
  export function annotationIfExpression<T extends PrimitiveType>(
898
- annotationIfExpression: IfAnnotationExpressionValue<T>,
889
+ annotationValue: IfAnnotationExpressionValue<T>,
899
890
  visitedNavigationPaths: string[] = [],
900
891
  pathVisitor?: Function
901
892
  ): Expression<T> {
902
893
  return ifElse(
903
- parseAnnotationCondition(annotationIfExpression[0], visitedNavigationPaths, pathVisitor),
904
- parseAnnotationCondition(annotationIfExpression[1] as any, visitedNavigationPaths, pathVisitor),
905
- parseAnnotationCondition(annotationIfExpression[2] as any, visitedNavigationPaths, pathVisitor)
894
+ parseAnnotationCondition(annotationValue[0], visitedNavigationPaths, pathVisitor),
895
+ parseAnnotationCondition(annotationValue[1] as any, visitedNavigationPaths, pathVisitor),
896
+ parseAnnotationCondition(annotationValue[2] as any, visitedNavigationPaths, pathVisitor)
906
897
  );
907
898
  }
908
899
 
909
900
  export function annotationApplyExpression(
910
- annotationApplyExpression: ApplyAnnotationExpression<string>,
901
+ applyExpression: ApplyAnnotationExpression<string>,
911
902
  visitedNavigationPaths: string[] = [],
912
903
  pathVisitor?: Function
913
904
  ): Expression<string> {
914
- switch (annotationApplyExpression.Function) {
905
+ switch (applyExpression.Function) {
915
906
  case "odata.concat":
916
907
  return concat(
917
- ...annotationApplyExpression.Apply.map((applyParam: any) => {
908
+ ...applyExpression.Apply.map((applyParam: any) => {
918
909
  let applyParamConverted = applyParam;
919
910
  if (applyParam.hasOwnProperty("$Path")) {
920
911
  applyParamConverted = {
@@ -936,8 +927,8 @@ export function annotationApplyExpression(
936
927
  return annotationExpression(applyParamConverted, visitedNavigationPaths, undefined, pathVisitor);
937
928
  })
938
929
  );
939
- break;
940
930
  }
931
+ return unresolveableExpression;
941
932
  }
942
933
 
943
934
  /**
@@ -960,10 +951,6 @@ function comparison<T extends PrimitiveType>(
960
951
  return unresolveableExpression;
961
952
  }
962
953
  if (isConstant(leftExpression) && isConstant(rightExpression)) {
963
- if (leftExpression.value === undefined || rightExpression.value === undefined) {
964
- return constant(leftExpression.value === rightExpression.value);
965
- }
966
-
967
954
  switch (operator) {
968
955
  case "!==":
969
956
  return constant(leftExpression.value !== rightExpression.value);
@@ -976,7 +963,6 @@ function comparison<T extends PrimitiveType>(
976
963
  case ">=":
977
964
  return constant(leftExpression.value >= rightExpression.value);
978
965
  case "===":
979
- default:
980
966
  return constant(leftExpression.value === rightExpression.value);
981
967
  }
982
968
  } else {
@@ -989,6 +975,16 @@ function comparison<T extends PrimitiveType>(
989
975
  }
990
976
  }
991
977
 
978
+ export function length(expression: BindingExpressionExpression<any> | UnresolveableBindingExpression): Expression<number> {
979
+ if (expression._type === "Unresolveable") {
980
+ return expression;
981
+ }
982
+ return {
983
+ _type: "Length",
984
+ bindingExpression: expression
985
+ };
986
+ }
987
+
992
988
  /**
993
989
  * Comparison: "equal" (===).
994
990
  *
@@ -1010,32 +1006,35 @@ export function equal<T extends PrimitiveType>(
1010
1006
  return constant(true);
1011
1007
  }
1012
1008
 
1013
- // ((a === c) === true) => (a === c)
1014
- if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1015
- return leftExpression;
1016
- } else if (rightExpression._type === "Comparison" && isConstant(leftExpression) && leftExpression.value === true) {
1017
- // (true === (a === c)) => (a === c)
1018
- return rightExpression;
1019
- } else if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1020
- // ((a === c) === false) => !(a === c)
1021
- return not(leftExpression);
1022
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onTrue, rightExpression)) {
1023
- // (if(xxxx) { aaa } else { bbb } ) === aaa )
1024
- return or(leftExpression.condition, equal(leftExpression.onFalse, rightExpression));
1025
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)) {
1026
- return or(not(leftExpression.condition), equal(leftExpression.onTrue, rightExpression));
1027
- } else if (
1028
- leftExpression._type === "IfElse" &&
1029
- isConstant(leftExpression.onTrue) &&
1030
- isConstant(rightExpression) &&
1031
- isConstant(leftExpression.onFalse) &&
1032
- !_checkExpressionsAreEqual(leftExpression.onTrue, rightExpression) &&
1033
- !_checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)
1034
- ) {
1035
- return constant(false);
1009
+ function reduce(left: Expression<T>, right: Expression<T>) {
1010
+ if (left._type === "Comparison" && isTrue(right)) {
1011
+ // compare(a, b) === true ~~> compare(a, b)
1012
+ return left;
1013
+ } else if (left._type === "Comparison" && isFalse(right)) {
1014
+ // compare(a, b) === false ~~> !compare(a, b)
1015
+ return not(left);
1016
+ } else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onTrue, right)) {
1017
+ // (if (x) { a } else { b }) === a ~~> x || (b === a)
1018
+ return or(left.condition, equal(left.onFalse, right));
1019
+ } else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onFalse, right)) {
1020
+ // (if (x) { a } else { b }) === b ~~> !x || (a === b)
1021
+ return or(not(left.condition), equal(left.onTrue, right));
1022
+ } else if (
1023
+ left._type === "IfElse" &&
1024
+ isConstant(left.onTrue) &&
1025
+ isConstant(left.onFalse) &&
1026
+ isConstant(right) &&
1027
+ !_checkExpressionsAreEqual(left.onTrue, right) &&
1028
+ !_checkExpressionsAreEqual(left.onFalse, right)
1029
+ ) {
1030
+ return constant(false);
1031
+ }
1032
+ return undefined;
1036
1033
  }
1037
1034
 
1038
- return comparison("===", leftExpression, rightExpression);
1035
+ // exploit symmetry: a === b <~> b === a
1036
+ const reduced = reduce(leftExpression, rightExpression) ?? reduce(rightExpression, leftExpression);
1037
+ return reduced ?? comparison("===", leftExpression, rightExpression);
1039
1038
  }
1040
1039
 
1041
1040
  /**
@@ -1050,36 +1049,7 @@ export function notEqual<T extends PrimitiveType>(
1050
1049
  leftOperand: ExpressionOrPrimitive<T>,
1051
1050
  rightOperand: ExpressionOrPrimitive<T>
1052
1051
  ): Expression<boolean> {
1053
- const leftExpression = wrapPrimitive(leftOperand);
1054
- const rightExpression = wrapPrimitive(rightOperand);
1055
-
1056
- if (_checkExpressionsAreEqual(leftExpression, rightExpression)) {
1057
- return constant(false);
1058
- }
1059
-
1060
- // ((a === c) !== true) => !(a === c)
1061
- if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1062
- return not(leftExpression);
1063
- } else if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1064
- // ((a === c) !== false) => (a === c)
1065
- return leftExpression;
1066
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onTrue, rightExpression)) {
1067
- return and(not(leftExpression.condition), notEqual(leftExpression.onFalse, rightExpression));
1068
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)) {
1069
- return and(leftExpression.condition, notEqual(leftExpression.onTrue, rightExpression));
1070
- } else if (
1071
- leftExpression._type === "IfElse" &&
1072
- isConstant(leftExpression.onTrue) &&
1073
- isConstant(rightExpression) &&
1074
- isConstant(leftExpression.onFalse) &&
1075
- !_checkExpressionsAreEqual(leftExpression.onTrue, rightExpression) &&
1076
- !_checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)
1077
- ) {
1078
- // If the left expression is an if else where both onTrue and onFalse are not equals to the right expression -> simplify as true
1079
- return constant(true);
1080
- }
1081
-
1082
- return comparison("!==", leftExpression, rightExpression);
1052
+ return not(equal(leftOperand, rightOperand));
1083
1053
  }
1084
1054
 
1085
1055
  /**
@@ -1184,61 +1154,40 @@ export function ifElse<T extends PrimitiveType>(
1184
1154
  onFalseExpression = onFalseExpression.onFalse;
1185
1155
  }
1186
1156
 
1187
- // inline nested if-else expressions: condition
1188
- if (conditionExpression._type === "IfElse") {
1189
- if (
1190
- isConstant(conditionExpression.onFalse) &&
1191
- !conditionExpression.onFalse.value &&
1192
- isConstant(conditionExpression.onTrue) &&
1193
- conditionExpression.onTrue.value
1194
- ) {
1195
- // ifElse(ifElse(X, true, false), a, b) ==> ifElse(X, a, b)
1196
- conditionExpression = conditionExpression.condition;
1197
- } else if (
1198
- isConstant(conditionExpression.onFalse) &&
1199
- conditionExpression.onFalse.value &&
1200
- isConstant(conditionExpression.onTrue) &&
1201
- !conditionExpression.onTrue.value
1202
- ) {
1203
- // ifElse(ifElse(X, false, true), a, b) ==> ifElse(not(X), a, b)
1204
- conditionExpression = not(conditionExpression.condition);
1205
- } else if (
1206
- isConstant(conditionExpression.onTrue) &&
1207
- !conditionExpression.onTrue.value &&
1208
- !isConstant(conditionExpression.onFalse)
1209
- ) {
1210
- // ifElse(ifElse(X, false, a), b, c) ==> ifElse(and(not(X), a), b, c)
1211
- conditionExpression = and(not(conditionExpression.condition), conditionExpression.onFalse);
1212
- }
1213
- }
1214
-
1215
- // again swap branches if needed (in case one of the optimizations above led to a negated condition)
1216
- if (conditionExpression._type === "Not") {
1217
- // ifElse(not(X), a, b) --> ifElse(X, b, a)
1218
- [onTrueExpression, onFalseExpression] = [onFalseExpression, onTrueExpression];
1219
- conditionExpression = not(conditionExpression);
1220
- }
1221
-
1222
- // compute expression result for constant conditions
1157
+ // (if true then a else b) ~~> a
1158
+ // (if false then a else b) ~~> b
1223
1159
  if (isConstant(conditionExpression)) {
1224
1160
  return conditionExpression.value ? onTrueExpression : onFalseExpression;
1225
1161
  }
1226
1162
 
1227
- // compute expression result if onTrue and onFalse branches are equal
1163
+ // if (isConstantBoolean(onTrueExpression) || isConstantBoolean(onFalseExpression)) {
1164
+ // return or(and(condition, onTrueExpression as Expression<boolean>), and(not(condition), onFalseExpression as Expression<boolean>)) as Expression<T>
1165
+ // }
1166
+ // (if X then a else a) ~~> a
1228
1167
  if (_checkExpressionsAreEqual(onTrueExpression, onFalseExpression)) {
1229
1168
  return onTrueExpression;
1230
1169
  }
1231
1170
 
1232
- // If either trueExpression or falseExpression is a value equals to false the expression can be simplified
1233
- // If(Condition) Then XXX Else False -> Condition && XXX
1234
- if (isConstant(onFalseExpression) && onFalseExpression.value === false) {
1171
+ // if X then a else false ~~> X && a
1172
+ if (isFalse(onFalseExpression)) {
1235
1173
  return and(conditionExpression, onTrueExpression as Expression<boolean>) as Expression<T>;
1236
1174
  }
1237
- // If(Condition) Then False Else XXX -> !Condition && XXX
1238
- if (isConstant(onTrueExpression) && onTrueExpression.value === false) {
1175
+
1176
+ // if X then a else true ~~> !X || a
1177
+ if (isTrue(onFalseExpression)) {
1178
+ return or(not(conditionExpression), onTrueExpression as Expression<boolean>) as Expression<T>;
1179
+ }
1180
+
1181
+ // if X then false else a ~~> !X && a
1182
+ if (isFalse(onTrueExpression)) {
1239
1183
  return and(not(conditionExpression), onFalseExpression as Expression<boolean>) as Expression<T>;
1240
1184
  }
1241
1185
 
1186
+ // if X then true else a ~~> X || a
1187
+ if (isTrue(onTrueExpression)) {
1188
+ return or(conditionExpression, onFalseExpression as Expression<boolean>) as Expression<T>;
1189
+ }
1190
+
1242
1191
  return {
1243
1192
  _type: "IfElse",
1244
1193
  condition: conditionExpression,
@@ -1389,17 +1338,17 @@ export function addTypeInformation<T, U extends Fn<T>>(
1389
1338
  /**
1390
1339
  * Function call, optionally with arguments.
1391
1340
  *
1392
- * @param fn Function name or reference to function
1341
+ * @param func Function name or reference to function
1393
1342
  * @param parameters Arguments
1394
1343
  * @param on Object to call the function on
1395
1344
  * @returns {FunctionExpression<T>} Expression representing the function call (not the result of the function call!)
1396
1345
  */
1397
1346
  export function fn<T, U extends FunctionOrName<T>>(
1398
- fn: U,
1347
+ func: U,
1399
1348
  parameters: WrappedTuple<FunctionParameters<T, U>>,
1400
1349
  on?: ExpressionOrPrimitive<object>
1401
1350
  ): FunctionExpression<T> {
1402
- const functionName = typeof fn === "string" ? fn : (fn as Fn<T>).__functionName;
1351
+ const functionName = typeof func === "string" ? func : (func as Fn<T>).__functionName;
1403
1352
  return {
1404
1353
  _type: "Function",
1405
1354
  obj: on !== undefined ? wrapPrimitive(on) : undefined,
@@ -1445,31 +1394,26 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1445
1394
  inExpression: Expression<T>,
1446
1395
  expressionType: ExpressionType,
1447
1396
  transformFunction: TransformFunction,
1448
- includeAllExpression: boolean = false
1397
+ includeAllExpression = false
1449
1398
  ): Expression<T> {
1450
1399
  let expression = inExpression;
1451
1400
  switch (expression._type) {
1452
1401
  case "Function":
1453
- expression.parameters = expression.parameters.map(expression =>
1454
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1402
+ case "Formatter":
1403
+ expression.parameters = expression.parameters.map(parameter =>
1404
+ transformRecursively(parameter, expressionType, transformFunction, includeAllExpression)
1455
1405
  );
1456
1406
  break;
1457
1407
  case "Concat":
1458
- expression.expressions = expression.expressions.map(expression =>
1459
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1408
+ expression.expressions = expression.expressions.map(subExpression =>
1409
+ transformRecursively(subExpression, expressionType, transformFunction, includeAllExpression)
1460
1410
  );
1461
1411
  break;
1462
1412
  case "ComplexType":
1463
- expression.bindingParameters = expression.bindingParameters.map(expression =>
1464
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1413
+ expression.bindingParameters = expression.bindingParameters.map(bindingParameter =>
1414
+ transformRecursively(bindingParameter, expressionType, transformFunction, includeAllExpression)
1465
1415
  );
1466
1416
  break;
1467
- case "Formatter":
1468
- expression.parameters = expression.parameters.map(expression =>
1469
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1470
- );
1471
- break;
1472
-
1473
1417
  case "IfElse":
1474
1418
  const onTrue = transformRecursively(expression.onTrue, expressionType, transformFunction, includeAllExpression);
1475
1419
  const onFalse = transformRecursively(expression.onFalse, expressionType, transformFunction, includeAllExpression);
@@ -1489,8 +1433,8 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1489
1433
  break;
1490
1434
  case "Set":
1491
1435
  if (includeAllExpression) {
1492
- expression.operands = expression.operands.map(expression =>
1493
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1436
+ expression.operands = expression.operands.map(operand =>
1437
+ transformRecursively(operand, expressionType, transformFunction, includeAllExpression)
1494
1438
  );
1495
1439
  }
1496
1440
  break;
@@ -1503,6 +1447,7 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1503
1447
  break;
1504
1448
  case "DefaultBinding":
1505
1449
  case "Ref":
1450
+ case "Length":
1506
1451
  case "Binding":
1507
1452
  case "Constant":
1508
1453
  // Do nothing
@@ -1519,6 +1464,158 @@ export type BindingExpression<T> = T | string | undefined;
1519
1464
  const needParenthesis = function<T extends PrimitiveType>(expr: ExpressionOrPrimitive<T>): boolean {
1520
1465
  return !isConstant(expr) && !isBinding(expr) && isExpression(expr) && expr._type !== "IfElse" && expr._type !== "Function";
1521
1466
  };
1467
+
1468
+ /**
1469
+ * Compiles a constant object to a string.
1470
+ * @param expr
1471
+ * @param isNullable
1472
+ * @returns {string} The compiled string
1473
+ */
1474
+ function compileConstantObject<T>(expr: ConstantExpression<object>, isNullable = false) {
1475
+ if (isNullable && Object.keys(expr.value).length === 0) {
1476
+ return "";
1477
+ }
1478
+ // Objects
1479
+ const o = expr.value as PlainExpressionObject;
1480
+ const properties: string[] = [];
1481
+ Object.keys(o).forEach(key => {
1482
+ const value = o[key];
1483
+ const childResult = compileBinding(value, true, false, true);
1484
+ if (childResult && childResult.length > 0) {
1485
+ properties.push(`${key}: ${childResult}`);
1486
+ }
1487
+ });
1488
+ return `{${properties.join(", ")}}`;
1489
+ }
1490
+
1491
+ /**
1492
+ * Compiles a Constant Binding Expression.
1493
+ * @param expr
1494
+ * @param embeddedInBinding
1495
+ * @param isNullable
1496
+ * @returns {string} The compiled string
1497
+ */
1498
+ function compileConstant<T extends PrimitiveType>(expr: ConstantExpression<T>, embeddedInBinding: boolean, isNullable = false) {
1499
+ if (expr.value === null) {
1500
+ return "null";
1501
+ }
1502
+ if (expr.value === undefined) {
1503
+ return "undefined";
1504
+ }
1505
+ if (typeof expr.value === "object") {
1506
+ if (Array.isArray(expr.value)) {
1507
+ const entries = expr.value.map(expression => compileBinding(expression, true));
1508
+ return `[${entries.join(", ")}]`;
1509
+ } else {
1510
+ return compileConstantObject(expr as ConstantExpression<object>, isNullable);
1511
+ }
1512
+ }
1513
+
1514
+ if (embeddedInBinding) {
1515
+ switch (typeof expr.value) {
1516
+ case "number":
1517
+ case "bigint":
1518
+ case "boolean":
1519
+ return expr.value.toString();
1520
+ case "string":
1521
+ return `'${escapeXmlAttribute(expr.value.toString())}'`;
1522
+ default:
1523
+ return "";
1524
+ }
1525
+ } else {
1526
+ return expr.value.toString();
1527
+ }
1528
+ }
1529
+
1530
+ /**
1531
+ * Generates the binding string for a Binding expression.
1532
+ *
1533
+ * @param expressionForBinding The expression to compile
1534
+ * @param embeddedInBinding Whether the expression to compile is embedded into another expression
1535
+ * @param embeddedSeparator The binding value evaluator ($ or % depending on whether we want to force the type or not)
1536
+ * @returns {BindingExpression<T>} The corresponding expression binding
1537
+ */
1538
+ function compileBindingForBinding<T extends PrimitiveType>(
1539
+ expressionForBinding: BindingExpressionExpression<T> | DefaultBindingExpressionExpression<T>,
1540
+ embeddedInBinding: boolean,
1541
+ embeddedSeparator: string
1542
+ ) {
1543
+ if (
1544
+ expressionForBinding.type ||
1545
+ expressionForBinding.parameters ||
1546
+ expressionForBinding.targetType ||
1547
+ expressionForBinding.formatOptions ||
1548
+ expressionForBinding.constraints
1549
+ ) {
1550
+ // This is now a complex binding definition, let's prepare for it
1551
+ const complexBindingDefinition = {
1552
+ path: compileBindingPath(expressionForBinding),
1553
+ type: expressionForBinding.type,
1554
+ targetType: expressionForBinding.targetType,
1555
+ parameters: expressionForBinding.parameters,
1556
+ formatOptions: expressionForBinding.formatOptions,
1557
+ constraints: expressionForBinding.constraints
1558
+ };
1559
+ let outBinding = compileBinding(complexBindingDefinition);
1560
+ if (embeddedInBinding) {
1561
+ outBinding = `${embeddedSeparator}` + outBinding;
1562
+ }
1563
+ return outBinding;
1564
+ } else if (embeddedInBinding) {
1565
+ return `${embeddedSeparator}{${compileBindingPath(expressionForBinding)}}`;
1566
+ } else {
1567
+ return `{${compileBindingPath(expressionForBinding)}}`;
1568
+ }
1569
+ }
1570
+
1571
+ function compileComplexTypeExpression<T extends PrimitiveType>(expression: ComplexTypeExpression<T>) {
1572
+ if (expression.bindingParameters.length === 1) {
1573
+ return `{${compilePathParameter(expression.bindingParameters[0], true)}, type: '${expression.type}'}`;
1574
+ }
1575
+
1576
+ let outputEnd;
1577
+ // this code is based on sap.ui.model.odata.v4._AnnotationHelperExpression.fetchCurrencyOrUnit
1578
+ switch (expression.type) {
1579
+ case "sap.ui.model.odata.type.Unit":
1580
+ outputEnd = `,{mode:'OneTime',path:'/##@@requestUnitsOfMeasure',targetType:'any'}],type:'sap.ui.model.odata.type.Unit'`;
1581
+ break;
1582
+ case "sap.ui.model.odata.type.Currency":
1583
+ outputEnd = `,{mode:'OneTime',path:'/##@@requestCurrencyCodes',targetType:'any'}],type:'sap.ui.model.odata.type.Currency'`;
1584
+ break;
1585
+ default:
1586
+ outputEnd = `], type: '${expression.type}'`;
1587
+ }
1588
+ if (hasElements(expression.formatOptions)) {
1589
+ outputEnd += `, formatOptions: ${compileBinding(expression.formatOptions)}`;
1590
+ }
1591
+ if (hasElements(expression.parameters)) {
1592
+ outputEnd += `, parameters: ${compileBinding(expression.parameters)}`;
1593
+ }
1594
+ outputEnd += "}";
1595
+
1596
+ return `{mode:'TwoWay', parts:[${expression.bindingParameters.map((param: any) => compilePathParameter(param)).join(",")}${outputEnd}`;
1597
+ }
1598
+
1599
+ /**
1600
+ * Wrap the compiled binding string as required dependening on its context.
1601
+ *
1602
+ * @param expression The compiled expression
1603
+ * @param embeddedInBinding True if the compiled expression is to be embedded in a binding
1604
+ * @param parenthesisRequired True if the embedded binding needs to be wrapped in parethesis so that it is evaluated as one
1605
+ * @returns {BindingExpression<string>} Finalized compiled expression
1606
+ */
1607
+ function wrapBindingExpression(expression: string, embeddedInBinding: boolean, parenthesisRequired = false): BindingExpression<string> {
1608
+ if (embeddedInBinding) {
1609
+ if (parenthesisRequired) {
1610
+ return `(${expression})`;
1611
+ } else {
1612
+ return expression;
1613
+ }
1614
+ } else {
1615
+ return `{= ${expression}}`;
1616
+ }
1617
+ }
1618
+
1522
1619
  /**
1523
1620
  * Compile an expression into an expression binding.
1524
1621
  *
@@ -1526,55 +1623,24 @@ const needParenthesis = function<T extends PrimitiveType>(expr: ExpressionOrPrim
1526
1623
  * @param expression The expression to compile
1527
1624
  * @param embeddedInBinding Whether the expression to compile is embedded into another expression
1528
1625
  * @param keepTargetType Keep the target type of the embedded bindings instead of casting them to any
1626
+ * @param isNullable Whether binding expression can resolve to empty string or not
1529
1627
  * @returns {BindingExpression<T>} The corresponding expression binding
1530
1628
  */
1531
1629
  export function compileBinding<T extends PrimitiveType>(
1532
1630
  expression: ExpressionOrPrimitive<T>,
1533
- embeddedInBinding: boolean = false,
1534
- keepTargetType: boolean = false
1631
+ embeddedInBinding = false,
1632
+ keepTargetType = false,
1633
+ isNullable = false
1535
1634
  ): BindingExpression<string> {
1536
1635
  const expr = wrapPrimitive(expression);
1537
1636
  const embeddedSeparator = keepTargetType ? "$" : "%";
1538
- let outProperty = "";
1637
+
1539
1638
  switch (expr._type) {
1540
1639
  case "Unresolveable":
1541
1640
  return undefined;
1542
- case "Constant":
1543
- if (expr.value === null) {
1544
- return "null";
1545
- }
1546
- if (expr.value === undefined) {
1547
- return "undefined";
1548
- }
1549
- if (typeof expr.value === "object") {
1550
- if (Array.isArray(expr.value)) {
1551
- const entries = expr.value.map(expression => compileBinding(expression, true));
1552
- return `[${entries.join(", ")}]`;
1553
- } else {
1554
- // Objects
1555
- const o = expr.value as PlainExpressionObject;
1556
- const properties = Object.keys(o).map(key => {
1557
- const value = o[key];
1558
- return `${key}: ${compileBinding(value, true)}`;
1559
- });
1560
- return `{${properties.join(", ")}}`;
1561
- }
1562
- }
1563
1641
 
1564
- if (embeddedInBinding) {
1565
- switch (typeof expr.value) {
1566
- case "number":
1567
- case "bigint":
1568
- case "boolean":
1569
- return expr.value.toString();
1570
- case "string":
1571
- return `'${escapeXmlAttribute(expr.value.toString())}'`;
1572
- default:
1573
- return "";
1574
- }
1575
- } else {
1576
- return expr.value.toString();
1577
- }
1642
+ case "Constant":
1643
+ return compileConstant(expr, embeddedInBinding, isNullable);
1578
1644
 
1579
1645
  case "Ref":
1580
1646
  return expr.ref || "null";
@@ -1584,164 +1650,91 @@ export function compileBinding<T extends PrimitiveType>(
1584
1650
  return expr.obj === undefined
1585
1651
  ? `${expr.fn}(${argumentString})`
1586
1652
  : `${compileBinding(expr.obj, true)}.${expr.fn}(${argumentString})`;
1653
+
1587
1654
  case "EmbeddedExpressionBinding":
1588
- if (embeddedInBinding) {
1589
- return `(${expr.value.substr(2, expr.value.length - 3)})`;
1590
- } else {
1591
- return `${expr.value}`;
1592
- }
1655
+ return embeddedInBinding ? `(${expr.value.substr(2, expr.value.length - 3)})` : `${expr.value}`;
1656
+
1593
1657
  case "EmbeddedBinding":
1594
- if (embeddedInBinding) {
1595
- return `${embeddedSeparator}${expr.value}`;
1596
- } else {
1597
- return `${expr.value}`;
1598
- }
1658
+ return embeddedInBinding ? `${embeddedSeparator}${expr.value}` : `${expr.value}`;
1659
+
1599
1660
  case "DefaultBinding":
1600
1661
  case "Binding":
1601
- if (expr.type || expr.parameters || expr.targetType) {
1602
- let outBinding = "";
1603
- if (embeddedInBinding) {
1604
- outBinding += `${embeddedSeparator}`;
1605
- }
1606
- outBinding += `{path:'${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}'`;
1607
- if (expr.type) {
1608
- outBinding += `, type: '${expr.type}'`;
1609
- }
1610
- if (expr.constraints && Object.keys(expr.constraints).length > 0) {
1611
- outBinding += `, constraints: ${compileBinding(expr.constraints)}`;
1612
- }
1613
- if (expr.formatOptions) {
1614
- outBinding += `, formatOptions: ${compileBinding(expr.formatOptions)}`;
1615
- }
1616
- if (expr.parameters && Object.keys(expr.parameters).length > 0) {
1617
- outBinding += `, parameters: ${compileBinding(expr.parameters)}`;
1618
- }
1619
- if (expr.targetType) {
1620
- outBinding += `, targetType: '${expr.targetType}'`;
1621
- }
1622
- outBinding += "}";
1623
- return outBinding;
1624
- } else {
1625
- if (embeddedInBinding) {
1626
- return `${embeddedSeparator}{${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}}`;
1627
- } else {
1628
- return `{${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}}`;
1629
- }
1630
- }
1662
+ return compileBindingForBinding(expr, embeddedInBinding, embeddedSeparator);
1631
1663
 
1632
1664
  case "Comparison":
1633
- const isOperand1Complex = needParenthesis(expr.operand1);
1634
- const isOperand2Complex = needParenthesis(expr.operand2);
1635
- let comparisonPart = "";
1636
- if (isOperand1Complex) {
1637
- comparisonPart += "(";
1638
- }
1639
- comparisonPart += `${compileBinding(expr.operand1, true)}`;
1640
- if (isOperand1Complex) {
1641
- comparisonPart += ")";
1642
- }
1643
- comparisonPart += ` ${expr.operator} `;
1644
- if (isOperand2Complex) {
1645
- comparisonPart += "(";
1646
- }
1647
- comparisonPart += `${compileBinding(expr.operand2, true)}`;
1648
- if (isOperand2Complex) {
1649
- comparisonPart += ")";
1650
- }
1651
- if (embeddedInBinding) {
1652
- return comparisonPart;
1653
- }
1654
- return `{= ${comparisonPart}}`;
1665
+ const comparisonExpression = compileComparisonExpression(expr);
1666
+ return wrapBindingExpression(comparisonExpression, embeddedInBinding);
1655
1667
 
1656
1668
  case "IfElse":
1657
- if (embeddedInBinding) {
1658
- return `(${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1659
- expr.onFalse,
1660
- true
1661
- )})`;
1662
- } else {
1663
- return `{= ${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1664
- expr.onFalse,
1665
- true
1666
- )}}`;
1667
- }
1669
+ const ifElseExpression = `${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1670
+ expr.onFalse,
1671
+ true
1672
+ )}`;
1673
+ return wrapBindingExpression(ifElseExpression, embeddedInBinding, true);
1668
1674
 
1669
1675
  case "Set":
1670
- if (embeddedInBinding) {
1671
- return `(${expr.operands.map(expression => compileBinding(expression, true)).join(` ${expr.operator} `)})`;
1672
- } else {
1673
- return `{= (${expr.operands.map(expression => compileBinding(expression, true)).join(` ${expr.operator} `)})}`;
1674
- }
1676
+ const setExpression = expr.operands.map(operand => compileBinding(operand, true)).join(` ${expr.operator} `);
1677
+ return wrapBindingExpression(setExpression, embeddedInBinding, true);
1675
1678
 
1676
1679
  case "Concat":
1677
- if (embeddedInBinding) {
1678
- return `${expr.expressions.map(expression => compileBinding(expression, true, true)).join(` + `)}`;
1679
- } else {
1680
- return `{= ${expr.expressions.map(expression => compileBinding(expression, true, true)).join(` + `)} }`;
1681
- }
1680
+ const concatExpression = expr.expressions.map(nestedExpression => compileBinding(nestedExpression, true, true)).join(" + ");
1681
+ return wrapBindingExpression(concatExpression, embeddedInBinding);
1682
+
1683
+ case "Length":
1684
+ const lengthExpression = `${compileBinding(expr.bindingExpression, true)}.length`;
1685
+ return wrapBindingExpression(lengthExpression, embeddedInBinding);
1682
1686
 
1683
1687
  case "Not":
1684
- if (embeddedInBinding) {
1685
- return `!${compileBinding(expr.operand, true)}`;
1686
- } else {
1687
- return `{= !${compileBinding(expr.operand, true)}}`;
1688
- }
1688
+ const notExpression = `!${compileBinding(expr.operand, true)}`;
1689
+ return wrapBindingExpression(notExpression, embeddedInBinding);
1689
1690
 
1690
1691
  case "Truthy":
1691
- if (embeddedInBinding) {
1692
- return `!!${compileBinding(expr.operand, true)}`;
1693
- } else {
1694
- return `{= !!${compileBinding(expr.operand, true)}}`;
1695
- }
1692
+ const truthyExpression = `!!${compileBinding(expr.operand, true)}`;
1693
+ return wrapBindingExpression(truthyExpression, embeddedInBinding);
1696
1694
 
1697
1695
  case "Formatter":
1698
- if (expr.parameters.length === 1) {
1699
- outProperty += `{${compilePathParameter(expr.parameters[0], true)}, formatter: '${expr.fn}'}`;
1700
- } else {
1701
- outProperty += `{parts:[${expr.parameters.map((param: any) => compilePathParameter(param)).join(",")}], formatter: '${
1702
- expr.fn
1703
- }'}`;
1704
- }
1705
- if (embeddedInBinding) {
1706
- outProperty = `\$${outProperty}`;
1707
- }
1708
- return outProperty;
1696
+ const formatterExpression = compileFormatterExpression(expr);
1697
+ return embeddedInBinding ? `\$${formatterExpression}` : formatterExpression;
1698
+
1709
1699
  case "ComplexType":
1710
- if (expr.bindingParameters.length === 1) {
1711
- outProperty += `{${compilePathParameter(expr.bindingParameters[0], true)}, type: '${expr.type}'}`;
1712
- } else {
1713
- let outputEnd;
1714
- // this code is based on sap.ui.model.odata.v4._AnnotationHelperExpression.fetchCurrencyOrUnit
1715
- switch (expr.type) {
1716
- case "sap.ui.model.odata.type.Unit":
1717
- outputEnd = `,{mode:'OneTime',path:'/##@@requestUnitsOfMeasure',targetType:'any'}],type:'sap.ui.model.odata.type.Unit'`;
1718
- break;
1719
- case "sap.ui.model.odata.type.Currency":
1720
- outputEnd = `,{mode:'OneTime',path:'/##@@requestCurrencyCodes',targetType:'any'}],type:'sap.ui.model.odata.type.Currency'`;
1721
- break;
1722
- default:
1723
- outputEnd = `], type: '${expr.type}'`;
1724
- }
1725
- if (expr.formatOptions && Object.keys(expr.formatOptions).length > 0) {
1726
- outputEnd += `, formatOptions: ${compileBinding(expr.formatOptions)}`;
1727
- }
1728
- if (expr.parameters && Object.keys(expr.parameters).length > 0) {
1729
- outputEnd += `, parameters: ${compileBinding(expr.parameters)}`;
1730
- }
1731
- outputEnd += "}";
1732
- outProperty += `{mode:'TwoWay', parts:[${expr.bindingParameters
1733
- .map((param: any) => compilePathParameter(param))
1734
- .join(",")}${outputEnd}`;
1735
- }
1736
- if (embeddedInBinding) {
1737
- outProperty = `\$${outProperty}`;
1738
- }
1739
- return outProperty;
1700
+ const complexTypeExpression = compileComplexTypeExpression(expr);
1701
+ return embeddedInBinding ? `\$${complexTypeExpression}` : complexTypeExpression;
1702
+
1740
1703
  default:
1741
1704
  return "";
1742
1705
  }
1743
1706
  }
1744
1707
 
1708
+ /**
1709
+ * Compile a comparison expression.
1710
+ *
1711
+ * @param expression The comparison expression.
1712
+ * @returns The compiled expression. Needs wrapping before it can be used as an expression binding.
1713
+ */
1714
+ function compileComparisonExpression(expression: ComparisonExpression) {
1715
+ function compileOperand(operand: Expression<any>) {
1716
+ const compiledOperand = compileBinding(operand, true) ?? "undefined";
1717
+ return wrapBindingExpression(compiledOperand, true, needParenthesis(operand));
1718
+ }
1719
+
1720
+ return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
1721
+ }
1722
+
1723
+ /**
1724
+ * Compile a formatter expression.
1725
+ *
1726
+ * @param expression The formatter expression.
1727
+ * @returns The compiled expression.
1728
+ */
1729
+ function compileFormatterExpression<T extends PrimitiveType>(expression: FormatterExpression<T>) {
1730
+ if (expression.parameters.length === 1) {
1731
+ return `{${compilePathParameter(expression.parameters[0], true)}, formatter: '${expression.fn}'}`;
1732
+ } else {
1733
+ const parts = expression.parameters.map(param => compilePathParameter(param));
1734
+ return `{parts: [${parts.join(", ")}], formatter: '${expression.fn}'}`;
1735
+ }
1736
+ }
1737
+
1745
1738
  /**
1746
1739
  * Compile the path parameter of a formatter call.
1747
1740
  *
@@ -1749,53 +1742,54 @@ export function compileBinding<T extends PrimitiveType>(
1749
1742
  * @param singlePath Whether there is one or multiple path to consider
1750
1743
  * @returns {string} The string snippet to include in the overall binding definition
1751
1744
  */
1752
- function compilePathParameter(expression: Expression<any>, singlePath: boolean = false): string {
1745
+ function compilePathParameter(expression: Expression<any>, singlePath = false): string {
1753
1746
  let outValue = "";
1754
- switch (expression._type) {
1755
- case "Constant":
1756
- switch (typeof expression.value) {
1757
- case "number":
1758
- case "bigint":
1759
- outValue = `value: ${expression.value.toString()}`;
1760
- break;
1761
- case "string":
1762
- outValue = `value: '${escapeXmlAttribute(expression.value.toString())}'`;
1763
- break;
1764
- case "boolean":
1765
- outValue = `value: '${expression.value.toString()}'`;
1766
- break;
1767
- default:
1768
- outValue = "value: ''";
1769
- break;
1770
- }
1771
- if (singlePath) {
1772
- return outValue;
1773
- }
1774
- return `{${outValue}}`;
1747
+ if (expression._type === "Constant") {
1748
+ switch (typeof expression.value) {
1749
+ case "number":
1750
+ case "bigint":
1751
+ outValue = `value: ${expression.value.toString()}`;
1752
+ break;
1753
+ case "string":
1754
+ outValue = `value: '${escapeXmlAttribute(expression.value.toString())}'`;
1755
+ break;
1756
+ case "boolean":
1757
+ outValue = `value: '${expression.value.toString()}'`;
1758
+ break;
1759
+ default:
1760
+ outValue = "value: ''";
1761
+ break;
1762
+ }
1763
+ } else if (expression._type === "DefaultBinding" || expression._type === "Binding") {
1764
+ outValue = `path: '${compileBindingPath(expression)}'`;
1775
1765
 
1776
- case "DefaultBinding":
1777
- case "Binding":
1778
- outValue = `path:'${expression.modelName ? `${expression.modelName}>` : ""}${expression.path}'`;
1766
+ outValue += expression.type ? `, type: '${expression.type}'` : `, targetType: 'any'`;
1779
1767
 
1780
- if (expression.type) {
1781
- outValue += `, type : '${expression.type}'`;
1782
- } else {
1783
- outValue += `, targetType : 'any'`;
1784
- }
1785
- if (expression.constraints && Object.keys(expression.constraints).length > 0) {
1786
- outValue += `, constraints: ${compileBinding(expression.constraints)}`;
1787
- }
1788
- if (expression.formatOptions && Object.keys(expression.formatOptions).length > 0) {
1789
- outValue += `, formatOptions: ${compileBinding(expression.formatOptions)}`;
1790
- }
1791
- if (expression.parameters && Object.keys(expression.parameters).length > 0) {
1792
- outValue += `, parameters: ${compileBinding(expression.parameters)}`;
1793
- }
1794
- if (singlePath) {
1795
- return outValue;
1796
- }
1797
- return `{${outValue}}`;
1798
- default:
1799
- return "";
1768
+ if (hasElements(expression.constraints)) {
1769
+ outValue += `, constraints: ${compileBinding(expression.constraints)}`;
1770
+ }
1771
+ if (hasElements(expression.formatOptions)) {
1772
+ outValue += `, formatOptions: ${compileBinding(expression.formatOptions)}`;
1773
+ }
1774
+ if (hasElements(expression.parameters)) {
1775
+ outValue += `, parameters: ${compileBinding(expression.parameters)}`;
1776
+ }
1777
+ } else {
1778
+ return "";
1800
1779
  }
1780
+ return singlePath ? outValue : `{${outValue}}`;
1781
+ }
1782
+
1783
+ function hasElements(obj: any) {
1784
+ return obj && Object.keys(obj).length > 0;
1785
+ }
1786
+
1787
+ /**
1788
+ * Compile a binding expression path.
1789
+ *
1790
+ * @param expression The expression to compile.
1791
+ * @returns The compiled path.
1792
+ */
1793
+ function compileBindingPath<T extends PrimitiveType>(expression: BindingExpressionExpression<T> | DefaultBindingExpressionExpression<T>) {
1794
+ return `${expression.modelName ? expression.modelName + ">" : ""}${expression.path}`;
1801
1795
  }