@sapui5/sap.fe.base 1.125.0 → 1.126.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.
@@ -0,0 +1,2452 @@
1
+ import type {
2
+ ActionParameter,
3
+ AndAnnotationExpression,
4
+ AndConditionalExpression,
5
+ ApplyAnnotationExpression,
6
+ Boolean,
7
+ ConditionalCheckOrValue,
8
+ EntitySet,
9
+ EntityType,
10
+ EqAnnotationExpression,
11
+ EqConditionalExpression,
12
+ GeAnnotationExpression,
13
+ GeConditionalExpression,
14
+ GtAnnotationExpression,
15
+ GtConditionalExpression,
16
+ IfAnnotationExpression,
17
+ IfAnnotationExpressionValue,
18
+ LeAnnotationExpression,
19
+ LeConditionalExpression,
20
+ LtAnnotationExpression,
21
+ LtConditionalExpression,
22
+ NeAnnotationExpression,
23
+ NeConditionalExpression,
24
+ NotAnnotationExpression,
25
+ NotConditionalExpression,
26
+ OrAnnotationExpression,
27
+ OrConditionalExpression,
28
+ PathAnnotationExpression,
29
+ PathConditionExpression,
30
+ Property,
31
+ PropertyAnnotationValue
32
+ } from "@sap-ux/vocabularies-types";
33
+ import { CommonAnnotationTerms } from "@sap-ux/vocabularies-types/vocabularies/Common";
34
+ import { resolveEnumValue } from "sap/fe/base/AnnotationEnum";
35
+
36
+ export type PrimitiveType = string | number | bigint | boolean | object | null | undefined;
37
+ type DefinedPrimitiveType = string | number | bigint | boolean | object;
38
+ type PrimitiveTypeCast<P> = P extends String ? string : P extends Boolean ? boolean : P extends Number ? number : P;
39
+ type BaseExpression<_T> = {
40
+ _type: string;
41
+ };
42
+
43
+ export type FormatOptions = Partial<{
44
+ customUnits: Record<string, object>;
45
+ customCurrencies: Record<string, object>;
46
+ decimals: number;
47
+ emptyString: null | number | string;
48
+ fiscalType: string;
49
+ groupingEnabled: boolean;
50
+ maxFractionDigits: number;
51
+ maxIntegerDigits: number;
52
+ minFractionDigits: number;
53
+ minIntegerDigits: number;
54
+ parseAsString: boolean;
55
+ parseKeepsEmptyString: boolean;
56
+ pattern: string;
57
+ precision: number;
58
+ preserveDecimals: boolean;
59
+ shortDecimals: number;
60
+ shortRefNumber: number;
61
+ showMeasure: boolean;
62
+ showNumber: boolean;
63
+ showScale: boolean;
64
+ style: string;
65
+ }>;
66
+
67
+ export type ConstantExpression<T> = BaseExpression<T> & {
68
+ _type: "Constant";
69
+ value: T;
70
+ };
71
+
72
+ type SetOperator = "&&" | "||";
73
+ export type SetExpression = BaseExpression<boolean> & {
74
+ _type: "Set";
75
+ operator: SetOperator;
76
+ operands: BindingToolkitExpression<boolean>[];
77
+ };
78
+
79
+ export type NotExpression = BaseExpression<boolean> & {
80
+ _type: "Not";
81
+ operand: BindingToolkitExpression<boolean>;
82
+ };
83
+
84
+ export type TruthyExpression = BaseExpression<boolean> & {
85
+ _type: "Truthy";
86
+ operand: BindingToolkitExpression<string>;
87
+ };
88
+
89
+ export type ReferenceExpression = BaseExpression<object> & {
90
+ _type: "Ref";
91
+ ref: string | null;
92
+ };
93
+
94
+ export type FormatterExpression<T> = BaseExpression<T> & {
95
+ _type: "Formatter";
96
+ fn: string;
97
+ parameters: BindingToolkitExpression<unknown>[];
98
+ };
99
+
100
+ type ComplexTypeExpression<T> = BaseExpression<T> & {
101
+ _type: "ComplexType";
102
+ type: string;
103
+ formatOptions: FormatOptions;
104
+ parameters: object;
105
+ bindingParameters: BindingToolkitExpression<unknown>[];
106
+ };
107
+
108
+ export type FunctionExpression<T> = BaseExpression<T> & {
109
+ _type: "Function";
110
+ obj?: BindingToolkitExpression<object>;
111
+ fn: string;
112
+ isFormattingFn: boolean;
113
+ parameters: BindingToolkitExpression<unknown>[];
114
+ };
115
+
116
+ export type ConcatExpression = BaseExpression<string> & {
117
+ _type: "Concat";
118
+ expressions: BindingToolkitExpression<string>[];
119
+ };
120
+
121
+ export type LengthExpression = BaseExpression<string> & {
122
+ _type: "Length";
123
+ pathInModel: PathInModelExpression<unknown>;
124
+ };
125
+
126
+ type UnresolvablePathExpression = BaseExpression<string> & {
127
+ _type: "Unresolvable";
128
+ };
129
+
130
+ /**
131
+ * @typedef PathInModelExpression
132
+ */
133
+ export type PathInModelExpression<T> = BaseExpression<T> & {
134
+ _type: "PathInModel";
135
+ modelName?: string;
136
+ path: string;
137
+ targetEntitySet?: EntitySet;
138
+ type?: string;
139
+ constraints?: BindingConstraints;
140
+ parameters?: object;
141
+ targetType?: string;
142
+ mode?: string;
143
+ formatOptions?: FormatOptions;
144
+ };
145
+
146
+ export type EmbeddedUI5BindingExpression<T> = BaseExpression<T> & {
147
+ _type: "EmbeddedBinding";
148
+ value: string;
149
+ };
150
+
151
+ export type EmbeddedUI5ExpressionBindingExpression<T> = BaseExpression<T> & {
152
+ _type: "EmbeddedExpressionBinding";
153
+ value: string;
154
+ };
155
+
156
+ export type ComparisonOperator = "===" | "!==" | ">=" | ">" | "<=" | "<";
157
+ export type ComparisonExpression = BaseExpression<boolean> & {
158
+ _type: "Comparison";
159
+ operator: ComparisonOperator;
160
+ operand1: BindingToolkitExpression<unknown>;
161
+ operand2: BindingToolkitExpression<unknown>;
162
+ };
163
+
164
+ export type IfElseExpression<T> = BaseExpression<T> & {
165
+ _type: "IfElse";
166
+ condition: BindingToolkitExpression<boolean>;
167
+ onTrue: BindingToolkitExpression<T>;
168
+ onFalse: BindingToolkitExpression<T>;
169
+ };
170
+ export function isProperty(serviceObject: unknown): serviceObject is Property {
171
+ return (serviceObject as Property)?._type === "Property";
172
+ }
173
+
174
+ /**
175
+ * An expression that evaluates to type T.
176
+ *
177
+ * @typedef BindingToolkitExpression
178
+ */
179
+ export type BindingToolkitExpression<T> =
180
+ | UnresolvablePathExpression
181
+ | ConstantExpression<T>
182
+ | SetExpression
183
+ | NotExpression
184
+ | TruthyExpression
185
+ | ConcatExpression
186
+ | LengthExpression
187
+ | PathInModelExpression<T>
188
+ | EmbeddedUI5BindingExpression<T>
189
+ | EmbeddedUI5ExpressionBindingExpression<T>
190
+ | ComparisonExpression
191
+ | IfElseExpression<T>
192
+ | FormatterExpression<T>
193
+ | ComplexTypeExpression<T>
194
+ | ReferenceExpression
195
+ | FunctionExpression<T>;
196
+
197
+ type TypeMappingDefinition = {
198
+ type: string;
199
+ constraints?: {
200
+ $Precision?: string;
201
+ $V4?: string;
202
+ $Scale?: string;
203
+ $MaxLength?: string;
204
+ $Nullable?: string;
205
+ "@Org.OData.Validation.V1.Minimum/$Decimal"?: string;
206
+ "@Org.OData.Validation.V1.Minimum@Org.OData.Validation.V1.Exclusive"?: string;
207
+ "@Org.OData.Validation.V1.Maximum/$Decimal"?: string;
208
+ "@Org.OData.Validation.V1.Maximum@Org.OData.Validation.V1.Exclusive"?: string;
209
+ "@com.sap.vocabularies.Common.v1.IsDigitSequence"?: string;
210
+ };
211
+ };
212
+ export const EDM_TYPE_MAPPING: Record<string, TypeMappingDefinition> = {
213
+ "Edm.Boolean": { type: "sap.ui.model.odata.type.Boolean" },
214
+ "Edm.Byte": { type: "sap.ui.model.odata.type.Byte" },
215
+ "Edm.Date": { type: "sap.ui.model.odata.type.Date" },
216
+ "Edm.DateTimeOffset": {
217
+ constraints: {
218
+ $Precision: "precision",
219
+ $V4: "V4"
220
+ },
221
+ type: "sap.ui.model.odata.type.DateTimeOffset"
222
+ },
223
+ "Edm.Decimal": {
224
+ constraints: {
225
+ "@Org.OData.Validation.V1.Minimum/$Decimal": "minimum",
226
+ "@Org.OData.Validation.V1.Minimum@Org.OData.Validation.V1.Exclusive": "minimumExclusive",
227
+ "@Org.OData.Validation.V1.Maximum/$Decimal": "maximum",
228
+ "@Org.OData.Validation.V1.Maximum@Org.OData.Validation.V1.Exclusive": "maximumExclusive",
229
+ $Precision: "precision",
230
+ $Scale: "scale"
231
+ },
232
+ type: "sap.ui.model.odata.type.Decimal"
233
+ },
234
+ "Edm.Double": { type: "sap.ui.model.odata.type.Double" },
235
+ "Edm.Guid": { type: "sap.ui.model.odata.type.Guid" },
236
+ "Edm.Int16": { type: "sap.ui.model.odata.type.Int16" },
237
+ "Edm.Int32": { type: "sap.ui.model.odata.type.Int32" },
238
+ "Edm.Int64": { type: "sap.ui.model.odata.type.Int64" },
239
+ "Edm.SByte": { type: "sap.ui.model.odata.type.SByte" },
240
+ "Edm.Single": { type: "sap.ui.model.odata.type.Single" },
241
+ "Edm.Stream": { type: "sap.ui.model.odata.type.Stream" },
242
+ "Edm.Binary": { type: "sap.ui.model.odata.type.Stream" },
243
+ "Edm.String": {
244
+ constraints: {
245
+ "@com.sap.vocabularies.Common.v1.IsDigitSequence": "isDigitSequence",
246
+ $MaxLength: "maxLength",
247
+ $Nullable: "nullable"
248
+ },
249
+ type: "sap.ui.model.odata.type.String"
250
+ },
251
+ "Edm.TimeOfDay": {
252
+ constraints: {
253
+ $Precision: "precision"
254
+ },
255
+ type: "sap.ui.model.odata.type.TimeOfDay"
256
+ }
257
+ };
258
+
259
+ /**
260
+ * An expression that evaluates to type T, or a constant value of type T
261
+ */
262
+ export type ExpressionOrPrimitive<T extends PrimitiveType> = BindingToolkitExpression<T> | T;
263
+
264
+ export const unresolvableExpression: UnresolvablePathExpression = {
265
+ _type: "Unresolvable"
266
+ };
267
+
268
+ export function escapeXmlAttribute(inputString: string): string {
269
+ return inputString.replace(/'/g, "\\'");
270
+ }
271
+
272
+ export function hasUnresolvableExpression(...expressions: BindingToolkitExpression<unknown>[]): boolean {
273
+ return expressions.find((expr) => expr._type === "Unresolvable") !== undefined;
274
+ }
275
+ /**
276
+ * Check two expressions for (deep) equality.
277
+ *
278
+ * @param a
279
+ * @param b
280
+ * @returns `true` if the two expressions are equal
281
+ */
282
+ export function _checkExpressionsAreEqual<T>(a: BindingToolkitExpression<T>, b: BindingToolkitExpression<T>): boolean {
283
+ if (a._type !== b._type) {
284
+ return false;
285
+ }
286
+
287
+ switch (a._type) {
288
+ case "Unresolvable":
289
+ return false; // Unresolvable is never equal to anything even itself
290
+ case "Constant":
291
+ case "EmbeddedBinding":
292
+ case "EmbeddedExpressionBinding":
293
+ return a.value === (b as ConstantExpression<T>).value;
294
+
295
+ case "Not":
296
+ return _checkExpressionsAreEqual(a.operand, (b as NotExpression).operand);
297
+ case "Truthy":
298
+ return _checkExpressionsAreEqual(a.operand, (b as TruthyExpression).operand);
299
+ case "Set":
300
+ return (
301
+ a.operator === (b as SetExpression).operator &&
302
+ a.operands.length === (b as SetExpression).operands.length &&
303
+ a.operands.every((expression) =>
304
+ (b as SetExpression).operands.some((otherExpression) => _checkExpressionsAreEqual(expression, otherExpression))
305
+ )
306
+ );
307
+
308
+ case "IfElse":
309
+ return (
310
+ _checkExpressionsAreEqual(a.condition, (b as IfElseExpression<T>).condition) &&
311
+ _checkExpressionsAreEqual(a.onTrue, (b as IfElseExpression<T>).onTrue) &&
312
+ _checkExpressionsAreEqual(a.onFalse, (b as IfElseExpression<T>).onFalse)
313
+ );
314
+
315
+ case "Comparison":
316
+ return (
317
+ a.operator === (b as ComparisonExpression).operator &&
318
+ _checkExpressionsAreEqual(a.operand1, (b as ComparisonExpression).operand1) &&
319
+ _checkExpressionsAreEqual(a.operand2, (b as ComparisonExpression).operand2)
320
+ );
321
+
322
+ case "Concat":
323
+ const aExpressions = a.expressions;
324
+ const bExpressions = (b as ConcatExpression).expressions;
325
+ if (aExpressions.length !== bExpressions.length) {
326
+ return false;
327
+ }
328
+ return aExpressions.every((expression, index) => {
329
+ return _checkExpressionsAreEqual(expression, bExpressions[index]);
330
+ });
331
+
332
+ case "Length":
333
+ return _checkExpressionsAreEqual(a.pathInModel, (b as LengthExpression).pathInModel);
334
+
335
+ case "PathInModel":
336
+ return (
337
+ a.modelName === (b as PathInModelExpression<T>).modelName &&
338
+ a.path === (b as PathInModelExpression<T>).path &&
339
+ a.targetEntitySet === (b as PathInModelExpression<T>).targetEntitySet
340
+ );
341
+
342
+ case "Formatter":
343
+ return (
344
+ a.fn === (b as FormatterExpression<T>).fn &&
345
+ a.parameters.length === (b as FormatterExpression<T>).parameters.length &&
346
+ a.parameters.every((value, index) => _checkExpressionsAreEqual((b as FormatterExpression<T>).parameters[index], value))
347
+ );
348
+ case "ComplexType":
349
+ return (
350
+ a.type === (b as ComplexTypeExpression<T>).type &&
351
+ a.bindingParameters.length === (b as ComplexTypeExpression<T>).bindingParameters.length &&
352
+ a.bindingParameters.every((value, index) =>
353
+ _checkExpressionsAreEqual((b as ComplexTypeExpression<T>).bindingParameters[index], value)
354
+ )
355
+ );
356
+ case "Function":
357
+ const otherFunction = b as FunctionExpression<T>;
358
+ if (a.obj === undefined || otherFunction.obj === undefined) {
359
+ return a.obj === otherFunction;
360
+ }
361
+
362
+ return (
363
+ a.fn === otherFunction.fn &&
364
+ _checkExpressionsAreEqual(a.obj, otherFunction.obj) &&
365
+ a.parameters.length === otherFunction.parameters.length &&
366
+ a.parameters.every((value, index) => _checkExpressionsAreEqual(otherFunction.parameters[index], value))
367
+ );
368
+
369
+ case "Ref":
370
+ return a.ref === (b as ReferenceExpression).ref;
371
+ }
372
+ return false;
373
+ }
374
+
375
+ /**
376
+ * Converts a nested SetExpression by inlining operands of type SetExpression with the same operator.
377
+ *
378
+ * @param expression The expression to flatten
379
+ * @returns A new SetExpression with the same operator
380
+ */
381
+ function flattenSetExpression(expression: SetExpression): SetExpression {
382
+ return expression.operands.reduce(
383
+ (result: SetExpression, operand) => {
384
+ const candidatesForFlattening =
385
+ operand._type === "Set" && operand.operator === expression.operator ? operand.operands : [operand];
386
+ candidatesForFlattening.forEach((candidate) => {
387
+ if (result.operands.every((e) => !_checkExpressionsAreEqual(e, candidate))) {
388
+ result.operands.push(candidate);
389
+ }
390
+ });
391
+ return result;
392
+ },
393
+ { _type: "Set", operator: expression.operator, operands: [] }
394
+ );
395
+ }
396
+
397
+ /**
398
+ * Detects whether an array of boolean expressions contains an expression and its negation.
399
+ *
400
+ * @param expressions Array of expressions
401
+ * @returns `true` if the set of expressions contains an expression and its negation
402
+ */
403
+ function hasOppositeExpressions(expressions: BindingToolkitExpression<boolean>[]): boolean {
404
+ const negatedExpressions = expressions.map(not);
405
+ return expressions.some((expression, index) => {
406
+ for (let i = index + 1; i < negatedExpressions.length; i++) {
407
+ if (_checkExpressionsAreEqual(expression, negatedExpressions[i])) {
408
+ return true;
409
+ }
410
+ }
411
+ return false;
412
+ });
413
+ }
414
+
415
+ /**
416
+ * Logical `and` expression.
417
+ *
418
+ * The expression is simplified to false if this can be decided statically (that is, if one operand is a constant
419
+ * false or if the expression contains an operand and its negation).
420
+ *
421
+ * @param operands Expressions to connect by `and`
422
+ * @returns Expression evaluating to boolean
423
+ */
424
+ export function and(...operands: ExpressionOrPrimitive<boolean>[]): BindingToolkitExpression<boolean> {
425
+ const expressions = flattenSetExpression({
426
+ _type: "Set",
427
+ operator: "&&",
428
+ operands: operands.map(wrapPrimitive)
429
+ }).operands;
430
+
431
+ if (hasUnresolvableExpression(...expressions)) {
432
+ return unresolvableExpression;
433
+ }
434
+ let isStaticFalse = false;
435
+ const nonTrivialExpression = expressions.filter((expression) => {
436
+ if (isFalse(expression)) {
437
+ isStaticFalse = true;
438
+ }
439
+ return !isConstant(expression);
440
+ });
441
+ if (isStaticFalse) {
442
+ return constant(false);
443
+ } else if (nonTrivialExpression.length === 0) {
444
+ // Resolve the constant then
445
+ const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
446
+ return constant(isValid);
447
+ } else if (nonTrivialExpression.length === 1) {
448
+ return nonTrivialExpression[0];
449
+ } else if (hasOppositeExpressions(nonTrivialExpression)) {
450
+ return constant(false);
451
+ } else {
452
+ return {
453
+ _type: "Set",
454
+ operator: "&&",
455
+ operands: nonTrivialExpression
456
+ };
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Logical `or` expression.
462
+ *
463
+ * The expression is simplified to true if this can be decided statically (that is, if one operand is a constant
464
+ * true or if the expression contains an operand and its negation).
465
+ *
466
+ * @param operands Expressions to connect by `or`
467
+ * @returns Expression evaluating to boolean
468
+ */
469
+ export function or(...operands: ExpressionOrPrimitive<boolean>[]): BindingToolkitExpression<boolean> {
470
+ const expressions = flattenSetExpression({
471
+ _type: "Set",
472
+ operator: "||",
473
+ operands: operands.map(wrapPrimitive)
474
+ }).operands;
475
+ if (hasUnresolvableExpression(...expressions)) {
476
+ return unresolvableExpression;
477
+ }
478
+ let isStaticTrue = false;
479
+ const nonTrivialExpression = expressions.filter((expression) => {
480
+ if (isTrue(expression)) {
481
+ isStaticTrue = true;
482
+ }
483
+ return !isConstant(expression) || expression.value;
484
+ });
485
+ if (isStaticTrue) {
486
+ return constant(true);
487
+ } else if (nonTrivialExpression.length === 0) {
488
+ // Resolve the constant then
489
+ const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
490
+ return constant(isValid);
491
+ } else if (nonTrivialExpression.length === 1) {
492
+ return nonTrivialExpression[0];
493
+ } else if (hasOppositeExpressions(nonTrivialExpression)) {
494
+ return constant(true);
495
+ } else {
496
+ return {
497
+ _type: "Set",
498
+ operator: "||",
499
+ operands: nonTrivialExpression
500
+ };
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Logical `not` operator.
506
+ *
507
+ * @param operand The expression to reverse
508
+ * @returns The resulting expression that evaluates to boolean
509
+ */
510
+ export function not(operand: ExpressionOrPrimitive<boolean>): BindingToolkitExpression<boolean> {
511
+ operand = wrapPrimitive(operand);
512
+ if (hasUnresolvableExpression(operand)) {
513
+ return unresolvableExpression;
514
+ } else if (isConstant(operand)) {
515
+ return constant(!operand.value);
516
+ } else if (
517
+ typeof operand === "object" &&
518
+ operand._type === "Set" &&
519
+ operand.operator === "||" &&
520
+ operand.operands.every((expression) => isConstant(expression) || isComparison(expression))
521
+ ) {
522
+ return and(...operand.operands.map((expression) => not(expression)));
523
+ } else if (
524
+ typeof operand === "object" &&
525
+ operand._type === "Set" &&
526
+ operand.operator === "&&" &&
527
+ operand.operands.every((expression) => isConstant(expression) || isComparison(expression))
528
+ ) {
529
+ return or(...operand.operands.map((expression) => not(expression)));
530
+ } else if (isComparison(operand)) {
531
+ // Create the reverse comparison
532
+ switch (operand.operator) {
533
+ case "!==":
534
+ return { ...operand, operator: "===" };
535
+ case "<":
536
+ return { ...operand, operator: ">=" };
537
+ case "<=":
538
+ return { ...operand, operator: ">" };
539
+ case "===":
540
+ return { ...operand, operator: "!==" };
541
+ case ">":
542
+ return { ...operand, operator: "<=" };
543
+ case ">=":
544
+ return { ...operand, operator: "<" };
545
+ }
546
+ } else if (operand._type === "Not") {
547
+ return operand.operand;
548
+ }
549
+
550
+ return {
551
+ _type: "Not",
552
+ operand: operand
553
+ };
554
+ }
555
+
556
+ /**
557
+ * Evaluates whether a binding expression is equal to true with a loose equality.
558
+ *
559
+ * @param operand The expression to check
560
+ * @returns The resulting expression that evaluates to boolean
561
+ */
562
+ export function isTruthy(operand: BindingToolkitExpression<string>): BindingToolkitExpression<boolean> {
563
+ if (isConstant(operand)) {
564
+ return constant(!!operand.value);
565
+ } else {
566
+ return {
567
+ _type: "Truthy",
568
+ operand: operand
569
+ };
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Creates a binding expression that will be evaluated by the corresponding model.
575
+ *
576
+ * @param path
577
+ * @param modelName
578
+ * @param visitedNavigationPaths
579
+ * @param pathVisitor
580
+ * @returns An expression representating that path in the model
581
+ * @deprecated use pathInModel instead
582
+ */
583
+ export function bindingExpression<TargetType extends PrimitiveType>(
584
+ path: string,
585
+ modelName?: string,
586
+ visitedNavigationPaths: string[] = [],
587
+ pathVisitor?: Function
588
+ ): PathInModelExpression<TargetType> | UnresolvablePathExpression {
589
+ return pathInModel(path, modelName, visitedNavigationPaths, pathVisitor);
590
+ }
591
+
592
+ /**
593
+ * Creates a binding expression that will be evaluated by the corresponding model.
594
+ *
595
+ * @template TargetType
596
+ * @param path The path on the model
597
+ * @param [modelName] The name of the model
598
+ * @param [visitedNavigationPaths] The paths from the root entitySet
599
+ * @param [pathVisitor] A function to modify the resulting path
600
+ * @returns An expression representating that path in the model
601
+ */
602
+ export function pathInModel(
603
+ path: undefined,
604
+ modelName?: string,
605
+ visitedNavigationPaths?: string[],
606
+ pathVisitor?: Function
607
+ ): UnresolvablePathExpression;
608
+ export function pathInModel<TargetType extends PrimitiveType>(
609
+ path: string,
610
+ modelName?: string,
611
+ visitedNavigationPaths?: string[],
612
+ pathVisitor?: undefined
613
+ ): PathInModelExpression<TargetType>;
614
+ export function pathInModel<TargetType extends PrimitiveType>(
615
+ path: string | undefined,
616
+ modelName?: string,
617
+ visitedNavigationPaths?: string[],
618
+ pathVisitor?: Function
619
+ ): UnresolvablePathExpression | PathInModelExpression<TargetType>;
620
+ export function pathInModel<TargetType extends PrimitiveType>(
621
+ path: string | undefined,
622
+ modelName?: string,
623
+ visitedNavigationPaths: string[] = [],
624
+ pathVisitor?: Function
625
+ ): UnresolvablePathExpression | PathInModelExpression<TargetType> {
626
+ if (path === undefined) {
627
+ return unresolvableExpression;
628
+ }
629
+ let targetPath;
630
+ if (pathVisitor) {
631
+ targetPath = pathVisitor(path);
632
+ if (targetPath === undefined) {
633
+ return unresolvableExpression;
634
+ }
635
+ } else {
636
+ const localPath = visitedNavigationPaths.concat();
637
+ localPath.push(path);
638
+ targetPath = localPath.join("/");
639
+ }
640
+ return {
641
+ _type: "PathInModel",
642
+ modelName: modelName,
643
+ path: targetPath
644
+ };
645
+ }
646
+
647
+ type PlainExpressionObject = { [index: string]: BindingToolkitExpression<unknown> };
648
+
649
+ /**
650
+ * Creates a constant expression based on a primitive value.
651
+ *
652
+ * @template T
653
+ * @param value The constant to wrap in an expression
654
+ * @returns The constant expression
655
+ */
656
+ export function constant<T extends PrimitiveType>(value: T): ConstantExpression<T> {
657
+ let constantValue: T;
658
+
659
+ if (typeof value === "object" && value !== null && value !== undefined) {
660
+ if (Array.isArray(value)) {
661
+ constantValue = value.map(wrapPrimitive) as T;
662
+ } else if (isPrimitiveObject(value)) {
663
+ constantValue = value.valueOf() as T;
664
+ } else {
665
+ constantValue = Object.entries(value).reduce((plainExpression, [key, val]) => {
666
+ const wrappedValue = wrapPrimitive(val);
667
+ if (wrappedValue._type !== "Constant" || wrappedValue.value !== undefined) {
668
+ plainExpression[key] = wrappedValue;
669
+ }
670
+ return plainExpression;
671
+ }, {} as PlainExpressionObject) as T;
672
+ }
673
+ } else {
674
+ constantValue = value;
675
+ }
676
+
677
+ return { _type: "Constant", value: constantValue };
678
+ }
679
+
680
+ export function resolveBindingString<T extends PrimitiveType>(
681
+ value: string | boolean | number,
682
+ targetType?: string
683
+ ): ConstantExpression<T> | PathInModelExpression<T> | EmbeddedUI5BindingExpression<T> | EmbeddedUI5ExpressionBindingExpression<T> {
684
+ if (value !== undefined && typeof value === "string" && value.startsWith("{")) {
685
+ const pathInModelRegex = /^{(.*)>(.+)}$/; // Matches model paths like "model>path" or ">path" (default model)
686
+ const pathInModelRegexMatch = pathInModelRegex.exec(value);
687
+
688
+ if (value.startsWith("{=")) {
689
+ // Expression binding, we can just remove the outer binding things
690
+ return {
691
+ _type: "EmbeddedExpressionBinding",
692
+ value: value
693
+ };
694
+ } else if (pathInModelRegexMatch) {
695
+ return pathInModel(pathInModelRegexMatch[2] || "", pathInModelRegexMatch[1] || undefined);
696
+ } else {
697
+ return {
698
+ _type: "EmbeddedBinding",
699
+ value: value
700
+ };
701
+ }
702
+ } else if (targetType === "boolean" && typeof value === "string" && (value === "true" || value === "false")) {
703
+ return constant(value === "true") as ConstantExpression<T>;
704
+ } else if (targetType === "number" && typeof value === "string" && (!isNaN(Number(value)) || value === "NaN")) {
705
+ return constant(Number(value)) as ConstantExpression<T>;
706
+ } else {
707
+ return constant(value) as ConstantExpression<T>;
708
+ }
709
+ }
710
+
711
+ /**
712
+ * A named reference.
713
+ *
714
+ * @see fn
715
+ * @param reference Reference
716
+ * @returns The object reference binding part
717
+ */
718
+ export function ref(reference: string | null): ReferenceExpression {
719
+ return { _type: "Ref", ref: reference };
720
+ }
721
+
722
+ /**
723
+ * Wrap a primitive into a constant expression if it is not already an expression.
724
+ *
725
+ * @template T
726
+ * @param something The object to wrap in a Constant expression
727
+ * @returns Either the original object or the wrapped one depending on the case
728
+ */
729
+ export function wrapPrimitive<T extends PrimitiveType>(something: ExpressionOrPrimitive<T>): BindingToolkitExpression<T> {
730
+ if (isBindingToolkitExpression(something)) {
731
+ return something as BindingToolkitExpression<T>;
732
+ }
733
+
734
+ return constant(something);
735
+ }
736
+
737
+ /**
738
+ * Checks if the expression or value provided is a binding tooling expression or not.
739
+ *
740
+ * Every object having a property named `_type` of some value is considered an expression, even if there is actually
741
+ * no such expression type supported.
742
+ *
743
+ * @param expression
744
+ * @returns `true` if the expression is a binding toolkit expression
745
+ */
746
+ export function isBindingToolkitExpression(
747
+ expression: BindingToolkitExpression<unknown> | unknown
748
+ ): expression is BindingToolkitExpression<unknown> {
749
+ return (expression as BindingToolkitExpression<unknown>)?._type !== undefined;
750
+ }
751
+
752
+ /**
753
+ * Checks if the expression or value provided is constant or not.
754
+ *
755
+ * @template T The target type
756
+ * @param maybeConstant The expression or primitive value that is to be checked
757
+ * @returns `true` if it is constant
758
+ */
759
+ export function isConstant<T extends PrimitiveType>(maybeConstant: ExpressionOrPrimitive<T>): maybeConstant is ConstantExpression<T> {
760
+ return typeof maybeConstant !== "object" || (maybeConstant as BaseExpression<T>)._type === "Constant";
761
+ }
762
+
763
+ function isTrue(expression: BindingToolkitExpression<PrimitiveType>): boolean {
764
+ return isConstant(expression) && expression.value === true;
765
+ }
766
+
767
+ function isFalse(expression: BindingToolkitExpression<PrimitiveType>): boolean {
768
+ return isConstant(expression) && expression.value === false;
769
+ }
770
+
771
+ /**
772
+ * Checks if the expression or value provided is a path in model expression or not.
773
+ *
774
+ * @template T The target type
775
+ * @param maybeBinding The expression or primitive value that is to be checked
776
+ * @returns `true` if it is a path in model expression
777
+ */
778
+ export function isPathInModelExpression<T extends PrimitiveType>(
779
+ maybeBinding: ExpressionOrPrimitive<T>
780
+ ): maybeBinding is PathInModelExpression<T> {
781
+ return (maybeBinding as BaseExpression<T>)?._type === "PathInModel";
782
+ }
783
+
784
+ /**
785
+ * Checks if the expression or value provided is a complex type expression.
786
+ *
787
+ * @template T The target type
788
+ * @param maybeBinding The expression or primitive value that is to be checked
789
+ * @returns `true` if it is a path in model expression
790
+ */
791
+ export function isComplexTypeExpression<T extends PrimitiveType>(
792
+ maybeBinding: ExpressionOrPrimitive<T>
793
+ ): maybeBinding is ComplexTypeExpression<T> {
794
+ return (maybeBinding as BaseExpression<T>)?._type === "ComplexType";
795
+ }
796
+
797
+ /**
798
+ * Checks if the expression or value provided is a concat expression or not.
799
+ *
800
+ * @param expression
801
+ * @returns `true` if the expression is a ConcatExpression
802
+ */
803
+ function isConcatExpression(expression: BindingToolkitExpression<PrimitiveType>): expression is ConcatExpression {
804
+ return (expression as BaseExpression<PrimitiveType>)?._type === "Concat";
805
+ }
806
+
807
+ /**
808
+ * Checks if the expression or value provided is a IfElse expression or not.
809
+ *
810
+ * @param expression
811
+ * @returns `true` if the expression is a IfElseExpression
812
+ */
813
+ function isIfElseExpression(expression: BindingToolkitExpression<PrimitiveType>): expression is ConcatExpression {
814
+ return (expression as BaseExpression<PrimitiveType>)?._type === "IfElse";
815
+ }
816
+
817
+ /**
818
+ * Checks if the expression provided is a comparison or not.
819
+ *
820
+ * @template T The target type
821
+ * @param expression The expression
822
+ * @returns `true` if the expression is a ComparisonExpression
823
+ */
824
+ function isComparison<T extends PrimitiveType>(expression: BindingToolkitExpression<T>): expression is ComparisonExpression {
825
+ return expression._type === "Comparison";
826
+ }
827
+
828
+ /**
829
+ * Checks whether the input parameter is a constant expression of type undefined.
830
+ *
831
+ * @param expression The input expression or object in general
832
+ * @returns `true` if the input is constant which has undefined for value
833
+ */
834
+ export function isUndefinedExpression(expression: unknown): expression is ConstantExpression<undefined> {
835
+ const expressionAsExpression = expression as BindingToolkitExpression<unknown>;
836
+ return expressionAsExpression?._type === "Constant" && expressionAsExpression?.value === undefined;
837
+ }
838
+
839
+ type ComplexAnnotationExpression<P> =
840
+ | PathAnnotationExpression<P>
841
+ | ApplyAnnotationExpression<P>
842
+ | IfAnnotationExpression<P>
843
+ | OrAnnotationExpression<P>
844
+ | AndAnnotationExpression<P>
845
+ | NeAnnotationExpression<P>
846
+ | EqAnnotationExpression<P>
847
+ | NotAnnotationExpression<P>
848
+ | GtAnnotationExpression<P>
849
+ | GeAnnotationExpression<P>
850
+ | LeAnnotationExpression<P>
851
+ | LtAnnotationExpression<P>;
852
+
853
+ function isPrimitiveObject(objectType: object): boolean {
854
+ switch (objectType.constructor.name) {
855
+ case "String":
856
+ case "Number":
857
+ case "Boolean":
858
+ return true;
859
+ default:
860
+ return false;
861
+ }
862
+ }
863
+ /**
864
+ * Check if the passed annotation annotationValue is a ComplexAnnotationExpression.
865
+ *
866
+ * @template T The target type
867
+ * @param annotationValue The annotation annotationValue to evaluate
868
+ * @returns `true` if the object is a {ComplexAnnotationExpression}
869
+ */
870
+ function isComplexAnnotationExpression<T>(annotationValue: PropertyAnnotationValue<T>): annotationValue is ComplexAnnotationExpression<T> {
871
+ return typeof annotationValue === "object" && !isPrimitiveObject(annotationValue as object);
872
+ }
873
+
874
+ /**
875
+ * Generate the corresponding annotationValue for a given annotation annotationValue.
876
+ *
877
+ * @template T The target type
878
+ * @param annotationValue The source annotation annotationValue
879
+ * @param visitedNavigationPaths The path from the root entity set
880
+ * @param defaultValue Default value if the annotationValue is undefined
881
+ * @param pathVisitor A function to modify the resulting path
882
+ * @returns The annotationValue equivalent to that annotation annotationValue
883
+ */
884
+ export function getExpressionFromAnnotation<T extends PrimitiveType>(
885
+ annotationValue: PropertyAnnotationValue<T> | undefined,
886
+ visitedNavigationPaths: string[] = [],
887
+ defaultValue?: ExpressionOrPrimitive<T>,
888
+ pathVisitor?: Function
889
+ ): BindingToolkitExpression<PrimitiveTypeCast<T>> {
890
+ if (annotationValue === undefined) {
891
+ return wrapPrimitive(defaultValue as PrimitiveTypeCast<T>);
892
+ }
893
+ annotationValue = annotationValue?.valueOf() as PropertyAnnotationValue<T>;
894
+ if (!isComplexAnnotationExpression(annotationValue)) {
895
+ return constant(annotationValue);
896
+ }
897
+
898
+ switch (annotationValue.type) {
899
+ case "Path":
900
+ return pathInModel(annotationValue.path, undefined, visitedNavigationPaths, pathVisitor);
901
+ case "If":
902
+ return annotationIfExpression(annotationValue.If, visitedNavigationPaths, pathVisitor) as BindingToolkitExpression<
903
+ PrimitiveTypeCast<T>
904
+ >;
905
+ case "Not":
906
+ return not(parseAnnotationCondition(annotationValue.Not, visitedNavigationPaths, pathVisitor)) as BindingToolkitExpression<
907
+ PrimitiveTypeCast<T>
908
+ >;
909
+ case "Eq":
910
+ return equal(
911
+ parseAnnotationCondition(annotationValue.Eq[0], visitedNavigationPaths, pathVisitor),
912
+ parseAnnotationCondition(annotationValue.Eq[1], visitedNavigationPaths, pathVisitor)
913
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
914
+ case "Ne":
915
+ return notEqual(
916
+ parseAnnotationCondition(annotationValue.Ne[0], visitedNavigationPaths, pathVisitor),
917
+ parseAnnotationCondition(annotationValue.Ne[1], visitedNavigationPaths, pathVisitor)
918
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
919
+ case "Gt":
920
+ return greaterThan(
921
+ parseAnnotationCondition(annotationValue.Gt[0], visitedNavigationPaths, pathVisitor),
922
+ parseAnnotationCondition(annotationValue.Gt[1], visitedNavigationPaths, pathVisitor)
923
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
924
+ case "Ge":
925
+ return greaterOrEqual(
926
+ parseAnnotationCondition(annotationValue.Ge[0], visitedNavigationPaths, pathVisitor),
927
+ parseAnnotationCondition(annotationValue.Ge[1], visitedNavigationPaths, pathVisitor)
928
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
929
+ case "Lt":
930
+ return lessThan(
931
+ parseAnnotationCondition(annotationValue.Lt[0], visitedNavigationPaths, pathVisitor),
932
+ parseAnnotationCondition(annotationValue.Lt[1], visitedNavigationPaths, pathVisitor)
933
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
934
+ case "Le":
935
+ return lessOrEqual(
936
+ parseAnnotationCondition(annotationValue.Le[0], visitedNavigationPaths, pathVisitor),
937
+ parseAnnotationCondition(annotationValue.Le[1], visitedNavigationPaths, pathVisitor)
938
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
939
+ case "Or":
940
+ return or(
941
+ ...annotationValue.Or.map(function (orCondition) {
942
+ return parseAnnotationCondition<boolean>(orCondition, visitedNavigationPaths, pathVisitor);
943
+ })
944
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
945
+ case "And":
946
+ return and(
947
+ ...annotationValue.And.map(function (andCondition) {
948
+ return parseAnnotationCondition<boolean>(andCondition, visitedNavigationPaths, pathVisitor);
949
+ })
950
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
951
+ case "Apply":
952
+ return annotationApplyExpression(
953
+ annotationValue as ApplyAnnotationExpression<string>,
954
+ visitedNavigationPaths,
955
+ pathVisitor
956
+ ) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
957
+ }
958
+ return unresolvableExpression;
959
+ }
960
+
961
+ /**
962
+ * Parse the annotation condition into an expression.
963
+ *
964
+ * @template T The target type
965
+ * @param annotationValue The condition or value from the annotation
966
+ * @param visitedNavigationPaths The path from the root entity set
967
+ * @param pathVisitor A function to modify the resulting path
968
+ * @returns An equivalent expression
969
+ */
970
+ function parseAnnotationCondition<T extends PrimitiveType>(
971
+ annotationValue: ConditionalCheckOrValue,
972
+ visitedNavigationPaths: string[] = [],
973
+ pathVisitor?: Function
974
+ ): BindingToolkitExpression<T> {
975
+ if (annotationValue === null || typeof annotationValue !== "object") {
976
+ return constant(annotationValue as T);
977
+ } else if (annotationValue.hasOwnProperty("$Or")) {
978
+ return or(
979
+ ...((annotationValue as OrConditionalExpression).$Or.map(function (orCondition) {
980
+ return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor);
981
+ }) as unknown as BindingToolkitExpression<boolean>[])
982
+ ) as BindingToolkitExpression<T>;
983
+ } else if (annotationValue.hasOwnProperty("$And")) {
984
+ return and(
985
+ ...((annotationValue as AndConditionalExpression).$And.map(function (andCondition) {
986
+ return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor);
987
+ }) as unknown as BindingToolkitExpression<boolean>[])
988
+ ) as BindingToolkitExpression<T>;
989
+ } else if (annotationValue.hasOwnProperty("$Not")) {
990
+ return not(
991
+ parseAnnotationCondition((annotationValue as NotConditionalExpression).$Not, visitedNavigationPaths, pathVisitor)
992
+ ) as BindingToolkitExpression<T>;
993
+ } else if (annotationValue.hasOwnProperty("$Eq")) {
994
+ return equal(
995
+ parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[0], visitedNavigationPaths, pathVisitor),
996
+ parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[1], visitedNavigationPaths, pathVisitor)
997
+ ) as BindingToolkitExpression<T>;
998
+ } else if (annotationValue.hasOwnProperty("$Ne")) {
999
+ return notEqual(
1000
+ parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[0], visitedNavigationPaths, pathVisitor),
1001
+ parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[1], visitedNavigationPaths, pathVisitor)
1002
+ ) as BindingToolkitExpression<T>;
1003
+ } else if (annotationValue.hasOwnProperty("$Gt")) {
1004
+ return greaterThan(
1005
+ parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[0], visitedNavigationPaths, pathVisitor),
1006
+ parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[1], visitedNavigationPaths, pathVisitor)
1007
+ ) as BindingToolkitExpression<T>;
1008
+ } else if (annotationValue.hasOwnProperty("$Ge")) {
1009
+ return greaterOrEqual(
1010
+ parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[0], visitedNavigationPaths, pathVisitor),
1011
+ parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[1], visitedNavigationPaths, pathVisitor)
1012
+ ) as BindingToolkitExpression<T>;
1013
+ } else if (annotationValue.hasOwnProperty("$Lt")) {
1014
+ return lessThan(
1015
+ parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[0], visitedNavigationPaths, pathVisitor),
1016
+ parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[1], visitedNavigationPaths, pathVisitor)
1017
+ ) as BindingToolkitExpression<T>;
1018
+ } else if (annotationValue.hasOwnProperty("$Le")) {
1019
+ return lessOrEqual(
1020
+ parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[0], visitedNavigationPaths, pathVisitor),
1021
+ parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[1], visitedNavigationPaths, pathVisitor)
1022
+ ) as BindingToolkitExpression<T>;
1023
+ } else if (annotationValue.hasOwnProperty("$Path")) {
1024
+ return pathInModel((annotationValue as PathConditionExpression<T>).$Path, undefined, visitedNavigationPaths, pathVisitor);
1025
+ } else if (annotationValue.hasOwnProperty("$Apply")) {
1026
+ return getExpressionFromAnnotation(
1027
+ {
1028
+ type: "Apply",
1029
+ Function: (annotationValue as { $Function?: string }).$Function,
1030
+ Apply: (annotationValue as { $Apply?: unknown }).$Apply
1031
+ } as ApplyAnnotationExpression<T>,
1032
+ visitedNavigationPaths,
1033
+ undefined,
1034
+ pathVisitor
1035
+ ) as BindingToolkitExpression<T>;
1036
+ } else if (annotationValue.hasOwnProperty("$If")) {
1037
+ return getExpressionFromAnnotation(
1038
+ {
1039
+ type: "If",
1040
+ If: (annotationValue as { $If?: unknown }).$If
1041
+ } as IfAnnotationExpression<T>,
1042
+ visitedNavigationPaths,
1043
+ undefined,
1044
+ pathVisitor
1045
+ ) as BindingToolkitExpression<T>;
1046
+ } else if (annotationValue.hasOwnProperty("$EnumMember")) {
1047
+ return constant(resolveEnumValue((annotationValue as { $EnumMember?: string }).$EnumMember) as T);
1048
+ }
1049
+ return constant(false as T);
1050
+ }
1051
+
1052
+ /**
1053
+ * Process the {IfAnnotationExpressionValue} into an expression.
1054
+ *
1055
+ * @template T The target type
1056
+ * @param annotationValue An If expression returning the type T
1057
+ * @param visitedNavigationPaths The path from the root entity set
1058
+ * @param pathVisitor A function to modify the resulting path
1059
+ * @returns The equivalent ifElse expression
1060
+ */
1061
+ export function annotationIfExpression<T extends PrimitiveType>(
1062
+ annotationValue: IfAnnotationExpressionValue<T>,
1063
+ visitedNavigationPaths: string[] = [],
1064
+ pathVisitor?: Function
1065
+ ): BindingToolkitExpression<T> {
1066
+ return ifElse(
1067
+ parseAnnotationCondition(annotationValue[0], visitedNavigationPaths, pathVisitor),
1068
+ parseAnnotationCondition(annotationValue[1] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor),
1069
+ parseAnnotationCondition(annotationValue[2] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor)
1070
+ );
1071
+ }
1072
+ // This type is not recursively transformed from the metamodel content, as such we have some ugly things there
1073
+ type SubApplyExpressionFromMetamodel = Record<string, unknown>;
1074
+ function convertSubApplyParameters(applyParam: SubApplyExpressionFromMetamodel): SubApplyExpressionFromMetamodel {
1075
+ let applyParamConverted = applyParam;
1076
+ if (applyParam.hasOwnProperty("$Path")) {
1077
+ applyParamConverted = {
1078
+ type: "Path",
1079
+ path: applyParam.$Path
1080
+ } as PathAnnotationExpression<string>;
1081
+ } else if (applyParam.hasOwnProperty("$If")) {
1082
+ applyParamConverted = {
1083
+ type: "If",
1084
+ If: applyParam.$If
1085
+ } as IfAnnotationExpression<string>;
1086
+ } else if (applyParam.hasOwnProperty("$Apply")) {
1087
+ applyParamConverted = {
1088
+ type: "Apply",
1089
+ Function: applyParam.$Function,
1090
+ Apply: applyParam.$Apply
1091
+ } as ApplyAnnotationExpression<string>;
1092
+ }
1093
+ return applyParamConverted;
1094
+ }
1095
+
1096
+ type ODataFunction = "odata.concat" | "odata.fillUriTemplate" | "odata.uriEncode";
1097
+ export function annotationApplyExpression(
1098
+ applyExpression: ApplyAnnotationExpression<string>,
1099
+ visitedNavigationPaths: string[] = [],
1100
+ pathVisitor?: Function
1101
+ ): BindingToolkitExpression<string> {
1102
+ switch (applyExpression.Function as ODataFunction) {
1103
+ case "odata.concat":
1104
+ return concat(
1105
+ ...applyExpression.Apply.map((applyParam) => {
1106
+ return getExpressionFromAnnotation(
1107
+ convertSubApplyParameters(applyParam) as PropertyAnnotationValue<string>,
1108
+ visitedNavigationPaths,
1109
+ undefined,
1110
+ pathVisitor
1111
+ );
1112
+ })
1113
+ );
1114
+ case "odata.uriEncode":
1115
+ const parameter = getExpressionFromAnnotation(
1116
+ convertSubApplyParameters(
1117
+ applyExpression.Apply[0] as unknown as SubApplyExpressionFromMetamodel
1118
+ ) as PropertyAnnotationValue<string>,
1119
+ visitedNavigationPaths,
1120
+ undefined,
1121
+ pathVisitor
1122
+ );
1123
+ // The second parameter for uriEncode is always a string since the target evaluation is against a formatValue call in ODataUtils which expect the target type as second parameter
1124
+ return fn("odata.uriEncode", [parameter, "Edm.String"], undefined, true);
1125
+ case "odata.fillUriTemplate":
1126
+ const template = applyExpression.Apply[0];
1127
+ const templateParams = applyExpression.Apply.slice(1) as unknown as SubApplyExpressionFromMetamodel[];
1128
+ const targetObject: Record<string, BindingToolkitExpression<unknown>> = {};
1129
+ templateParams.forEach((applyParam) => {
1130
+ targetObject[applyParam.$Name as string] = getExpressionFromAnnotation(
1131
+ convertSubApplyParameters(
1132
+ applyParam.$LabeledElement as SubApplyExpressionFromMetamodel
1133
+ ) as PropertyAnnotationValue<string>,
1134
+ visitedNavigationPaths,
1135
+ undefined,
1136
+ pathVisitor
1137
+ );
1138
+ });
1139
+ return fn("odata.fillUriTemplate", [template, targetObject], undefined, true);
1140
+ }
1141
+ return unresolvableExpression;
1142
+ }
1143
+
1144
+ /**
1145
+ * Generic helper for the comparison operations (equal, notEqual, ...).
1146
+ *
1147
+ * @template T The target type
1148
+ * @param operator The operator to apply
1149
+ * @param leftOperand The operand on the left side of the operator
1150
+ * @param rightOperand The operand on the right side of the operator
1151
+ * @returns An expression representing the comparison
1152
+ */
1153
+ function comparison<T extends PrimitiveType>(
1154
+ operator: ComparisonOperator,
1155
+ leftOperand: ExpressionOrPrimitive<T>,
1156
+ rightOperand: ExpressionOrPrimitive<T>
1157
+ ): BindingToolkitExpression<boolean> {
1158
+ const leftExpression = wrapPrimitive(leftOperand);
1159
+ const rightExpression = wrapPrimitive(rightOperand);
1160
+ if (hasUnresolvableExpression(leftExpression, rightExpression)) {
1161
+ return unresolvableExpression;
1162
+ }
1163
+ if (isConstant(leftExpression) && isConstant(rightExpression)) {
1164
+ switch (operator) {
1165
+ case "!==":
1166
+ return constant(leftExpression.value !== rightExpression.value);
1167
+ case "===":
1168
+ return constant(leftExpression.value === rightExpression.value);
1169
+ case "<":
1170
+ if (
1171
+ leftExpression.value === null ||
1172
+ leftExpression.value === undefined ||
1173
+ rightExpression.value === null ||
1174
+ rightExpression.value === undefined
1175
+ ) {
1176
+ return constant(false);
1177
+ }
1178
+ return constant(leftExpression.value < rightExpression.value);
1179
+ case "<=":
1180
+ if (
1181
+ leftExpression.value === null ||
1182
+ leftExpression.value === undefined ||
1183
+ rightExpression.value === null ||
1184
+ rightExpression.value === undefined
1185
+ ) {
1186
+ return constant(false);
1187
+ }
1188
+ return constant(leftExpression.value <= rightExpression.value);
1189
+ case ">":
1190
+ if (
1191
+ leftExpression.value === null ||
1192
+ leftExpression.value === undefined ||
1193
+ rightExpression.value === null ||
1194
+ rightExpression.value === undefined
1195
+ ) {
1196
+ return constant(false);
1197
+ }
1198
+ return constant(leftExpression.value > rightExpression.value);
1199
+ case ">=":
1200
+ if (
1201
+ leftExpression.value === null ||
1202
+ leftExpression.value === undefined ||
1203
+ rightExpression.value === null ||
1204
+ rightExpression.value === undefined
1205
+ ) {
1206
+ return constant(false);
1207
+ }
1208
+ return constant(leftExpression.value >= rightExpression.value);
1209
+ }
1210
+ } else {
1211
+ return {
1212
+ _type: "Comparison",
1213
+ operator: operator,
1214
+ operand1: leftExpression,
1215
+ operand2: rightExpression
1216
+ };
1217
+ }
1218
+ }
1219
+
1220
+ /**
1221
+ * Generic helper for the length of an expression.
1222
+ *
1223
+ * @param expression The input expression pointing to an array
1224
+ * @param checkUndefined Is the array potentially undefined
1225
+ * @returns An expression representing the length
1226
+ */
1227
+ export function length(
1228
+ expression: PathInModelExpression<unknown> | UnresolvablePathExpression,
1229
+ checkUndefined = false
1230
+ ): BindingToolkitExpression<number> {
1231
+ if (expression._type === "Unresolvable") {
1232
+ return expression;
1233
+ }
1234
+ if (!checkUndefined) {
1235
+ return {
1236
+ _type: "Length",
1237
+ pathInModel: expression
1238
+ };
1239
+ }
1240
+ return ifElse(equal(expression, undefined), -1, length(expression));
1241
+ }
1242
+
1243
+ /**
1244
+ * Comparison: "equal" (===).
1245
+ *
1246
+ * @template T The target type
1247
+ * @param leftOperand The operand on the left side
1248
+ * @param rightOperand The operand on the right side of the comparison
1249
+ * @returns An expression representing the comparison
1250
+ */
1251
+ export function equal<T extends PrimitiveType>(
1252
+ leftOperand: ExpressionOrPrimitive<T>,
1253
+ rightOperand: ExpressionOrPrimitive<T>
1254
+ ): BindingToolkitExpression<boolean> {
1255
+ const leftExpression = wrapPrimitive(leftOperand);
1256
+ const rightExpression = wrapPrimitive(rightOperand);
1257
+ if (hasUnresolvableExpression(leftExpression, rightExpression)) {
1258
+ return unresolvableExpression;
1259
+ }
1260
+ if (_checkExpressionsAreEqual(leftExpression, rightExpression)) {
1261
+ return constant(true);
1262
+ }
1263
+
1264
+ function reduce(left: BindingToolkitExpression<T>, right: BindingToolkitExpression<T>): BindingToolkitExpression<boolean> | undefined {
1265
+ if (left._type === "Comparison" && isTrue(right)) {
1266
+ // compare(a, b) === true ~~> compare(a, b)
1267
+ return left;
1268
+ } else if (left._type === "Comparison" && isFalse(right)) {
1269
+ // compare(a, b) === false ~~> !compare(a, b)
1270
+ return not(left);
1271
+ } else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onTrue, right)) {
1272
+ // (if (x) { a } else { b }) === a ~~> x || (b === a)
1273
+ return or(left.condition, equal(left.onFalse, right));
1274
+ } else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onFalse, right)) {
1275
+ // (if (x) { a } else { b }) === b ~~> !x || (a === b)
1276
+ return or(not(left.condition), equal(left.onTrue, right));
1277
+ } else if (
1278
+ left._type === "IfElse" &&
1279
+ isConstant(left.onTrue) &&
1280
+ isConstant(left.onFalse) &&
1281
+ isConstant(right) &&
1282
+ !_checkExpressionsAreEqual(left.onTrue, right) &&
1283
+ !_checkExpressionsAreEqual(left.onFalse, right)
1284
+ ) {
1285
+ return constant(false);
1286
+ }
1287
+ return undefined;
1288
+ }
1289
+
1290
+ // exploit symmetry: a === b <~> b === a
1291
+ const reduced = reduce(leftExpression, rightExpression) ?? reduce(rightExpression, leftExpression);
1292
+ return reduced ?? comparison("===", leftExpression, rightExpression);
1293
+ }
1294
+
1295
+ /**
1296
+ * Comparison: "not equal" (!==).
1297
+ *
1298
+ * @template T The target type
1299
+ * @param leftOperand The operand on the left side
1300
+ * @param rightOperand The operand on the right side of the comparison
1301
+ * @returns An expression representing the comparison
1302
+ */
1303
+ export function notEqual<T extends PrimitiveType>(
1304
+ leftOperand: ExpressionOrPrimitive<T>,
1305
+ rightOperand: ExpressionOrPrimitive<T>
1306
+ ): BindingToolkitExpression<boolean> {
1307
+ return not(equal(leftOperand, rightOperand));
1308
+ }
1309
+
1310
+ /**
1311
+ * Comparison: "greater or equal" (>=).
1312
+ *
1313
+ * @template T The target type
1314
+ * @param leftOperand The operand on the left side
1315
+ * @param rightOperand The operand on the right side of the comparison
1316
+ * @returns An expression representing the comparison
1317
+ */
1318
+ export function greaterOrEqual<T extends DefinedPrimitiveType>(
1319
+ leftOperand: ExpressionOrPrimitive<T>,
1320
+ rightOperand: ExpressionOrPrimitive<T>
1321
+ ): BindingToolkitExpression<boolean> {
1322
+ return comparison(">=", leftOperand, rightOperand);
1323
+ }
1324
+
1325
+ /**
1326
+ * Comparison: "greater than" (>).
1327
+ *
1328
+ * @template T The target type
1329
+ * @param leftOperand The operand on the left side
1330
+ * @param rightOperand The operand on the right side of the comparison
1331
+ * @returns An expression representing the comparison
1332
+ */
1333
+ export function greaterThan<T extends DefinedPrimitiveType>(
1334
+ leftOperand: ExpressionOrPrimitive<T>,
1335
+ rightOperand: ExpressionOrPrimitive<T>
1336
+ ): BindingToolkitExpression<boolean> {
1337
+ return comparison(">", leftOperand, rightOperand);
1338
+ }
1339
+
1340
+ /**
1341
+ * Comparison: "less or equal" (<=).
1342
+ *
1343
+ * @template T The target type
1344
+ * @param leftOperand The operand on the left side
1345
+ * @param rightOperand The operand on the right side of the comparison
1346
+ * @returns An expression representing the comparison
1347
+ */
1348
+ export function lessOrEqual<T extends DefinedPrimitiveType>(
1349
+ leftOperand: ExpressionOrPrimitive<T>,
1350
+ rightOperand: ExpressionOrPrimitive<T>
1351
+ ): BindingToolkitExpression<boolean> {
1352
+ return comparison("<=", leftOperand, rightOperand);
1353
+ }
1354
+
1355
+ /**
1356
+ * Comparison: "less than" (<).
1357
+ *
1358
+ * @template T The target type
1359
+ * @param leftOperand The operand on the left side
1360
+ * @param rightOperand The operand on the right side of the comparison
1361
+ * @returns An expression representing the comparison
1362
+ */
1363
+ export function lessThan<T extends DefinedPrimitiveType>(
1364
+ leftOperand: ExpressionOrPrimitive<T>,
1365
+ rightOperand: ExpressionOrPrimitive<T>
1366
+ ): BindingToolkitExpression<boolean> {
1367
+ return comparison("<", leftOperand, rightOperand);
1368
+ }
1369
+
1370
+ /**
1371
+ * If-then-else expression.
1372
+ *
1373
+ * Evaluates to onTrue if the condition evaluates to true, else evaluates to onFalse.
1374
+ *
1375
+ * @template T The target type
1376
+ * @param condition The condition to evaluate
1377
+ * @param onTrue Expression result if the condition evaluates to true
1378
+ * @param onFalse Expression result if the condition evaluates to false
1379
+ * @returns The expression that represents this conditional check
1380
+ */
1381
+ export function ifElse<T extends PrimitiveType>(
1382
+ condition: ExpressionOrPrimitive<boolean>,
1383
+ onTrue: ExpressionOrPrimitive<T>,
1384
+ onFalse: ExpressionOrPrimitive<T>
1385
+ ): BindingToolkitExpression<T> {
1386
+ let conditionExpression = wrapPrimitive(condition);
1387
+ let onTrueExpression = wrapPrimitive(onTrue);
1388
+ let onFalseExpression = wrapPrimitive(onFalse);
1389
+
1390
+ // swap branches if the condition is a negation
1391
+ if (conditionExpression._type === "Not") {
1392
+ // ifElse(not(X), a, b) --> ifElse(X, b, a)
1393
+ [onTrueExpression, onFalseExpression] = [onFalseExpression, onTrueExpression];
1394
+ conditionExpression = not(conditionExpression);
1395
+ }
1396
+
1397
+ // inline nested if-else expressions: onTrue branch
1398
+ // ifElse(X, ifElse(X, a, b), c) ==> ifElse(X, a, c)
1399
+ if (onTrueExpression._type === "IfElse" && _checkExpressionsAreEqual(conditionExpression, onTrueExpression.condition)) {
1400
+ onTrueExpression = onTrueExpression.onTrue;
1401
+ }
1402
+
1403
+ // inline nested if-else expressions: onFalse branch
1404
+ // ifElse(X, a, ifElse(X, b, c)) ==> ifElse(X, a, c)
1405
+ if (onFalseExpression._type === "IfElse" && _checkExpressionsAreEqual(conditionExpression, onFalseExpression.condition)) {
1406
+ onFalseExpression = onFalseExpression.onFalse;
1407
+ }
1408
+
1409
+ // (if true then a else b) ~~> a
1410
+ // (if false then a else b) ~~> b
1411
+ if (isConstant(conditionExpression)) {
1412
+ return conditionExpression.value ? onTrueExpression : onFalseExpression;
1413
+ }
1414
+
1415
+ // if (isConstantBoolean(onTrueExpression) || isConstantBoolean(onFalseExpression)) {
1416
+ // return or(and(condition, onTrueExpression as Expression<boolean>), and(not(condition), onFalseExpression as Expression<boolean>)) as Expression<T>
1417
+ // }
1418
+
1419
+ // (if X then a else a) ~~> a
1420
+ if (_checkExpressionsAreEqual(onTrueExpression, onFalseExpression)) {
1421
+ return onTrueExpression;
1422
+ }
1423
+
1424
+ // if X then a else false ~~> X && a
1425
+ if (isFalse(onFalseExpression)) {
1426
+ return and(conditionExpression, onTrueExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
1427
+ }
1428
+
1429
+ // if X then a else true ~~> !X || a
1430
+ if (isTrue(onFalseExpression)) {
1431
+ return or(not(conditionExpression), onTrueExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
1432
+ }
1433
+
1434
+ // if X then false else a ~~> !X && a
1435
+ if (isFalse(onTrueExpression)) {
1436
+ return and(not(conditionExpression), onFalseExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
1437
+ }
1438
+
1439
+ // if X then true else a ~~> X || a
1440
+ if (isTrue(onTrueExpression)) {
1441
+ return or(conditionExpression, onFalseExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
1442
+ }
1443
+ if (hasUnresolvableExpression(conditionExpression, onTrueExpression, onFalseExpression)) {
1444
+ return unresolvableExpression;
1445
+ }
1446
+ if (isComplexTypeExpression(condition) || isComplexTypeExpression(onTrue) || isComplexTypeExpression(onFalse)) {
1447
+ let pathIdx = 0;
1448
+ const myIfElseExpression = formatResult([condition, onTrue, onFalse], "sap.fe.core.formatters.StandardFormatter#ifElse");
1449
+ const allParts = [];
1450
+ transformRecursively(
1451
+ myIfElseExpression,
1452
+ "PathInModel",
1453
+ (constantPath: PathInModelExpression<unknown>) => {
1454
+ allParts.push(constantPath);
1455
+ return pathInModel(`$${pathIdx++}`, "$");
1456
+ },
1457
+ true
1458
+ );
1459
+ allParts.unshift(constant(JSON.stringify(myIfElseExpression)));
1460
+ return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
1461
+ }
1462
+ return {
1463
+ _type: "IfElse",
1464
+ condition: conditionExpression,
1465
+ onTrue: onTrueExpression,
1466
+ onFalse: onFalseExpression
1467
+ };
1468
+ }
1469
+
1470
+ /**
1471
+ * Checks whether the current expression has a reference to the default model (undefined).
1472
+ *
1473
+ * @param expression The expression to evaluate
1474
+ * @returns `true` if there is a reference to the default context
1475
+ */
1476
+ function hasReferenceToDefaultContext(expression: BindingToolkitExpression<unknown>): boolean {
1477
+ switch (expression._type) {
1478
+ case "Constant":
1479
+ case "Formatter":
1480
+ case "ComplexType":
1481
+ return false;
1482
+ case "Set":
1483
+ return expression.operands.some(hasReferenceToDefaultContext);
1484
+ case "PathInModel":
1485
+ return expression.modelName === undefined;
1486
+ case "Comparison":
1487
+ return hasReferenceToDefaultContext(expression.operand1) || hasReferenceToDefaultContext(expression.operand2);
1488
+ case "IfElse":
1489
+ return (
1490
+ hasReferenceToDefaultContext(expression.condition) ||
1491
+ hasReferenceToDefaultContext(expression.onTrue) ||
1492
+ hasReferenceToDefaultContext(expression.onFalse)
1493
+ );
1494
+ case "Not":
1495
+ case "Truthy":
1496
+ return hasReferenceToDefaultContext(expression.operand);
1497
+ default:
1498
+ return false;
1499
+ }
1500
+ }
1501
+
1502
+ // This is one case where any does make sense...
1503
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1504
+ export type Fn<T> = ((...params: any) => T | Promise<T>) & {
1505
+ __functionName: string;
1506
+ };
1507
+
1508
+ /**
1509
+ * @typedef WrappedTuple
1510
+ */
1511
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1512
+ // @ts-ignore
1513
+ type WrappedTuple<T> = { [K in keyof T]: WrappedTuple<T[K]> | ExpressionOrPrimitive<T[K]> | undefined };
1514
+
1515
+ // So, this works but I cannot get it to compile :D, but it still does what is expected...
1516
+
1517
+ /**
1518
+ * A function reference or a function name.
1519
+ */
1520
+ type FunctionOrName<T> = Fn<T> | string;
1521
+
1522
+ /**
1523
+ * Function parameters, either derived from the function or an untyped array.
1524
+ */
1525
+ type FunctionParameters<T, F extends FunctionOrName<T>> = F extends Fn<T> ? Parameters<F> : unknown[];
1526
+
1527
+ /**
1528
+ * Calls a formatter function to process the parameters.
1529
+ * If requireContext is set to true and no context is passed a default context will be added automatically.
1530
+ *
1531
+ * @template T
1532
+ * @template U
1533
+ * @param parameters The list of parameter that should match the type and number of the formatter function
1534
+ * @param formatterFunction The function to call
1535
+ * @param [contextEntityType] If no parameter refers to the context then we use this information to add a reference to the keys from the entity type.
1536
+ * @param [ignoreComplexType] Whether to ignore the transgformation to the StandardFormatter or not
1537
+ * @returns The corresponding expression
1538
+ */
1539
+ export function formatResult<T, U extends Fn<T>>(
1540
+ parameters: WrappedTuple<Parameters<U>>,
1541
+ formatterFunction: U | string,
1542
+ contextEntityType?: EntityType,
1543
+ ignoreComplexType = false
1544
+ ): BindingToolkitExpression<T> {
1545
+ const parameterExpressions = (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive);
1546
+
1547
+ if (hasUnresolvableExpression(...parameterExpressions)) {
1548
+ return unresolvableExpression;
1549
+ }
1550
+ if (contextEntityType) {
1551
+ // Otherwise, if the context is required and no context is provided make sure to add the default binding
1552
+ if (!parameterExpressions.some(hasReferenceToDefaultContext)) {
1553
+ contextEntityType.keys.forEach((key) => parameterExpressions.push(pathInModel(key.name, "")));
1554
+ }
1555
+ }
1556
+ let functionName = "";
1557
+ if (typeof formatterFunction === "string") {
1558
+ functionName = formatterFunction;
1559
+ } else {
1560
+ functionName = formatterFunction.__functionName;
1561
+ }
1562
+ // FormatterName can be of format sap.fe.core.xxx#methodName to have multiple formatter in one class
1563
+ const [formatterClass, formatterName] = functionName.split("#");
1564
+
1565
+ // In some case we also cannot call directly a function because of too complex input, in that case we need to convert to a simpler function call
1566
+ if (
1567
+ !ignoreComplexType &&
1568
+ (parameterExpressions.some(isComplexTypeExpression) ||
1569
+ parameterExpressions.some(isConcatExpression) ||
1570
+ parameterExpressions.some(isIfElseExpression))
1571
+ ) {
1572
+ let pathIdx = 0;
1573
+ const myFormatExpression = formatResult(parameterExpressions, functionName, undefined, true);
1574
+ const allParts = [];
1575
+ transformRecursively(
1576
+ myFormatExpression,
1577
+ "PathInModel",
1578
+ (constantPath: PathInModelExpression<unknown>) => {
1579
+ allParts.push(constantPath);
1580
+ return pathInModel(`$${pathIdx++}`, "$");
1581
+ },
1582
+ true
1583
+ );
1584
+ allParts.unshift(constant(JSON.stringify(myFormatExpression)));
1585
+ return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
1586
+ } else if (!!formatterName && formatterName.length > 0) {
1587
+ parameterExpressions.unshift(constant(formatterName));
1588
+ }
1589
+
1590
+ return {
1591
+ _type: "Formatter",
1592
+ fn: formatterClass,
1593
+ parameters: parameterExpressions
1594
+ };
1595
+ }
1596
+
1597
+ type BindingConstraints = {
1598
+ scale?: number;
1599
+ precision?: number;
1600
+ maxLength?: number;
1601
+ nullable?: boolean;
1602
+ minimum?: string;
1603
+ maximum?: string;
1604
+ isDigitSequence?: boolean;
1605
+ V4?: boolean;
1606
+ };
1607
+ export function setUpConstraints(targetMapping: TypeMappingDefinition, property: Property | ActionParameter): BindingConstraints {
1608
+ const constraints: BindingConstraints = {};
1609
+ if (targetMapping?.constraints?.$Scale && property.scale !== undefined) {
1610
+ constraints.scale = property.scale;
1611
+ }
1612
+ if (targetMapping?.constraints?.$Precision && property.precision !== undefined) {
1613
+ constraints.precision = property.precision;
1614
+ }
1615
+ if (targetMapping?.constraints?.$MaxLength && property.maxLength !== undefined) {
1616
+ constraints.maxLength = property.maxLength;
1617
+ }
1618
+ if (property.nullable === false) {
1619
+ constraints.nullable = false;
1620
+ }
1621
+ if (targetMapping?.constraints?.["@Org.OData.Validation.V1.Minimum/$Decimal"] && !isNaN(property.annotations?.Validation?.Minimum)) {
1622
+ constraints.minimum = `${property.annotations?.Validation?.Minimum}`;
1623
+ }
1624
+ if (targetMapping?.constraints?.["@Org.OData.Validation.V1.Maximum/$Decimal"] && !isNaN(property.annotations?.Validation?.Maximum)) {
1625
+ constraints.maximum = `${property.annotations?.Validation?.Maximum}`;
1626
+ }
1627
+ if (
1628
+ property.annotations?.Common?.IsDigitSequence &&
1629
+ targetMapping.type === "sap.ui.model.odata.type.String" &&
1630
+ targetMapping?.constraints?.["@com.sap.vocabularies.Common.v1.IsDigitSequence"]
1631
+ ) {
1632
+ constraints.isDigitSequence = true;
1633
+ }
1634
+ if (targetMapping?.constraints?.$V4) {
1635
+ constraints.V4 = true;
1636
+ }
1637
+ return constraints;
1638
+ }
1639
+
1640
+ /**
1641
+ * Generates the binding expression for the property, and sets up the formatOptions and constraints.
1642
+ *
1643
+ * @param property The Property for which we are setting up the binding
1644
+ * @param propertyBindingExpression The BindingExpression of the property above. Serves as the basis to which information can be added
1645
+ * @param ignoreConstraints Ignore constraints of the property
1646
+ * @returns The binding expression for the property with formatOptions and constraints
1647
+ */
1648
+ export function formatWithTypeInformation<T>(
1649
+ property: Property | ActionParameter,
1650
+ propertyBindingExpression: BindingToolkitExpression<string>,
1651
+ ignoreConstraints = false
1652
+ ): PathInModelExpression<T> {
1653
+ const outExpression: PathInModelExpression<unknown> = propertyBindingExpression as PathInModelExpression<unknown>;
1654
+ if (property._type !== "Property" && property._type !== "ActionParameter") {
1655
+ return outExpression;
1656
+ }
1657
+ const targetMapping = EDM_TYPE_MAPPING[property.type];
1658
+ if (!targetMapping) {
1659
+ return outExpression;
1660
+ }
1661
+ if (!outExpression.formatOptions) {
1662
+ outExpression.formatOptions = {};
1663
+ }
1664
+ outExpression.constraints = {};
1665
+
1666
+ outExpression.type = targetMapping.type;
1667
+ if (!ignoreConstraints) {
1668
+ outExpression.constraints = setUpConstraints(targetMapping, property);
1669
+ }
1670
+
1671
+ if (
1672
+ (outExpression?.type?.indexOf("sap.ui.model.odata.type.Int") === 0 && outExpression?.type !== "sap.ui.model.odata.type.Int64") ||
1673
+ outExpression?.type === "sap.ui.model.odata.type.Double"
1674
+ ) {
1675
+ outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
1676
+ parseAsString: false
1677
+ });
1678
+ }
1679
+ if (outExpression.type === "sap.ui.model.odata.type.String" && isProperty(property)) {
1680
+ if (!outExpression.constraints?.isDigitSequence || outExpression.constraints?.nullable === false) {
1681
+ outExpression.formatOptions.parseKeepsEmptyString = true;
1682
+ }
1683
+ const fiscalType = getFiscalType(property);
1684
+ if (fiscalType) {
1685
+ outExpression.formatOptions.fiscalType = fiscalType;
1686
+ outExpression.type = "sap.fe.core.type.FiscalDate";
1687
+ }
1688
+ }
1689
+ const typesSupportingParseEmptyValueToZero = [
1690
+ "sap.ui.model.odata.type.Int16",
1691
+ "sap.ui.model.odata.type.Int32",
1692
+ "sap.ui.model.odata.type.Int64",
1693
+ "sap.ui.model.odata.type.Single",
1694
+ "sap.ui.model.odata.type.Double",
1695
+ "sap.ui.model.odata.type.Decimal",
1696
+ "sap.ui.model.odata.type.Byte",
1697
+ "sap.ui.model.odata.type.SByte"
1698
+ ];
1699
+ if (
1700
+ outExpression.constraints?.nullable === false &&
1701
+ outExpression.type &&
1702
+ typesSupportingParseEmptyValueToZero.includes(outExpression.type)
1703
+ ) {
1704
+ outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
1705
+ parseEmptyValueToZero: true
1706
+ });
1707
+ }
1708
+ if (
1709
+ outExpression.constraints?.nullable !== false &&
1710
+ (outExpression.type === "sap.ui.model.odata.type.Int64" || outExpression.type === "sap.ui.model.odata.type.Decimal")
1711
+ ) {
1712
+ outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
1713
+ emptyString: ""
1714
+ });
1715
+ }
1716
+
1717
+ return outExpression;
1718
+ }
1719
+
1720
+ export const getFiscalType = function (property: Property): string | undefined {
1721
+ if (property.annotations?.Common?.IsFiscalYear) {
1722
+ return CommonAnnotationTerms.IsFiscalYear;
1723
+ }
1724
+ if (property.annotations?.Common?.IsFiscalPeriod) {
1725
+ return CommonAnnotationTerms.IsFiscalPeriod;
1726
+ }
1727
+ if (property.annotations?.Common?.IsFiscalYearPeriod) {
1728
+ return CommonAnnotationTerms.IsFiscalYearPeriod;
1729
+ }
1730
+ if (property.annotations?.Common?.IsFiscalQuarter) {
1731
+ return CommonAnnotationTerms.IsFiscalQuarter;
1732
+ }
1733
+ if (property.annotations?.Common?.IsFiscalYearQuarter) {
1734
+ return CommonAnnotationTerms.IsFiscalYearQuarter;
1735
+ }
1736
+ if (property.annotations?.Common?.IsFiscalWeek) {
1737
+ return CommonAnnotationTerms.IsFiscalWeek;
1738
+ }
1739
+ if (property.annotations?.Common?.IsFiscalYearWeek) {
1740
+ return CommonAnnotationTerms.IsFiscalYearWeek;
1741
+ }
1742
+ if (property.annotations?.Common?.IsDayOfFiscalYear) {
1743
+ return CommonAnnotationTerms.IsDayOfFiscalYear;
1744
+ }
1745
+ };
1746
+
1747
+ /**
1748
+ * Calls a complex type to process the parameters.
1749
+ * If requireContext is set to true and no context is passed, a default context will be added automatically.
1750
+ *
1751
+ * @template T
1752
+ * @template U
1753
+ * @param parameters The list of parameters that should match the type for the complex type=
1754
+ * @param type The complex type to use
1755
+ * @param [contextEntityType] The context entity type to consider
1756
+ * @param oFormatOptions
1757
+ * @returns The corresponding expression
1758
+ */
1759
+ export function addTypeInformation<T, U extends Fn<T>>(
1760
+ parameters: WrappedTuple<Parameters<U>>,
1761
+ type: string,
1762
+ contextEntityType?: EntityType,
1763
+ oFormatOptions?: ComplexTypeFormatOptions
1764
+ ): UnresolvablePathExpression | ComplexTypeExpression<T> | ConstantExpression<T> {
1765
+ const parameterExpressions = (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive);
1766
+ if (hasUnresolvableExpression(...parameterExpressions)) {
1767
+ return unresolvableExpression;
1768
+ }
1769
+ // If there is only one parameter and it is a constant and we don't expect the context then return the constant
1770
+ if (parameterExpressions.length === 1 && isConstant(parameterExpressions[0]) && !contextEntityType) {
1771
+ return parameterExpressions[0] as ConstantExpression<T>;
1772
+ } else if (contextEntityType) {
1773
+ // Otherwise, if the context is required and no context is provided make sure to add the default binding
1774
+ if (!parameterExpressions.some(hasReferenceToDefaultContext)) {
1775
+ contextEntityType.keys.forEach((key) => parameterExpressions.push(pathInModel(key.name, "")));
1776
+ }
1777
+ }
1778
+ oFormatOptions = _getComplexTypeFormatOptionsFromFirstParam(parameters[0], oFormatOptions);
1779
+
1780
+ if (type === "sap.ui.model.odata.type.Unit") {
1781
+ const uomPath = pathInModel("/##@@requestUnitsOfMeasure");
1782
+ uomPath.targetType = "any";
1783
+ uomPath.mode = "OneTime";
1784
+ parameterExpressions.push(uomPath);
1785
+ } else if (type === "sap.ui.model.odata.type.Currency") {
1786
+ const currencyPath = pathInModel("/##@@requestCurrencyCodes");
1787
+ currencyPath.targetType = "any";
1788
+ currencyPath.mode = "OneTime";
1789
+ parameterExpressions.push(currencyPath);
1790
+ }
1791
+
1792
+ return {
1793
+ _type: "ComplexType",
1794
+ type: type,
1795
+ formatOptions: oFormatOptions || {},
1796
+ parameters: {},
1797
+ bindingParameters: parameterExpressions
1798
+ };
1799
+ }
1800
+
1801
+ type ComplexTypeFormatOptions = {
1802
+ showNumber?: boolean;
1803
+ showMeasure?: boolean;
1804
+ parseAsString?: boolean;
1805
+ parseKeepsEmptyString?: boolean;
1806
+ emptyString?: string | number | null;
1807
+ showTime?: boolean;
1808
+ showDate?: boolean;
1809
+ showTimezone?: boolean;
1810
+ };
1811
+
1812
+ /**
1813
+ * Process the formatOptions for a complexType based on the first parameter.
1814
+ *
1815
+ * @param param The first parameter of the complex type
1816
+ * @param formatOptions Initial formatOptions
1817
+ * @returns The modified formatOptions
1818
+ */
1819
+ function _getComplexTypeFormatOptionsFromFirstParam<T, U extends Fn<T>>(
1820
+ param: Parameters<U>,
1821
+ formatOptions: undefined | ComplexTypeFormatOptions
1822
+ ): ComplexTypeFormatOptions | undefined {
1823
+ // if showMeasure is set to false we want to not parse as string to see the 0
1824
+ // we do that also for all bindings because otherwise the mdc Field isn't editable
1825
+ if (
1826
+ !(formatOptions && formatOptions.showNumber === false) &&
1827
+ (param?.type?.indexOf("sap.ui.model.odata.type.Int") === 0 ||
1828
+ param?.type === "sap.ui.model.odata.type.Decimal" ||
1829
+ param?.type === "sap.ui.model.odata.type.Double")
1830
+ ) {
1831
+ if (param?.type === "sap.ui.model.odata.type.Int64" || param?.type === "sap.ui.model.odata.type.Decimal") {
1832
+ //sap.ui.model.odata.type.Int64 do not support parseAsString false
1833
+ formatOptions = formatOptions?.showMeasure === false ? { showMeasure: false } : {};
1834
+ } else {
1835
+ formatOptions = formatOptions?.showMeasure === false ? { parseAsString: false, showMeasure: false } : { parseAsString: false };
1836
+ }
1837
+ formatOptions.emptyString = null;
1838
+ }
1839
+ return formatOptions;
1840
+ }
1841
+ /**
1842
+ * Function call, optionally with arguments.
1843
+ *
1844
+ * @param func Function name or reference to function
1845
+ * @param parameters Arguments
1846
+ * @param on Object to call the function on
1847
+ * @param isFormattingFn
1848
+ * @returns Expression representing the function call (not the result of the function call!)
1849
+ */
1850
+ export function fn<T, U extends FunctionOrName<T>>(
1851
+ func: U,
1852
+ parameters: WrappedTuple<FunctionParameters<T, U>>,
1853
+ on?: ExpressionOrPrimitive<object>,
1854
+ isFormattingFn = false
1855
+ ): FunctionExpression<T> {
1856
+ const functionName = typeof func === "string" ? func : func.__functionName;
1857
+ return {
1858
+ _type: "Function",
1859
+ obj: on !== undefined ? wrapPrimitive(on) : undefined,
1860
+ fn: functionName,
1861
+ isFormattingFn: isFormattingFn,
1862
+ parameters: (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive)
1863
+ };
1864
+ }
1865
+
1866
+ /**
1867
+ * Shortcut function to determine if a binding value is null, undefined or empty.
1868
+ *
1869
+ * @param expression
1870
+ * @returns A Boolean expression evaluating the fact that the current element is empty
1871
+ */
1872
+ export function isEmpty(expression: BindingToolkitExpression<string>): BindingToolkitExpression<boolean> {
1873
+ const aBindings: ExpressionOrPrimitive<boolean>[] = [];
1874
+ transformRecursively(expression, "PathInModel", (expr) => {
1875
+ aBindings.push(or(equal(expr, ""), equal(expr, undefined), equal(expr, null)));
1876
+ return expr;
1877
+ });
1878
+ return and(...aBindings);
1879
+ }
1880
+
1881
+ export function concat(...inExpressions: ExpressionOrPrimitive<string>[]): BindingToolkitExpression<string> {
1882
+ const expressions = inExpressions.map(wrapPrimitive);
1883
+ if (hasUnresolvableExpression(...expressions)) {
1884
+ return unresolvableExpression;
1885
+ }
1886
+ if (expressions.every(isConstant)) {
1887
+ return constant(
1888
+ expressions.reduce((concatenated: string, value) => {
1889
+ if (value.value !== undefined && value.value !== null) {
1890
+ return concatenated + value.value.toString();
1891
+ }
1892
+ return concatenated;
1893
+ }, "")
1894
+ );
1895
+ } else if (expressions.some(isComplexTypeExpression)) {
1896
+ let pathIdx = 0;
1897
+ const myConcatExpression = formatResult(expressions, "sap.fe.core.formatters.StandardFormatter#concat", undefined, true);
1898
+ const allParts = [];
1899
+ transformRecursively(myConcatExpression, "PathInModel", (constantPath: PathInModelExpression<unknown>) => {
1900
+ allParts.push(constantPath);
1901
+ return pathInModel(`$${pathIdx++}`, "$");
1902
+ });
1903
+ allParts.unshift(constant(JSON.stringify(myConcatExpression)));
1904
+ return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
1905
+ }
1906
+ return {
1907
+ _type: "Concat",
1908
+ expressions: expressions
1909
+ };
1910
+ }
1911
+
1912
+ type ExpressionTypeToExpression<T extends ExpressionType, V> = T extends "Constant"
1913
+ ? ConstantExpression<V>
1914
+ : T extends "PathInModel"
1915
+ ? PathInModelExpression<V>
1916
+ : T extends "Function"
1917
+ ? FunctionExpression<V>
1918
+ : T extends "Unresolvable"
1919
+ ? UnresolvablePathExpression
1920
+ : BindingToolkitExpression<V>;
1921
+
1922
+ export type TransformFunction<T, V> = (expressionPart: T) => BindingToolkitExpression<V>;
1923
+ export type ExpressionType = Pick<BindingToolkitExpression<unknown>, "_type">["_type"];
1924
+ export function transformRecursively<T extends PrimitiveType | unknown, ET extends ExpressionType>(
1925
+ inExpression: BindingToolkitExpression<T>,
1926
+ expressionType: ET,
1927
+ transformFunction: TransformFunction<ExpressionTypeToExpression<ET, T>, T>,
1928
+ includeAllExpression = false
1929
+ ): BindingToolkitExpression<T> {
1930
+ let expression: BindingToolkitExpression<T> = inExpression;
1931
+ switch (expression._type) {
1932
+ case "Function":
1933
+ case "Formatter":
1934
+ expression.parameters = expression.parameters.map((parameter) =>
1935
+ transformRecursively(parameter as BindingToolkitExpression<T>, expressionType, transformFunction, includeAllExpression)
1936
+ );
1937
+ break;
1938
+ case "Concat":
1939
+ expression.expressions = expression.expressions.map((subExpression) =>
1940
+ transformRecursively(
1941
+ subExpression,
1942
+ expressionType,
1943
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, string>, string>,
1944
+ includeAllExpression
1945
+ )
1946
+ );
1947
+ expression = concat(...expression.expressions) as BindingToolkitExpression<T>;
1948
+ break;
1949
+ case "ComplexType":
1950
+ expression.bindingParameters = expression.bindingParameters.map((bindingParameter) =>
1951
+ transformRecursively(
1952
+ bindingParameter as BindingToolkitExpression<T>,
1953
+ expressionType,
1954
+ transformFunction,
1955
+ includeAllExpression
1956
+ )
1957
+ );
1958
+ break;
1959
+ case "IfElse":
1960
+ const onTrue = transformRecursively(
1961
+ expression.onTrue,
1962
+ expressionType,
1963
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
1964
+ includeAllExpression
1965
+ );
1966
+ const onFalse = transformRecursively(
1967
+ expression.onFalse,
1968
+ expressionType,
1969
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
1970
+ includeAllExpression
1971
+ );
1972
+ let condition = expression.condition;
1973
+ if (includeAllExpression) {
1974
+ condition = transformRecursively(
1975
+ expression.condition,
1976
+ expressionType,
1977
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
1978
+ includeAllExpression
1979
+ );
1980
+ }
1981
+ expression = ifElse(condition, onTrue, onFalse) as BindingToolkitExpression<T>;
1982
+ break;
1983
+ case "Not":
1984
+ if (includeAllExpression) {
1985
+ const operand = transformRecursively(
1986
+ expression.operand,
1987
+ expressionType,
1988
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
1989
+ includeAllExpression
1990
+ );
1991
+ expression = not(operand) as BindingToolkitExpression<T>;
1992
+ }
1993
+ break;
1994
+ case "Truthy":
1995
+ break;
1996
+ case "Set":
1997
+ if (includeAllExpression) {
1998
+ const operands = expression.operands.map((operand) =>
1999
+ transformRecursively(
2000
+ operand,
2001
+ expressionType,
2002
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
2003
+ includeAllExpression
2004
+ )
2005
+ );
2006
+ expression =
2007
+ expression.operator === "||"
2008
+ ? (or(...operands) as BindingToolkitExpression<T>)
2009
+ : (and(...operands) as BindingToolkitExpression<T>);
2010
+ }
2011
+ break;
2012
+ case "Comparison":
2013
+ if (includeAllExpression) {
2014
+ const operand1 = transformRecursively(
2015
+ expression.operand1 as BindingToolkitExpression<T>,
2016
+ expressionType,
2017
+ transformFunction,
2018
+ includeAllExpression
2019
+ );
2020
+ const operand2 = transformRecursively(
2021
+ expression.operand2 as BindingToolkitExpression<T>,
2022
+ expressionType,
2023
+ transformFunction,
2024
+ includeAllExpression
2025
+ );
2026
+ expression = comparison(expression.operator, operand1, operand2) as BindingToolkitExpression<T>;
2027
+ }
2028
+ break;
2029
+ case "Constant":
2030
+ const constantValue: Record<string, BindingToolkitExpression<unknown>> = expression.value as Record<
2031
+ string,
2032
+ BindingToolkitExpression<unknown>
2033
+ >;
2034
+ if (typeof constantValue === "object" && constantValue) {
2035
+ Object.keys(constantValue).forEach((key) => {
2036
+ constantValue[key] = transformRecursively(
2037
+ constantValue[key],
2038
+ expressionType,
2039
+ transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
2040
+ includeAllExpression
2041
+ );
2042
+ });
2043
+ }
2044
+ break;
2045
+ case "Ref":
2046
+ case "Length":
2047
+ case "PathInModel":
2048
+ case "EmbeddedBinding":
2049
+ case "EmbeddedExpressionBinding":
2050
+ case "Unresolvable":
2051
+ // Do nothing
2052
+ break;
2053
+ }
2054
+ if (expressionType === expression._type) {
2055
+ expression = transformFunction(inExpression as ExpressionTypeToExpression<ET, T>);
2056
+ }
2057
+ return expression;
2058
+ }
2059
+
2060
+ export type CompiledBindingToolkitExpression = string | undefined;
2061
+
2062
+ const needParenthesis = function <T extends PrimitiveType>(expr: ExpressionOrPrimitive<T>): boolean {
2063
+ return (
2064
+ !isConstant(expr) &&
2065
+ !isPathInModelExpression(expr) &&
2066
+ isBindingToolkitExpression(expr) &&
2067
+ expr._type !== "IfElse" &&
2068
+ expr._type !== "Function"
2069
+ );
2070
+ };
2071
+
2072
+ /**
2073
+ * Compiles a constant object to a string.
2074
+ *
2075
+ * @param expr
2076
+ * @param isNullable
2077
+ * @returns The compiled string
2078
+ */
2079
+ function compileConstantObject(expr: ConstantExpression<object>, isNullable = false): string {
2080
+ if (isNullable && Object.keys(expr.value).length === 0) {
2081
+ return "";
2082
+ }
2083
+ const objects = expr.value as PlainExpressionObject;
2084
+ const properties: string[] = [];
2085
+ Object.keys(objects).forEach((key) => {
2086
+ const value = objects[key];
2087
+ const childResult = compileExpression(value, true, false, isNullable);
2088
+ if (childResult && childResult.length > 0) {
2089
+ properties.push(`${key}: ${childResult}`);
2090
+ }
2091
+ });
2092
+ return `{${properties.join(", ")}}`;
2093
+ }
2094
+
2095
+ /**
2096
+ * Compiles a Constant Binding Expression.
2097
+ *
2098
+ * @param expr
2099
+ * @param embeddedInBinding
2100
+ * @param isNullable
2101
+ * @param doNotStringify
2102
+ * @returns The compiled string
2103
+ */
2104
+ export function compileConstant<T extends PrimitiveType>(
2105
+ expr: ConstantExpression<T>,
2106
+ embeddedInBinding: boolean,
2107
+ isNullable?: boolean,
2108
+ doNotStringify?: false
2109
+ ): CompiledBindingToolkitExpression;
2110
+ export function compileConstant<T extends PrimitiveType>(
2111
+ expr: ConstantExpression<T>,
2112
+ embeddedInBinding: boolean,
2113
+ isNullable?: boolean,
2114
+ doNotStringify?: true
2115
+ ): PrimitiveType;
2116
+ export function compileConstant<T extends PrimitiveType>(
2117
+ expr: ConstantExpression<T>,
2118
+ embeddedInBinding: boolean,
2119
+ isNullable = false,
2120
+ doNotStringify = false
2121
+ ): CompiledBindingToolkitExpression | PrimitiveType {
2122
+ if (expr.value === null) {
2123
+ return doNotStringify ? null : "null";
2124
+ }
2125
+ if (expr.value === undefined) {
2126
+ return doNotStringify ? undefined : "undefined";
2127
+ }
2128
+ if (typeof expr.value === "object") {
2129
+ if (Array.isArray(expr.value)) {
2130
+ const entries = expr.value.map((expression) => compileExpression(expression, true));
2131
+ return `[${entries.join(", ")}]`;
2132
+ } else {
2133
+ return compileConstantObject(expr as ConstantExpression<object>, isNullable);
2134
+ }
2135
+ }
2136
+
2137
+ if (embeddedInBinding) {
2138
+ switch (typeof expr.value) {
2139
+ case "number":
2140
+ case "bigint":
2141
+ case "boolean":
2142
+ return expr.value.toString();
2143
+ case "string":
2144
+ return `'${escapeXmlAttribute(expr.value.toString())}'`;
2145
+ default:
2146
+ return "";
2147
+ }
2148
+ } else {
2149
+ return doNotStringify ? expr.value : expr.value.toString();
2150
+ }
2151
+ }
2152
+
2153
+ /**
2154
+ * Generates the binding string for a Binding expression.
2155
+ *
2156
+ * @param expressionForBinding The expression to compile
2157
+ * @param embeddedInBinding Whether the expression to compile is embedded into another expression
2158
+ * @param embeddedSeparator The binding value evaluator ($ or % depending on whether we want to force the type or not)
2159
+ * @returns The corresponding expression binding
2160
+ */
2161
+ function compilePathInModelExpression<T extends PrimitiveType>(
2162
+ expressionForBinding: PathInModelExpression<T>,
2163
+ embeddedInBinding: boolean,
2164
+ embeddedSeparator: string
2165
+ ): CompiledBindingToolkitExpression {
2166
+ if (
2167
+ expressionForBinding.type ||
2168
+ expressionForBinding.parameters ||
2169
+ expressionForBinding.targetType ||
2170
+ expressionForBinding.formatOptions ||
2171
+ expressionForBinding.constraints
2172
+ ) {
2173
+ // This is now a complex binding definition, let's prepare for it
2174
+ const complexBindingDefinition = {
2175
+ path: compilePathInModel(expressionForBinding),
2176
+ type: expressionForBinding.type,
2177
+ targetType: expressionForBinding.targetType,
2178
+ parameters: expressionForBinding.parameters,
2179
+ formatOptions: expressionForBinding.formatOptions,
2180
+ constraints: expressionForBinding.constraints
2181
+ };
2182
+ const outBinding = compileExpression(complexBindingDefinition, false, false, true);
2183
+ if (embeddedInBinding) {
2184
+ return `${embeddedSeparator}${outBinding}`;
2185
+ }
2186
+ return outBinding;
2187
+ } else if (embeddedInBinding) {
2188
+ return `${embeddedSeparator}{${compilePathInModel(expressionForBinding)}}`;
2189
+ } else {
2190
+ return `{${compilePathInModel(expressionForBinding)}}`;
2191
+ }
2192
+ }
2193
+
2194
+ function compileComplexTypeExpression<T extends PrimitiveType>(expression: ComplexTypeExpression<T>): string {
2195
+ if (expression.bindingParameters.length === 1) {
2196
+ return `{${compilePathParameter(expression.bindingParameters[0] as BindingToolkitExpression<PrimitiveType>, true)}, type: '${
2197
+ expression.type
2198
+ }'}`;
2199
+ }
2200
+
2201
+ let outputEnd = `], type: '${expression.type}'`;
2202
+ if (hasElements(expression.formatOptions)) {
2203
+ outputEnd += `, formatOptions: ${compileExpression(expression.formatOptions)}`;
2204
+ }
2205
+ if (hasElements(expression.parameters)) {
2206
+ outputEnd += `, parameters: ${compileExpression(expression.parameters)}`;
2207
+ }
2208
+ outputEnd += "}";
2209
+
2210
+ return `{mode:'TwoWay', parts:[${expression.bindingParameters
2211
+ .map((param) => compilePathParameter(param as BindingToolkitExpression<PrimitiveType>))
2212
+ .join(",")}${outputEnd}`;
2213
+ }
2214
+
2215
+ /**
2216
+ * Wrap the compiled binding string as required depending on its context.
2217
+ *
2218
+ * @param expression The compiled expression
2219
+ * @param embeddedInBinding True if the compiled expression is to be embedded in a binding
2220
+ * @param parenthesisRequired True if the embedded binding needs to be wrapped in parethesis so that it is evaluated as one
2221
+ * @returns Finalized compiled expression
2222
+ */
2223
+ export function wrapBindingExpression(
2224
+ expression: string,
2225
+ embeddedInBinding: boolean,
2226
+ parenthesisRequired = false
2227
+ ): CompiledBindingToolkitExpression {
2228
+ if (embeddedInBinding) {
2229
+ if (parenthesisRequired) {
2230
+ return `(${expression})`;
2231
+ } else {
2232
+ return expression;
2233
+ }
2234
+ } else {
2235
+ return `{= ${expression}}`;
2236
+ }
2237
+ }
2238
+
2239
+ /**
2240
+ * Compile an expression into an expression binding.
2241
+ *
2242
+ * @template T The target type
2243
+ * @param expression The expression to compile
2244
+ * @param embeddedInBinding Whether the expression to compile is embedded into another expression
2245
+ * @param keepTargetType Keep the target type of the embedded bindings instead of casting them to any
2246
+ * @param isNullable Whether binding expression can resolve to empty string or not
2247
+ * @returns The corresponding expression binding
2248
+ */
2249
+ export function compileExpression<T extends PrimitiveType>(
2250
+ expression: ExpressionOrPrimitive<T>,
2251
+ embeddedInBinding = false,
2252
+ keepTargetType = false,
2253
+ isNullable = false
2254
+ ): CompiledBindingToolkitExpression {
2255
+ const expr = wrapPrimitive(expression);
2256
+ const embeddedSeparator = keepTargetType ? "$" : "%";
2257
+
2258
+ switch (expr._type) {
2259
+ case "Unresolvable":
2260
+ return undefined;
2261
+
2262
+ case "Constant":
2263
+ return compileConstant(expr, embeddedInBinding, isNullable);
2264
+
2265
+ case "Ref":
2266
+ return expr.ref || "null";
2267
+
2268
+ case "Function":
2269
+ let hasEmbeddedFunctionCallOrBinding = false;
2270
+ if (expr.isFormattingFn) {
2271
+ transformRecursively(
2272
+ expr,
2273
+ "Function",
2274
+ (subFn) => {
2275
+ if (subFn !== expr && subFn.obj === undefined) {
2276
+ hasEmbeddedFunctionCallOrBinding = true;
2277
+ }
2278
+ return subFn;
2279
+ },
2280
+ true
2281
+ );
2282
+ transformRecursively(
2283
+ expr,
2284
+ "Constant",
2285
+ (subFn: ConstantExpression<T>) => {
2286
+ if ((subFn as unknown) !== expr && typeof subFn.value === "object") {
2287
+ transformRecursively(subFn, "PathInModel", (subSubFn) => {
2288
+ hasEmbeddedFunctionCallOrBinding = true;
2289
+ return subSubFn;
2290
+ });
2291
+ }
2292
+
2293
+ return subFn;
2294
+ },
2295
+ true
2296
+ );
2297
+ }
2298
+ const argumentString = `${expr.parameters.map((arg) => compileExpression(arg, true)).join(", ")}`;
2299
+ let fnCall =
2300
+ expr.obj === undefined
2301
+ ? `${expr.fn}(${argumentString})`
2302
+ : `${compileExpression(expr.obj, true)}.${expr.fn}(${argumentString})`;
2303
+ if (!embeddedInBinding && hasEmbeddedFunctionCallOrBinding) {
2304
+ fnCall = `{= ${fnCall}}`;
2305
+ }
2306
+ return fnCall;
2307
+
2308
+ case "EmbeddedExpressionBinding":
2309
+ return embeddedInBinding ? `(${expr.value.substring(2, expr.value.length - 1)})` : `${expr.value}`;
2310
+
2311
+ case "EmbeddedBinding":
2312
+ return embeddedInBinding ? `${embeddedSeparator}${expr.value}` : `${expr.value}`;
2313
+
2314
+ case "PathInModel":
2315
+ return compilePathInModelExpression(expr, embeddedInBinding, embeddedSeparator);
2316
+
2317
+ case "Comparison":
2318
+ const comparisonExpression = compileComparisonExpression(expr);
2319
+ return wrapBindingExpression(comparisonExpression, embeddedInBinding);
2320
+
2321
+ case "IfElse":
2322
+ const ifElseExpression = `${compileExpression(expr.condition, true)} ? ${compileExpression(
2323
+ expr.onTrue,
2324
+ true,
2325
+ keepTargetType
2326
+ )} : ${compileExpression(expr.onFalse, true, keepTargetType)}`;
2327
+ return wrapBindingExpression(ifElseExpression, embeddedInBinding, true);
2328
+
2329
+ case "Set":
2330
+ const setExpression = expr.operands.map((operand) => compileExpression(operand, true)).join(` ${expr.operator} `);
2331
+ return wrapBindingExpression(setExpression, embeddedInBinding, true);
2332
+
2333
+ case "Concat":
2334
+ const concatExpression = expr.expressions
2335
+ .map((nestedExpression) => compileExpression(nestedExpression, true, true))
2336
+ .join(" + ");
2337
+ return wrapBindingExpression(concatExpression, embeddedInBinding);
2338
+
2339
+ case "Length":
2340
+ const lengthExpression = `${compileExpression(expr.pathInModel, true)}.length`;
2341
+ return wrapBindingExpression(lengthExpression, embeddedInBinding);
2342
+
2343
+ case "Not":
2344
+ const notExpression = `!${compileExpression(expr.operand, true)}`;
2345
+ return wrapBindingExpression(notExpression, embeddedInBinding);
2346
+
2347
+ case "Truthy":
2348
+ const truthyExpression = `!!${compileExpression(expr.operand, true)}`;
2349
+ return wrapBindingExpression(truthyExpression, embeddedInBinding);
2350
+
2351
+ case "Formatter":
2352
+ const formatterExpression = compileFormatterExpression(expr);
2353
+ return embeddedInBinding ? `$${formatterExpression}` : formatterExpression;
2354
+
2355
+ case "ComplexType":
2356
+ const complexTypeExpression = compileComplexTypeExpression(expr);
2357
+ return embeddedInBinding ? `$${complexTypeExpression}` : complexTypeExpression;
2358
+
2359
+ default:
2360
+ return "";
2361
+ }
2362
+ }
2363
+
2364
+ /**
2365
+ * Compile a comparison expression.
2366
+ *
2367
+ * @param expression The comparison expression.
2368
+ * @returns The compiled expression. Needs wrapping before it can be used as an expression binding.
2369
+ */
2370
+ function compileComparisonExpression(expression: ComparisonExpression): string {
2371
+ function compileOperand(operand: BindingToolkitExpression<unknown>): CompiledBindingToolkitExpression {
2372
+ const compiledOperand = compileExpression(operand, true) ?? "undefined";
2373
+ return wrapBindingExpression(compiledOperand, true, needParenthesis(operand));
2374
+ }
2375
+
2376
+ return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
2377
+ }
2378
+
2379
+ /**
2380
+ * Compile a formatter expression.
2381
+ *
2382
+ * @param expression The formatter expression.
2383
+ * @returns The compiled expression.
2384
+ */
2385
+ function compileFormatterExpression<T extends PrimitiveType>(expression: FormatterExpression<T>): string {
2386
+ if (expression.parameters.length === 1) {
2387
+ return `{${compilePathParameter(expression.parameters[0] as BindingToolkitExpression<PrimitiveType>, true)}, formatter: '${
2388
+ expression.fn
2389
+ }'}`;
2390
+ } else {
2391
+ const parts = expression.parameters.map((param) => {
2392
+ if (param._type === "ComplexType") {
2393
+ return compileComplexTypeExpression(param);
2394
+ } else {
2395
+ return compilePathParameter(param as BindingToolkitExpression<PrimitiveType>);
2396
+ }
2397
+ });
2398
+ return `{parts: [${parts.join(", ")}], formatter: '${expression.fn}'}`;
2399
+ }
2400
+ }
2401
+
2402
+ /**
2403
+ * Compile the path parameter of a formatter call.
2404
+ *
2405
+ * @param expression The binding part to evaluate
2406
+ * @param singlePath Whether there is one or multiple path to consider
2407
+ * @returns The string snippet to include in the overall binding definition
2408
+ */
2409
+ function compilePathParameter(expression: BindingToolkitExpression<PrimitiveType>, singlePath = false): string {
2410
+ let outValue = "";
2411
+ if (expression._type === "Constant") {
2412
+ if (expression.value === undefined) {
2413
+ // Special case otherwise the JSTokenizer complains about incorrect content
2414
+ outValue = `value: 'undefined'`;
2415
+ } else {
2416
+ outValue = `value: ${compileConstant(expression, true)}`;
2417
+ }
2418
+ } else if (expression._type === "PathInModel") {
2419
+ outValue = `path: '${compilePathInModel(expression)}'`;
2420
+
2421
+ outValue += expression.type ? `, type: '${expression.type}'` : `, targetType: 'any'`;
2422
+ if (expression.mode) {
2423
+ outValue += `, mode: '${compileExpression(expression.mode)}'`;
2424
+ }
2425
+ if (hasElements(expression.constraints)) {
2426
+ outValue += `, constraints: ${compileExpression(expression.constraints)}`;
2427
+ }
2428
+ if (hasElements(expression.formatOptions)) {
2429
+ outValue += `, formatOptions: ${compileExpression(expression.formatOptions)}`;
2430
+ }
2431
+ if (hasElements(expression.parameters)) {
2432
+ outValue += `, parameters: ${compileExpression(expression.parameters)}`;
2433
+ }
2434
+ } else {
2435
+ return "";
2436
+ }
2437
+ return singlePath ? outValue : `{${outValue}}`;
2438
+ }
2439
+
2440
+ function hasElements(obj: object | undefined): boolean {
2441
+ return !!obj && Object.keys(obj).length > 0;
2442
+ }
2443
+
2444
+ /**
2445
+ * Compile a binding expression path.
2446
+ *
2447
+ * @param expression The expression to compile.
2448
+ * @returns The compiled path.
2449
+ */
2450
+ function compilePathInModel<T extends PrimitiveType>(expression: PathInModelExpression<T>): string {
2451
+ return `${expression.modelName ? expression.modelName + ">" : ""}${expression.path}`;
2452
+ }