@sapui5/sap.fe.core 1.97.0 → 1.99.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/package.json +5 -4
  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 +6 -3
  6. package/src/sap/fe/core/AppStateHandler.js +229 -181
  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 -2379
  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 -807
  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 +64 -0
  24. package/src/sap/fe/core/TransactionHelper.js +1576 -1579
  25. package/src/sap/fe/core/TransactionHelper.ts +1706 -0
  26. package/src/sap/fe/core/actions/draft.js +560 -581
  27. package/src/sap/fe/core/actions/draft.ts +594 -0
  28. package/src/sap/fe/core/actions/messageHandling.js +545 -511
  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 -1211
  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 +229 -260
  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 +27 -23
  41. package/src/sap/fe/core/controllerextensions/InternalRouting.js +82 -46
  42. package/src/sap/fe/core/controllerextensions/KPIManagement.js +113 -25
  43. package/src/sap/fe/core/controllerextensions/KPIManagement.ts +135 -44
  44. package/src/sap/fe/core/controllerextensions/MassEdit.js +172 -0
  45. package/src/sap/fe/core/controllerextensions/MessageHandler.js +22 -9
  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 +22 -8
  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 +3 -2
  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 +3 -2
  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 +1 -1
  75. package/src/sap/fe/core/converters/ManifestSettings.ts +4 -0
  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 +67 -6
  79. package/src/sap/fe/core/converters/MetaModelConverter.ts +70 -14
  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 +33 -21
  85. package/src/sap/fe/core/converters/controls/Common/Action.js +11 -2
  86. package/src/sap/fe/core/converters/controls/Common/Action.ts +8 -2
  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 +20 -10
  92. package/src/sap/fe/core/converters/controls/Common/Form.ts +24 -4
  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 +390 -398
  96. package/src/sap/fe/core/converters/controls/Common/Table.ts +497 -484
  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 +17 -9
  104. package/src/sap/fe/core/converters/controls/ObjectPage/SubSection.ts +24 -9
  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/IssueManager.js +7 -1
  108. package/src/sap/fe/core/converters/helpers/IssueManager.ts +6 -0
  109. package/src/sap/fe/core/converters/objectPage/HeaderAndFooterAction.js +4 -4
  110. package/src/sap/fe/core/converters/objectPage/HeaderAndFooterAction.ts +3 -3
  111. package/src/sap/fe/core/converters/templates/ListReportConverter.js +13 -6
  112. package/src/sap/fe/core/converters/templates/ListReportConverter.ts +26 -15
  113. package/src/sap/fe/core/designtime/AppComponent.designtime.js +11 -2
  114. package/src/sap/fe/core/formatters/CriticalityFormatter.js +1 -1
  115. package/src/sap/fe/core/formatters/CriticalityFormatter.ts +1 -1
  116. package/src/sap/fe/core/formatters/FPMFormatter.js +1 -1
  117. package/src/sap/fe/core/formatters/FPMFormatter.ts +4 -10
  118. package/src/sap/fe/core/formatters/KPIFormatter.js +1 -1
  119. package/src/sap/fe/core/formatters/KPIFormatter.ts +3 -1
  120. package/src/sap/fe/core/formatters/TableFormatter.js +39 -28
  121. package/src/sap/fe/core/formatters/TableFormatter.ts +43 -28
  122. package/src/sap/fe/core/formatters/ValueFormatter.js +30 -5
  123. package/src/sap/fe/core/formatters/ValueFormatter.ts +30 -7
  124. package/src/sap/fe/core/fpm/Component.js +3 -2
  125. package/src/sap/fe/core/helpers/AppStartupHelper.js +359 -0
  126. package/src/sap/fe/core/helpers/AppStartupHelper.ts +391 -0
  127. package/src/sap/fe/core/helpers/BindingExpression.js +484 -437
  128. package/src/sap/fe/core/helpers/BindingExpression.ts +480 -463
  129. package/src/sap/fe/core/helpers/ClassSupport.js +27 -15
  130. package/src/sap/fe/core/helpers/ClassSupport.ts +31 -20
  131. package/src/sap/fe/core/helpers/DynamicAnnotationPathHelper.js +63 -59
  132. package/src/sap/fe/core/helpers/DynamicAnnotationPathHelper.ts +56 -0
  133. package/src/sap/fe/core/helpers/EditState.js +81 -84
  134. package/src/sap/fe/core/helpers/EditState.ts +81 -0
  135. package/src/sap/fe/core/helpers/ExcelFormatHelper.js +62 -48
  136. package/src/sap/fe/core/helpers/ExcelFormatHelper.ts +49 -0
  137. package/src/sap/fe/core/helpers/FPMHelper.js +52 -56
  138. package/src/sap/fe/core/helpers/FPMHelper.ts +62 -0
  139. package/src/sap/fe/core/helpers/KeepAliveHelper.js +4 -5
  140. package/src/sap/fe/core/helpers/KeepAliveHelper.ts +10 -10
  141. package/src/sap/fe/core/helpers/MassEditHelper.js +687 -0
  142. package/src/sap/fe/core/helpers/ModelHelper.js +229 -225
  143. package/src/sap/fe/core/helpers/ModelHelper.ts +227 -0
  144. package/src/sap/fe/core/helpers/PasteHelper.js +210 -132
  145. package/src/sap/fe/core/helpers/PasteHelper.ts +196 -0
  146. package/src/sap/fe/core/helpers/SemanticDateOperators.js +332 -313
  147. package/src/sap/fe/core/helpers/SemanticDateOperators.ts +330 -0
  148. package/src/sap/fe/core/helpers/SemanticKeyHelper.js +66 -67
  149. package/src/sap/fe/core/helpers/SemanticKeyHelper.ts +73 -0
  150. package/src/sap/fe/core/helpers/StableIdHelper.js +4 -7
  151. package/src/sap/fe/core/helpers/StableIdHelper.ts +2 -6
  152. package/src/sap/fe/core/jsx-runtime/jsx.js +1 -1
  153. package/src/sap/fe/core/jsx-runtime/jsx.ts +1 -1
  154. package/src/sap/fe/core/library.js +30 -3
  155. package/src/sap/fe/core/library.support.js +12 -5
  156. package/src/sap/fe/core/manifestMerger/ChangePageConfiguration.js +62 -0
  157. package/src/sap/fe/core/manifestMerger/ChangePageConfiguration.ts +66 -0
  158. package/src/sap/fe/core/messagebundle.properties +61 -13
  159. package/src/sap/fe/core/messagebundle_ar.properties +33 -2
  160. package/src/sap/fe/core/messagebundle_bg.properties +33 -2
  161. package/src/sap/fe/core/messagebundle_ca.properties +33 -2
  162. package/src/sap/fe/core/messagebundle_cs.properties +34 -3
  163. package/src/sap/fe/core/messagebundle_cy.properties +33 -2
  164. package/src/sap/fe/core/messagebundle_da.properties +33 -2
  165. package/src/sap/fe/core/messagebundle_de.properties +33 -2
  166. package/src/sap/fe/core/messagebundle_el.properties +33 -2
  167. package/src/sap/fe/core/messagebundle_en.properties +32 -1
  168. package/src/sap/fe/core/messagebundle_en_GB.properties +32 -1
  169. package/src/sap/fe/core/messagebundle_en_US_sappsd.properties +36 -1
  170. package/src/sap/fe/core/messagebundle_en_US_saprigi.properties +33 -2
  171. package/src/sap/fe/core/messagebundle_en_US_saptrc.properties +33 -2
  172. package/src/sap/fe/core/messagebundle_es.properties +33 -2
  173. package/src/sap/fe/core/messagebundle_es_MX.properties +33 -2
  174. package/src/sap/fe/core/messagebundle_et.properties +33 -2
  175. package/src/sap/fe/core/messagebundle_fi.properties +34 -3
  176. package/src/sap/fe/core/messagebundle_fr.properties +38 -7
  177. package/src/sap/fe/core/messagebundle_fr_CA.properties +33 -2
  178. package/src/sap/fe/core/messagebundle_hi.properties +33 -2
  179. package/src/sap/fe/core/messagebundle_hr.properties +33 -2
  180. package/src/sap/fe/core/messagebundle_hu.properties +34 -3
  181. package/src/sap/fe/core/messagebundle_id.properties +36 -5
  182. package/src/sap/fe/core/messagebundle_it.properties +33 -2
  183. package/src/sap/fe/core/messagebundle_iw.properties +33 -2
  184. package/src/sap/fe/core/messagebundle_ja.properties +33 -2
  185. package/src/sap/fe/core/messagebundle_kk.properties +33 -2
  186. package/src/sap/fe/core/messagebundle_ko.properties +33 -2
  187. package/src/sap/fe/core/messagebundle_lt.properties +33 -2
  188. package/src/sap/fe/core/messagebundle_lv.properties +33 -2
  189. package/src/sap/fe/core/messagebundle_ms.properties +33 -2
  190. package/src/sap/fe/core/messagebundle_nl.properties +33 -2
  191. package/src/sap/fe/core/messagebundle_no.properties +33 -2
  192. package/src/sap/fe/core/messagebundle_pl.properties +33 -2
  193. package/src/sap/fe/core/messagebundle_pt.properties +34 -3
  194. package/src/sap/fe/core/messagebundle_pt_PT.properties +33 -2
  195. package/src/sap/fe/core/messagebundle_ro.properties +33 -2
  196. package/src/sap/fe/core/messagebundle_ru.properties +33 -2
  197. package/src/sap/fe/core/messagebundle_sh.properties +33 -2
  198. package/src/sap/fe/core/messagebundle_sk.properties +33 -2
  199. package/src/sap/fe/core/messagebundle_sl.properties +33 -2
  200. package/src/sap/fe/core/messagebundle_sv.properties +34 -3
  201. package/src/sap/fe/core/messagebundle_th.properties +33 -2
  202. package/src/sap/fe/core/messagebundle_tr.properties +33 -2
  203. package/src/sap/fe/core/messagebundle_uk.properties +33 -2
  204. package/src/sap/fe/core/messagebundle_vi.properties +33 -2
  205. package/src/sap/fe/core/messagebundle_zh_CN.properties +33 -2
  206. package/src/sap/fe/core/messagebundle_zh_TW.properties +33 -2
  207. package/src/sap/fe/core/services/AsyncComponentServiceFactory.js +2 -2
  208. package/src/sap/fe/core/services/AsyncComponentServiceFactory.ts +3 -1
  209. package/src/sap/fe/core/services/CacheHandlerServiceFactory.js +269 -202
  210. package/src/sap/fe/core/services/CacheHandlerServiceFactory.ts +212 -0
  211. package/src/sap/fe/core/services/EnvironmentServiceFactory.js +4 -3
  212. package/src/sap/fe/core/services/EnvironmentServiceFactory.ts +9 -5
  213. package/src/sap/fe/core/services/NavigationServiceFactory.js +406 -300
  214. package/src/sap/fe/core/services/NavigationServiceFactory.ts +316 -0
  215. package/src/sap/fe/core/services/ResourceModelServiceFactory.js +149 -81
  216. package/src/sap/fe/core/services/ResourceModelServiceFactory.ts +80 -0
  217. package/src/sap/fe/core/services/RoutingServiceFactory.js +987 -1152
  218. package/src/sap/fe/core/services/RoutingServiceFactory.ts +898 -0
  219. package/src/sap/fe/core/services/ShellServicesFactory.js +31 -2
  220. package/src/sap/fe/core/services/ShellServicesFactory.ts +45 -11
  221. package/src/sap/fe/core/services/SideEffectsServiceFactory.js +42 -85
  222. package/src/sap/fe/core/services/SideEffectsServiceFactory.ts +57 -100
  223. package/src/sap/fe/core/services/TemplatedViewServiceFactory.js +461 -478
  224. package/src/sap/fe/core/services/TemplatedViewServiceFactory.ts +453 -0
  225. package/src/sap/fe/core/services/view/TemplatingErrorPage.controller.js +10 -8
  226. package/src/sap/fe/core/services/view/TemplatingErrorPage.controller.ts +8 -0
  227. package/src/sap/fe/core/support/AnnotationIssue.support.js +15 -3
  228. package/src/sap/fe/core/support/AnnotationIssue.support.ts +16 -2
  229. package/src/sap/fe/core/support/CollectionFacetUnsupportedLevel.support.js +2 -2
  230. package/src/sap/fe/core/support/CollectionFacetUnsupportedLevel.support.ts +1 -1
  231. package/src/sap/fe/core/support/InvalidAnnotationColumnKey.support.js +38 -0
  232. package/src/sap/fe/core/support/InvalidAnnotationColumnKey.support.ts +18 -0
  233. package/src/sap/fe/core/templating/DataModelPathHelper.js +10 -48
  234. package/src/sap/fe/core/templating/DataModelPathHelper.ts +14 -46
  235. package/src/sap/fe/core/templating/DisplayModeFormatter.js +114 -0
  236. package/src/sap/fe/core/templating/DisplayModeFormatter.ts +86 -0
  237. package/src/sap/fe/core/templating/FieldControlHelper.js +8 -8
  238. package/src/sap/fe/core/templating/FieldControlHelper.ts +25 -7
  239. package/src/sap/fe/core/templating/FilterHelper.js +139 -70
  240. package/src/sap/fe/core/templating/FilterHelper.ts +140 -70
  241. package/src/sap/fe/core/templating/PropertyHelper.js +2 -2
  242. package/src/sap/fe/core/templating/PropertyHelper.ts +1 -1
  243. package/src/sap/fe/core/templating/UIFormatters.js +45 -110
  244. package/src/sap/fe/core/templating/UIFormatters.ts +39 -75
  245. package/src/sap/fe/core/type/Email.js +1 -1
  246. package/src/sap/fe/core/type/Email.ts +4 -6
  247. package/ui5.yaml +0 -3
  248. package/src/sap/fe/core/controls/filterbar.d.js +0 -8
  249. package/src/sap/fe/core/controls/filterbar.d.ts +0 -0
@@ -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,29 +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 (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1017
- // ((a === c) === false) => !(a === c)
1018
- return not(leftExpression);
1019
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onTrue, rightExpression)) {
1020
- // (if(xxxx) { aaa } else { bbb } ) === aaa )
1021
- return or(leftExpression.condition, equal(leftExpression.onFalse, rightExpression));
1022
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)) {
1023
- return or(not(leftExpression.condition), equal(leftExpression.onTrue, rightExpression));
1024
- } else if (
1025
- leftExpression._type === "IfElse" &&
1026
- isConstant(leftExpression.onTrue) &&
1027
- isConstant(rightExpression) &&
1028
- isConstant(leftExpression.onFalse) &&
1029
- !_checkExpressionsAreEqual(leftExpression.onTrue, rightExpression) &&
1030
- !_checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)
1031
- ) {
1032
- 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;
1033
1033
  }
1034
1034
 
1035
- 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);
1036
1038
  }
1037
1039
 
1038
1040
  /**
@@ -1047,36 +1049,7 @@ export function notEqual<T extends PrimitiveType>(
1047
1049
  leftOperand: ExpressionOrPrimitive<T>,
1048
1050
  rightOperand: ExpressionOrPrimitive<T>
1049
1051
  ): Expression<boolean> {
1050
- const leftExpression = wrapPrimitive(leftOperand);
1051
- const rightExpression = wrapPrimitive(rightOperand);
1052
-
1053
- if (_checkExpressionsAreEqual(leftExpression, rightExpression)) {
1054
- return constant(false);
1055
- }
1056
-
1057
- // ((a === c) !== true) => !(a === c)
1058
- if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1059
- return not(leftExpression);
1060
- } else if (leftExpression._type === "Comparison" && isConstant(rightExpression) && rightExpression.value === true) {
1061
- // ((a === c) !== false) => (a === c)
1062
- return leftExpression;
1063
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onTrue, rightExpression)) {
1064
- return and(not(leftExpression.condition), notEqual(leftExpression.onFalse, rightExpression));
1065
- } else if (leftExpression._type === "IfElse" && _checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)) {
1066
- return and(leftExpression.condition, notEqual(leftExpression.onTrue, rightExpression));
1067
- } else if (
1068
- leftExpression._type === "IfElse" &&
1069
- isConstant(leftExpression.onTrue) &&
1070
- isConstant(rightExpression) &&
1071
- isConstant(leftExpression.onFalse) &&
1072
- !_checkExpressionsAreEqual(leftExpression.onTrue, rightExpression) &&
1073
- !_checkExpressionsAreEqual(leftExpression.onFalse, rightExpression)
1074
- ) {
1075
- // If the left expression is an if else where both onTrue and onFalse are not equals to the right expression -> simplify as true
1076
- return constant(true);
1077
- }
1078
-
1079
- return comparison("!==", leftExpression, rightExpression);
1052
+ return not(equal(leftOperand, rightOperand));
1080
1053
  }
1081
1054
 
1082
1055
  /**
@@ -1181,61 +1154,40 @@ export function ifElse<T extends PrimitiveType>(
1181
1154
  onFalseExpression = onFalseExpression.onFalse;
1182
1155
  }
1183
1156
 
1184
- // inline nested if-else expressions: condition
1185
- if (conditionExpression._type === "IfElse") {
1186
- if (
1187
- isConstant(conditionExpression.onFalse) &&
1188
- !conditionExpression.onFalse.value &&
1189
- isConstant(conditionExpression.onTrue) &&
1190
- conditionExpression.onTrue.value
1191
- ) {
1192
- // ifElse(ifElse(X, true, false), a, b) ==> ifElse(X, a, b)
1193
- conditionExpression = conditionExpression.condition;
1194
- } else if (
1195
- isConstant(conditionExpression.onFalse) &&
1196
- conditionExpression.onFalse.value &&
1197
- isConstant(conditionExpression.onTrue) &&
1198
- !conditionExpression.onTrue.value
1199
- ) {
1200
- // ifElse(ifElse(X, false, true), a, b) ==> ifElse(not(X), a, b)
1201
- conditionExpression = not(conditionExpression.condition);
1202
- } else if (
1203
- isConstant(conditionExpression.onTrue) &&
1204
- !conditionExpression.onTrue.value &&
1205
- !isConstant(conditionExpression.onFalse)
1206
- ) {
1207
- // ifElse(ifElse(X, false, a), b, c) ==> ifElse(and(not(X), a), b, c)
1208
- conditionExpression = and(not(conditionExpression.condition), conditionExpression.onFalse);
1209
- }
1210
- }
1211
-
1212
- // again swap branches if needed (in case one of the optimizations above led to a negated condition)
1213
- if (conditionExpression._type === "Not") {
1214
- // ifElse(not(X), a, b) --> ifElse(X, b, a)
1215
- [onTrueExpression, onFalseExpression] = [onFalseExpression, onTrueExpression];
1216
- conditionExpression = not(conditionExpression);
1217
- }
1218
-
1219
- // compute expression result for constant conditions
1157
+ // (if true then a else b) ~~> a
1158
+ // (if false then a else b) ~~> b
1220
1159
  if (isConstant(conditionExpression)) {
1221
1160
  return conditionExpression.value ? onTrueExpression : onFalseExpression;
1222
1161
  }
1223
1162
 
1224
- // 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
1225
1167
  if (_checkExpressionsAreEqual(onTrueExpression, onFalseExpression)) {
1226
1168
  return onTrueExpression;
1227
1169
  }
1228
1170
 
1229
- // If either trueExpression or falseExpression is a value equals to false the expression can be simplified
1230
- // If(Condition) Then XXX Else False -> Condition && XXX
1231
- if (isConstant(onFalseExpression) && onFalseExpression.value === false) {
1171
+ // if X then a else false ~~> X && a
1172
+ if (isFalse(onFalseExpression)) {
1232
1173
  return and(conditionExpression, onTrueExpression as Expression<boolean>) as Expression<T>;
1233
1174
  }
1234
- // If(Condition) Then False Else XXX -> !Condition && XXX
1235
- 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)) {
1236
1183
  return and(not(conditionExpression), onFalseExpression as Expression<boolean>) as Expression<T>;
1237
1184
  }
1238
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
+
1239
1191
  return {
1240
1192
  _type: "IfElse",
1241
1193
  condition: conditionExpression,
@@ -1386,17 +1338,17 @@ export function addTypeInformation<T, U extends Fn<T>>(
1386
1338
  /**
1387
1339
  * Function call, optionally with arguments.
1388
1340
  *
1389
- * @param fn Function name or reference to function
1341
+ * @param func Function name or reference to function
1390
1342
  * @param parameters Arguments
1391
1343
  * @param on Object to call the function on
1392
1344
  * @returns {FunctionExpression<T>} Expression representing the function call (not the result of the function call!)
1393
1345
  */
1394
1346
  export function fn<T, U extends FunctionOrName<T>>(
1395
- fn: U,
1347
+ func: U,
1396
1348
  parameters: WrappedTuple<FunctionParameters<T, U>>,
1397
1349
  on?: ExpressionOrPrimitive<object>
1398
1350
  ): FunctionExpression<T> {
1399
- const functionName = typeof fn === "string" ? fn : (fn as Fn<T>).__functionName;
1351
+ const functionName = typeof func === "string" ? func : (func as Fn<T>).__functionName;
1400
1352
  return {
1401
1353
  _type: "Function",
1402
1354
  obj: on !== undefined ? wrapPrimitive(on) : undefined,
@@ -1442,31 +1394,26 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1442
1394
  inExpression: Expression<T>,
1443
1395
  expressionType: ExpressionType,
1444
1396
  transformFunction: TransformFunction,
1445
- includeAllExpression: boolean = false
1397
+ includeAllExpression = false
1446
1398
  ): Expression<T> {
1447
1399
  let expression = inExpression;
1448
1400
  switch (expression._type) {
1449
1401
  case "Function":
1450
- expression.parameters = expression.parameters.map(expression =>
1451
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1402
+ case "Formatter":
1403
+ expression.parameters = expression.parameters.map(parameter =>
1404
+ transformRecursively(parameter, expressionType, transformFunction, includeAllExpression)
1452
1405
  );
1453
1406
  break;
1454
1407
  case "Concat":
1455
- expression.expressions = expression.expressions.map(expression =>
1456
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1408
+ expression.expressions = expression.expressions.map(subExpression =>
1409
+ transformRecursively(subExpression, expressionType, transformFunction, includeAllExpression)
1457
1410
  );
1458
1411
  break;
1459
1412
  case "ComplexType":
1460
- expression.bindingParameters = expression.bindingParameters.map(expression =>
1461
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1462
- );
1463
- break;
1464
- case "Formatter":
1465
- expression.parameters = expression.parameters.map(expression =>
1466
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1413
+ expression.bindingParameters = expression.bindingParameters.map(bindingParameter =>
1414
+ transformRecursively(bindingParameter, expressionType, transformFunction, includeAllExpression)
1467
1415
  );
1468
1416
  break;
1469
-
1470
1417
  case "IfElse":
1471
1418
  const onTrue = transformRecursively(expression.onTrue, expressionType, transformFunction, includeAllExpression);
1472
1419
  const onFalse = transformRecursively(expression.onFalse, expressionType, transformFunction, includeAllExpression);
@@ -1486,8 +1433,8 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1486
1433
  break;
1487
1434
  case "Set":
1488
1435
  if (includeAllExpression) {
1489
- expression.operands = expression.operands.map(expression =>
1490
- transformRecursively(expression, expressionType, transformFunction, includeAllExpression)
1436
+ expression.operands = expression.operands.map(operand =>
1437
+ transformRecursively(operand, expressionType, transformFunction, includeAllExpression)
1491
1438
  );
1492
1439
  }
1493
1440
  break;
@@ -1500,6 +1447,7 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1500
1447
  break;
1501
1448
  case "DefaultBinding":
1502
1449
  case "Ref":
1450
+ case "Length":
1503
1451
  case "Binding":
1504
1452
  case "Constant":
1505
1453
  // Do nothing
@@ -1513,6 +1461,161 @@ export function transformRecursively<T extends PrimitiveType | unknown>(
1513
1461
 
1514
1462
  export type BindingExpression<T> = T | string | undefined;
1515
1463
 
1464
+ const needParenthesis = function<T extends PrimitiveType>(expr: ExpressionOrPrimitive<T>): boolean {
1465
+ return !isConstant(expr) && !isBinding(expr) && isExpression(expr) && expr._type !== "IfElse" && expr._type !== "Function";
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
+
1516
1619
  /**
1517
1620
  * Compile an expression into an expression binding.
1518
1621
  *
@@ -1520,55 +1623,24 @@ export type BindingExpression<T> = T | string | undefined;
1520
1623
  * @param expression The expression to compile
1521
1624
  * @param embeddedInBinding Whether the expression to compile is embedded into another expression
1522
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
1523
1627
  * @returns {BindingExpression<T>} The corresponding expression binding
1524
1628
  */
1525
1629
  export function compileBinding<T extends PrimitiveType>(
1526
1630
  expression: ExpressionOrPrimitive<T>,
1527
- embeddedInBinding: boolean = false,
1528
- keepTargetType: boolean = false
1631
+ embeddedInBinding = false,
1632
+ keepTargetType = false,
1633
+ isNullable = false
1529
1634
  ): BindingExpression<string> {
1530
1635
  const expr = wrapPrimitive(expression);
1531
1636
  const embeddedSeparator = keepTargetType ? "$" : "%";
1532
- let outProperty = "";
1637
+
1533
1638
  switch (expr._type) {
1534
1639
  case "Unresolveable":
1535
1640
  return undefined;
1536
- case "Constant":
1537
- if (expr.value === null) {
1538
- return "null";
1539
- }
1540
- if (expr.value === undefined) {
1541
- return "undefined";
1542
- }
1543
- if (typeof expr.value === "object") {
1544
- if (Array.isArray(expr.value)) {
1545
- const entries = expr.value.map(expression => compileBinding(expression, true));
1546
- return `[${entries.join(", ")}]`;
1547
- } else {
1548
- // Objects
1549
- const o = expr.value as PlainExpressionObject;
1550
- const properties = Object.keys(o).map(key => {
1551
- const value = o[key];
1552
- return `${key}: ${compileBinding(value, true)}`;
1553
- });
1554
- return `{${properties.join(", ")}}`;
1555
- }
1556
- }
1557
1641
 
1558
- if (embeddedInBinding) {
1559
- switch (typeof expr.value) {
1560
- case "number":
1561
- case "bigint":
1562
- case "boolean":
1563
- return expr.value.toString();
1564
- case "string":
1565
- return `'${escapeXmlAttribute(expr.value.toString())}'`;
1566
- default:
1567
- return "";
1568
- }
1569
- } else {
1570
- return expr.value.toString();
1571
- }
1642
+ case "Constant":
1643
+ return compileConstant(expr, embeddedInBinding, isNullable);
1572
1644
 
1573
1645
  case "Ref":
1574
1646
  return expr.ref || "null";
@@ -1578,147 +1650,91 @@ export function compileBinding<T extends PrimitiveType>(
1578
1650
  return expr.obj === undefined
1579
1651
  ? `${expr.fn}(${argumentString})`
1580
1652
  : `${compileBinding(expr.obj, true)}.${expr.fn}(${argumentString})`;
1653
+
1581
1654
  case "EmbeddedExpressionBinding":
1582
- if (embeddedInBinding) {
1583
- return `(${expr.value.substr(2, expr.value.length - 3)})`;
1584
- } else {
1585
- return `${expr.value}`;
1586
- }
1655
+ return embeddedInBinding ? `(${expr.value.substr(2, expr.value.length - 3)})` : `${expr.value}`;
1656
+
1587
1657
  case "EmbeddedBinding":
1588
- if (embeddedInBinding) {
1589
- return `${embeddedSeparator}${expr.value}`;
1590
- } else {
1591
- return `${expr.value}`;
1592
- }
1658
+ return embeddedInBinding ? `${embeddedSeparator}${expr.value}` : `${expr.value}`;
1659
+
1593
1660
  case "DefaultBinding":
1594
1661
  case "Binding":
1595
- if (expr.type || expr.parameters || expr.targetType) {
1596
- let outBinding = "";
1597
- if (embeddedInBinding) {
1598
- outBinding += `${embeddedSeparator}`;
1599
- }
1600
- outBinding += `{path:'${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}'`;
1601
- if (expr.type) {
1602
- outBinding += `, type: '${expr.type}'`;
1603
- }
1604
- if (expr.constraints && Object.keys(expr.constraints).length > 0) {
1605
- outBinding += `, constraints: ${compileBinding(expr.constraints)}`;
1606
- }
1607
- if (expr.formatOptions) {
1608
- outBinding += `, formatOptions: ${compileBinding(expr.formatOptions)}`;
1609
- }
1610
- if (expr.parameters && Object.keys(expr.parameters).length > 0) {
1611
- outBinding += `, parameters: ${compileBinding(expr.parameters)}`;
1612
- }
1613
- if (expr.targetType) {
1614
- outBinding += `, targetType: '${expr.targetType}'`;
1615
- }
1616
- outBinding += "}";
1617
- return outBinding;
1618
- } else {
1619
- if (embeddedInBinding) {
1620
- return `${embeddedSeparator}{${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}}`;
1621
- } else {
1622
- return `{${expr.modelName ? `${expr.modelName}>` : ""}${expr.path}}`;
1623
- }
1624
- }
1662
+ return compileBindingForBinding(expr, embeddedInBinding, embeddedSeparator);
1625
1663
 
1626
1664
  case "Comparison":
1627
- const comparisonPart = `${compileBinding(expr.operand1, true)} ${expr.operator} ${compileBinding(expr.operand2, true)}`;
1628
- if (embeddedInBinding) {
1629
- return comparisonPart;
1630
- }
1631
- return `{= ${comparisonPart}}`;
1665
+ const comparisonExpression = compileComparisonExpression(expr);
1666
+ return wrapBindingExpression(comparisonExpression, embeddedInBinding);
1632
1667
 
1633
1668
  case "IfElse":
1634
- if (embeddedInBinding) {
1635
- return `(${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1636
- expr.onFalse,
1637
- true
1638
- )})`;
1639
- } else {
1640
- return `{= ${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1641
- expr.onFalse,
1642
- true
1643
- )}}`;
1644
- }
1669
+ const ifElseExpression = `${compileBinding(expr.condition, true)} ? ${compileBinding(expr.onTrue, true)} : ${compileBinding(
1670
+ expr.onFalse,
1671
+ true
1672
+ )}`;
1673
+ return wrapBindingExpression(ifElseExpression, embeddedInBinding, true);
1645
1674
 
1646
1675
  case "Set":
1647
- if (embeddedInBinding) {
1648
- return `(${expr.operands.map(expression => compileBinding(expression, true)).join(` ${expr.operator} `)})`;
1649
- } else {
1650
- return `{= (${expr.operands.map(expression => compileBinding(expression, true)).join(` ${expr.operator} `)})}`;
1651
- }
1676
+ const setExpression = expr.operands.map(operand => compileBinding(operand, true)).join(` ${expr.operator} `);
1677
+ return wrapBindingExpression(setExpression, embeddedInBinding, true);
1652
1678
 
1653
1679
  case "Concat":
1654
- if (embeddedInBinding) {
1655
- return `${expr.expressions.map(expression => compileBinding(expression, true, true)).join(` + `)}`;
1656
- } else {
1657
- return `{= ${expr.expressions.map(expression => compileBinding(expression, true, true)).join(` + `)} }`;
1658
- }
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);
1659
1686
 
1660
1687
  case "Not":
1661
- if (embeddedInBinding) {
1662
- return `!${compileBinding(expr.operand, true)}`;
1663
- } else {
1664
- return `{= !${compileBinding(expr.operand, true)}}`;
1665
- }
1688
+ const notExpression = `!${compileBinding(expr.operand, true)}`;
1689
+ return wrapBindingExpression(notExpression, embeddedInBinding);
1666
1690
 
1667
1691
  case "Truthy":
1668
- if (embeddedInBinding) {
1669
- return `!!${compileBinding(expr.operand, true)}`;
1670
- } else {
1671
- return `{= !!${compileBinding(expr.operand, true)}}`;
1672
- }
1692
+ const truthyExpression = `!!${compileBinding(expr.operand, true)}`;
1693
+ return wrapBindingExpression(truthyExpression, embeddedInBinding);
1673
1694
 
1674
1695
  case "Formatter":
1675
- if (expr.parameters.length === 1) {
1676
- outProperty += `{${compilePathParameter(expr.parameters[0], true)}, formatter: '${expr.fn}'}`;
1677
- } else {
1678
- outProperty += `{parts:[${expr.parameters.map((param: any) => compilePathParameter(param)).join(",")}], formatter: '${
1679
- expr.fn
1680
- }'}`;
1681
- }
1682
- if (embeddedInBinding) {
1683
- outProperty = `\$${outProperty}`;
1684
- }
1685
- return outProperty;
1696
+ const formatterExpression = compileFormatterExpression(expr);
1697
+ return embeddedInBinding ? `\$${formatterExpression}` : formatterExpression;
1698
+
1686
1699
  case "ComplexType":
1687
- if (expr.bindingParameters.length === 1) {
1688
- outProperty += `{${compilePathParameter(expr.bindingParameters[0], true)}, type: '${expr.type}'}`;
1689
- } else {
1690
- let outputEnd;
1691
- // this code is based on sap.ui.model.odata.v4._AnnotationHelperExpression.fetchCurrencyOrUnit
1692
- switch (expr.type) {
1693
- case "sap.ui.model.odata.type.Unit":
1694
- outputEnd = `,{mode:'OneTime',path:'/##@@requestUnitsOfMeasure',targetType:'any'}],type:'sap.ui.model.odata.type.Unit'`;
1695
- break;
1696
- case "sap.ui.model.odata.type.Currency":
1697
- outputEnd = `,{mode:'OneTime',path:'/##@@requestCurrencyCodes',targetType:'any'}],type:'sap.ui.model.odata.type.Currency'`;
1698
- break;
1699
- default:
1700
- outputEnd = `], type: '${expr.type}'`;
1701
- }
1702
- if (expr.formatOptions && Object.keys(expr.formatOptions).length > 0) {
1703
- outputEnd += `, formatOptions: ${compileBinding(expr.formatOptions)}`;
1704
- }
1705
- if (expr.parameters && Object.keys(expr.parameters).length > 0) {
1706
- outputEnd += `, parameters: ${compileBinding(expr.parameters)}`;
1707
- }
1708
- outputEnd += "}";
1709
- outProperty += `{mode:'TwoWay', parts:[${expr.bindingParameters
1710
- .map((param: any) => compilePathParameter(param))
1711
- .join(",")}${outputEnd}`;
1712
- }
1713
- if (embeddedInBinding) {
1714
- outProperty = `\$${outProperty}`;
1715
- }
1716
- return outProperty;
1700
+ const complexTypeExpression = compileComplexTypeExpression(expr);
1701
+ return embeddedInBinding ? `\$${complexTypeExpression}` : complexTypeExpression;
1702
+
1717
1703
  default:
1718
1704
  return "";
1719
1705
  }
1720
1706
  }
1721
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
+
1722
1738
  /**
1723
1739
  * Compile the path parameter of a formatter call.
1724
1740
  *
@@ -1726,53 +1742,54 @@ export function compileBinding<T extends PrimitiveType>(
1726
1742
  * @param singlePath Whether there is one or multiple path to consider
1727
1743
  * @returns {string} The string snippet to include in the overall binding definition
1728
1744
  */
1729
- function compilePathParameter(expression: Expression<any>, singlePath: boolean = false): string {
1745
+ function compilePathParameter(expression: Expression<any>, singlePath = false): string {
1730
1746
  let outValue = "";
1731
- switch (expression._type) {
1732
- case "Constant":
1733
- switch (typeof expression.value) {
1734
- case "number":
1735
- case "bigint":
1736
- outValue = `value: ${expression.value.toString()}`;
1737
- break;
1738
- case "string":
1739
- outValue = `value: '${escapeXmlAttribute(expression.value.toString())}'`;
1740
- break;
1741
- case "boolean":
1742
- outValue = `value: '${expression.value.toString()}'`;
1743
- break;
1744
- default:
1745
- outValue = "value: ''";
1746
- break;
1747
- }
1748
- if (singlePath) {
1749
- return outValue;
1750
- }
1751
- 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)}'`;
1752
1765
 
1753
- case "DefaultBinding":
1754
- case "Binding":
1755
- outValue = `path:'${expression.modelName ? `${expression.modelName}>` : ""}${expression.path}'`;
1766
+ outValue += expression.type ? `, type: '${expression.type}'` : `, targetType: 'any'`;
1756
1767
 
1757
- if (expression.type) {
1758
- outValue += `, type : '${expression.type}'`;
1759
- } else {
1760
- outValue += `, targetType : 'any'`;
1761
- }
1762
- if (expression.constraints && Object.keys(expression.constraints).length > 0) {
1763
- outValue += `, constraints: ${compileBinding(expression.constraints)}`;
1764
- }
1765
- if (expression.formatOptions && Object.keys(expression.formatOptions).length > 0) {
1766
- outValue += `, formatOptions: ${compileBinding(expression.formatOptions)}`;
1767
- }
1768
- if (expression.parameters && Object.keys(expression.parameters).length > 0) {
1769
- outValue += `, parameters: ${compileBinding(expression.parameters)}`;
1770
- }
1771
- if (singlePath) {
1772
- return outValue;
1773
- }
1774
- return `{${outValue}}`;
1775
- default:
1776
- 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 "";
1777
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}`;
1778
1795
  }