@aemforms/af-core 0.22.23 → 0.22.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/lib/browser/afb-events.js +151 -0
  2. package/lib/browser/afb-runtime.js +3620 -0
  3. package/lib/cjs/index.cjs +1876 -267
  4. package/lib/esm/BaseNode.js +454 -26
  5. package/lib/esm/Checkbox.js +1 -37
  6. package/lib/esm/CheckboxGroup.js +1 -38
  7. package/lib/esm/Container.js +9 -30
  8. package/lib/esm/DateField.js +2 -38
  9. package/lib/esm/Field.d.ts +2 -2
  10. package/lib/esm/Field.js +26 -62
  11. package/lib/esm/Fieldset.js +3 -36
  12. package/lib/esm/FileObject.js +1 -23
  13. package/lib/esm/FileUpload.js +1 -34
  14. package/lib/esm/Form.d.ts +1 -1
  15. package/lib/esm/Form.js +8 -40
  16. package/lib/esm/FormInstance.js +5 -53
  17. package/lib/esm/FormMetaData.js +1 -26
  18. package/lib/esm/InstanceManager.js +8 -35
  19. package/lib/esm/Node.js +1 -25
  20. package/lib/esm/Scriptable.js +2 -29
  21. package/lib/esm/controller/EventQueue.js +1 -23
  22. package/lib/esm/controller/Events.d.ts +1 -1
  23. package/lib/esm/controller/Events.js +19 -41
  24. package/lib/esm/controller/Logger.d.ts +2 -2
  25. package/lib/esm/controller/Logger.js +1 -23
  26. package/lib/esm/data/DataGroup.js +1 -24
  27. package/lib/esm/data/DataValue.js +1 -23
  28. package/lib/esm/data/EmptyDataValue.js +1 -23
  29. package/lib/esm/index.js +21 -55
  30. package/lib/esm/rules/FunctionRuntime.d.ts +3 -3
  31. package/lib/esm/rules/FunctionRuntime.js +6 -31
  32. package/lib/esm/rules/RuleEngine.js +1 -30
  33. package/lib/esm/types/Json.d.ts +16 -16
  34. package/lib/esm/types/Json.js +2 -24
  35. package/lib/esm/types/Model.d.ts +4 -4
  36. package/lib/esm/types/Model.js +1 -23
  37. package/lib/esm/types/index.js +2 -22
  38. package/lib/esm/utils/DataRefParser.d.ts +2 -2
  39. package/lib/esm/utils/DataRefParser.js +6 -31
  40. package/lib/esm/utils/Fetch.d.ts +1 -1
  41. package/lib/esm/utils/Fetch.js +2 -24
  42. package/lib/esm/utils/FormCreationUtils.js +2 -40
  43. package/lib/esm/utils/FormUtils.js +8 -33
  44. package/lib/esm/utils/JsonUtils.js +11 -34
  45. package/lib/esm/utils/LogUtils.js +1 -23
  46. package/lib/esm/utils/SchemaUtils.js +2 -24
  47. package/lib/esm/utils/TranslationUtils.d.ts +1 -1
  48. package/lib/esm/utils/TranslationUtils.js +9 -32
  49. package/lib/esm/utils/ValidationUtils.d.ts +4 -4
  50. package/lib/esm/utils/ValidationUtils.js +4 -30
  51. package/package.json +2 -14
  52. package/lib/esm/BaseNode-dc59ab07.js +0 -478
package/lib/cjs/index.cjs CHANGED
@@ -1,25 +1,169 @@
1
- /*************************************************************************
2
- * ADOBE CONFIDENTIAL
3
- * ___________________
4
- *
5
- * Copyright 2022 Adobe
6
- * All Rights Reserved.
7
- *
8
- * NOTICE: All information contained herein is, and remains
9
- * the property of Adobe and its suppliers, if any. The intellectual
10
- * and technical concepts contained herein are proprietary to Adobe
11
- * and its suppliers and are protected by all applicable intellectual
12
- * property laws, including trade secret and copyright laws.
13
- * Dissemination of this information or reproduction of this material
14
- * is strictly forbidden unless prior written permission is obtained
15
- * from Adobe.
16
-
17
- * Adobe permits you to use and modify this file solely in accordance with
18
- * the terms of the Adobe license agreement accompanying it.
19
- *************************************************************************/
20
-
21
1
  'use strict';
22
2
 
3
+ const translationProps = ['description', 'placeholder', 'enum', 'enumNames', 'label.value', 'constraintMessages.accept',
4
+ 'constraintMessages.enum', 'constraintMessages.exclusiveMinimum', 'constraintMessages.exclusiveMaximum', 'constraintMessages.format', 'constraintMessages.maxFileSize', 'constraintMessages.maxLength',
5
+ 'constraintMessages.maximum', 'constraintMessages.maxItems', 'constraintMessages.minLength', 'constraintMessages.minimum', 'constraintMessages.minItems', 'constraintMessages.pattern', 'constraintMessages.required',
6
+ 'constraintMessages.step', 'constraintMessages.type', 'constraintMessages.validationExpression'];
7
+ const constraintProps = ['accept', 'enum', 'exclusiveMinimum', 'exclusiveMaximum',
8
+ 'format', 'maxFileSize', 'maxLength', 'maximum', 'maxItems',
9
+ 'minLength', 'minimum', 'minItems', 'pattern', 'required', 'step', 'validationExpression', 'enumNames'];
10
+
11
+ class ValidationError {
12
+ fieldName;
13
+ errorMessages;
14
+ constructor(fieldName = '', errorMessages = []) {
15
+ this.errorMessages = errorMessages;
16
+ this.fieldName = fieldName;
17
+ }
18
+ }
19
+
20
+ const objToMap = (o) => new Map(Object.entries(o));
21
+ const stringViewTypes = objToMap({ 'date': 'date-input', 'data-url': 'file-input', 'binary': 'file-input' });
22
+ const typeToViewTypes = objToMap({
23
+ 'number': 'number-input',
24
+ 'boolean': 'checkbox',
25
+ 'object': 'panel',
26
+ 'array': 'panel',
27
+ 'file': 'file-input',
28
+ 'file[]': 'file-input'
29
+ });
30
+ const arrayTypes = ['string[]', 'boolean[]', 'number[]', 'array'];
31
+ const defaultFieldTypes = (schema) => {
32
+ const type = schema.type || 'string';
33
+ if ('enum' in schema) {
34
+ const enums = schema.enum;
35
+ if (enums.length > 2 || arrayTypes.indexOf(type) > -1) {
36
+ return 'drop-down';
37
+ }
38
+ else {
39
+ return 'checkbox';
40
+ }
41
+ }
42
+ if (type === 'string' || type === 'string[]') {
43
+ return stringViewTypes.get(schema.format) || 'text-input';
44
+ }
45
+ return typeToViewTypes.get(type) || 'text-input';
46
+ };
47
+ const fieldSchema = (input) => {
48
+ if ('items' in input) {
49
+ const fieldset = input;
50
+ const items = fieldset.items;
51
+ if (fieldset.type === 'array') {
52
+ return {
53
+ type: 'array',
54
+ items: fieldSchema(items[0]),
55
+ minItems: fieldset?.minItems,
56
+ maxItems: fieldset?.maxItems
57
+ };
58
+ }
59
+ else {
60
+ const iter = items.filter(x => x.name != null);
61
+ return {
62
+ type: 'object',
63
+ properties: Object.fromEntries(iter.map(item => [item.name, fieldSchema(item)])),
64
+ required: iter.filter(x => x.required).map(x => x.name)
65
+ };
66
+ }
67
+ }
68
+ else {
69
+ const field = input;
70
+ const schemaProps = ['type', 'maxLength', 'minLength', 'minimum', 'maximum', 'format', 'pattern', 'step', 'enum'];
71
+ const schema = schemaProps.reduce((acc, prop) => {
72
+ const p = prop;
73
+ if (prop in field && field[p] != undefined) {
74
+ acc[prop] = field[p];
75
+ }
76
+ return acc;
77
+ }, {});
78
+ if (field.dataRef === 'none' || Object.keys(schema).length == 0) {
79
+ return undefined;
80
+ }
81
+ return {
82
+ title: field.label?.value,
83
+ description: field.description,
84
+ ...schema
85
+ };
86
+ }
87
+ };
88
+ const exportDataSchema = (form) => {
89
+ return fieldSchema(form);
90
+ };
91
+
92
+ const getProperty = (data, key, def) => {
93
+ if (key in data) {
94
+ return data[key];
95
+ }
96
+ else if (!key.startsWith(':')) {
97
+ const prefixedKey = `:${key}`;
98
+ if (prefixedKey in data) {
99
+ return data[prefixedKey];
100
+ }
101
+ }
102
+ return def;
103
+ };
104
+ const isFile = function (item) {
105
+ return (item?.type === 'file' || item?.type === 'file[]') ||
106
+ ((item?.type === 'string' || item?.type === 'string[]') &&
107
+ (item?.format === 'binary' || item?.format === 'data-url'));
108
+ };
109
+ const checkIfConstraintsArePresent = function (item) {
110
+ return constraintProps.some(cp => item[cp] !== undefined);
111
+ };
112
+ const isCheckbox = function (item) {
113
+ const fieldType = item?.fieldType || defaultFieldTypes(item);
114
+ return fieldType === 'checkbox';
115
+ };
116
+ const isCheckboxGroup = function (item) {
117
+ const fieldType = item?.fieldType || defaultFieldTypes(item);
118
+ return fieldType === 'checkbox-group';
119
+ };
120
+ const isDateField = function (item) {
121
+ const fieldType = item?.fieldType || defaultFieldTypes(item);
122
+ return (fieldType === 'text-input' && item?.format === 'date') || fieldType === 'date-input';
123
+ };
124
+ function deepClone(obj, idGenerator) {
125
+ let result;
126
+ if (obj instanceof Array) {
127
+ result = [];
128
+ result = obj.map(x => deepClone(x, idGenerator));
129
+ }
130
+ else if (typeof obj === 'object' && obj !== null) {
131
+ result = {};
132
+ Object.entries(obj).forEach(([key, value]) => {
133
+ result[key] = deepClone(value, idGenerator);
134
+ });
135
+ }
136
+ else {
137
+ result = obj;
138
+ }
139
+ if (idGenerator && result && result.id) {
140
+ result.id = idGenerator();
141
+ }
142
+ return result;
143
+ }
144
+ function checkIfKeyAdded(currentObj, prevObj, objKey) {
145
+ if (currentObj != null && prevObj != null) {
146
+ const newPrvObj = { ...prevObj };
147
+ newPrvObj[objKey] = currentObj[objKey];
148
+ const newJsonStr = jsonString(currentObj).replace(jsonString(newPrvObj), '');
149
+ return newJsonStr === '';
150
+ }
151
+ else {
152
+ return false;
153
+ }
154
+ }
155
+ const jsonString = (obj) => {
156
+ return JSON.stringify(obj, null, 2);
157
+ };
158
+ const isRepeatable = (obj) => {
159
+ return ((obj.repeatable &&
160
+ ((obj.minOccur === undefined && obj.maxOccur === undefined) ||
161
+ (obj.minOccur !== undefined && obj.maxOccur !== undefined && obj.maxOccur !== 0) ||
162
+ (obj.minOccur !== undefined && obj.maxOccur !== undefined && obj.minOccur !== 0 && obj.maxOccur !== 0) ||
163
+ (obj.minOccur !== undefined && obj.minOccur >= 0) ||
164
+ (obj.maxOccur !== undefined && obj.maxOccur !== 0))) || false);
165
+ };
166
+
23
167
  class ActionImpl {
24
168
  _metadata;
25
169
  _type;
@@ -543,12 +687,12 @@ const resolveData = (data, input, create) => {
543
687
  return result;
544
688
  };
545
689
 
546
- function __decorate(decorators, target, key, desc) {
690
+ var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
547
691
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
548
692
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
549
693
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
550
694
  return c > 3 && r && Object.defineProperty(target, key, r), r;
551
- }
695
+ };
552
696
  const editableProperties = [
553
697
  'value',
554
698
  'label',
@@ -979,177 +1123,22 @@ class BaseNode {
979
1123
  }
980
1124
  }
981
1125
  }
982
- __decorate([
1126
+ __decorate$3([
983
1127
  dependencyTracked()
984
1128
  ], BaseNode.prototype, "index", null);
985
- __decorate([
1129
+ __decorate$3([
986
1130
  dependencyTracked()
987
1131
  ], BaseNode.prototype, "description", null);
988
- __decorate([
1132
+ __decorate$3([
989
1133
  dependencyTracked()
990
1134
  ], BaseNode.prototype, "visible", null);
991
- __decorate([
1135
+ __decorate$3([
992
1136
  dependencyTracked()
993
1137
  ], BaseNode.prototype, "label", null);
994
- __decorate([
1138
+ __decorate$3([
995
1139
  dependencyTracked()
996
1140
  ], BaseNode.prototype, "properties", null);
997
1141
 
998
- const translationProps = ['description', 'placeholder', 'enum', 'enumNames', 'label.value', 'constraintMessages.accept',
999
- 'constraintMessages.enum', 'constraintMessages.exclusiveMinimum', 'constraintMessages.exclusiveMaximum', 'constraintMessages.format', 'constraintMessages.maxFileSize', 'constraintMessages.maxLength',
1000
- 'constraintMessages.maximum', 'constraintMessages.maxItems', 'constraintMessages.minLength', 'constraintMessages.minimum', 'constraintMessages.minItems', 'constraintMessages.pattern', 'constraintMessages.required',
1001
- 'constraintMessages.step', 'constraintMessages.type', 'constraintMessages.validationExpression'];
1002
- const constraintProps = ['accept', 'enum', 'exclusiveMinimum', 'exclusiveMaximum',
1003
- 'format', 'maxFileSize', 'maxLength', 'maximum', 'maxItems',
1004
- 'minLength', 'minimum', 'minItems', 'pattern', 'required', 'step', 'validationExpression', 'enumNames'];
1005
-
1006
- const objToMap = (o) => new Map(Object.entries(o));
1007
- const stringViewTypes = objToMap({ 'date': 'date-input', 'data-url': 'file-input', 'binary': 'file-input' });
1008
- const typeToViewTypes = objToMap({
1009
- 'number': 'number-input',
1010
- 'boolean': 'checkbox',
1011
- 'object': 'panel',
1012
- 'array': 'panel',
1013
- 'file': 'file-input',
1014
- 'file[]': 'file-input'
1015
- });
1016
- const arrayTypes = ['string[]', 'boolean[]', 'number[]', 'array'];
1017
- const defaultFieldTypes = (schema) => {
1018
- const type = schema.type || 'string';
1019
- if ('enum' in schema) {
1020
- const enums = schema.enum;
1021
- if (enums.length > 2 || arrayTypes.indexOf(type) > -1) {
1022
- return 'drop-down';
1023
- }
1024
- else {
1025
- return 'checkbox';
1026
- }
1027
- }
1028
- if (type === 'string' || type === 'string[]') {
1029
- return stringViewTypes.get(schema.format) || 'text-input';
1030
- }
1031
- return typeToViewTypes.get(type) || 'text-input';
1032
- };
1033
- const fieldSchema = (input) => {
1034
- if ('items' in input) {
1035
- const fieldset = input;
1036
- const items = fieldset.items;
1037
- if (fieldset.type === 'array') {
1038
- return {
1039
- type: 'array',
1040
- items: fieldSchema(items[0]),
1041
- minItems: fieldset?.minItems,
1042
- maxItems: fieldset?.maxItems
1043
- };
1044
- }
1045
- else {
1046
- const iter = items.filter(x => x.name != null);
1047
- return {
1048
- type: 'object',
1049
- properties: Object.fromEntries(iter.map(item => [item.name, fieldSchema(item)])),
1050
- required: iter.filter(x => x.required).map(x => x.name)
1051
- };
1052
- }
1053
- }
1054
- else {
1055
- const field = input;
1056
- const schemaProps = ['type', 'maxLength', 'minLength', 'minimum', 'maximum', 'format', 'pattern', 'step', 'enum'];
1057
- const schema = schemaProps.reduce((acc, prop) => {
1058
- const p = prop;
1059
- if (prop in field && field[p] != undefined) {
1060
- acc[prop] = field[p];
1061
- }
1062
- return acc;
1063
- }, {});
1064
- if (field.dataRef === 'none' || Object.keys(schema).length == 0) {
1065
- return undefined;
1066
- }
1067
- return {
1068
- title: field.label?.value,
1069
- description: field.description,
1070
- ...schema
1071
- };
1072
- }
1073
- };
1074
- const exportDataSchema = (form) => {
1075
- return fieldSchema(form);
1076
- };
1077
-
1078
- const getProperty = (data, key, def) => {
1079
- if (key in data) {
1080
- return data[key];
1081
- }
1082
- else if (!key.startsWith(':')) {
1083
- const prefixedKey = `:${key}`;
1084
- if (prefixedKey in data) {
1085
- return data[prefixedKey];
1086
- }
1087
- }
1088
- return def;
1089
- };
1090
- const isFile = function (item) {
1091
- return (item?.type === 'file' || item?.type === 'file[]') ||
1092
- ((item?.type === 'string' || item?.type === 'string[]') &&
1093
- (item?.format === 'binary' || item?.format === 'data-url'));
1094
- };
1095
- const checkIfConstraintsArePresent = function (item) {
1096
- return constraintProps.some(cp => item[cp] !== undefined);
1097
- };
1098
- const isCheckbox = function (item) {
1099
- const fieldType = item?.fieldType || defaultFieldTypes(item);
1100
- return fieldType === 'checkbox';
1101
- };
1102
- const isCheckboxGroup = function (item) {
1103
- const fieldType = item?.fieldType || defaultFieldTypes(item);
1104
- return fieldType === 'checkbox-group';
1105
- };
1106
- const isDateField = function (item) {
1107
- const fieldType = item?.fieldType || defaultFieldTypes(item);
1108
- return (fieldType === 'text-input' && item?.format === 'date') || fieldType === 'date-input';
1109
- };
1110
- function deepClone(obj, idGenerator) {
1111
- let result;
1112
- if (obj instanceof Array) {
1113
- result = [];
1114
- result = obj.map(x => deepClone(x, idGenerator));
1115
- }
1116
- else if (typeof obj === 'object' && obj !== null) {
1117
- result = {};
1118
- Object.entries(obj).forEach(([key, value]) => {
1119
- result[key] = deepClone(value, idGenerator);
1120
- });
1121
- }
1122
- else {
1123
- result = obj;
1124
- }
1125
- if (idGenerator && result && result.id) {
1126
- result.id = idGenerator();
1127
- }
1128
- return result;
1129
- }
1130
- function checkIfKeyAdded(currentObj, prevObj, objKey) {
1131
- if (currentObj != null && prevObj != null) {
1132
- const newPrvObj = { ...prevObj };
1133
- newPrvObj[objKey] = currentObj[objKey];
1134
- const newJsonStr = jsonString(currentObj).replace(jsonString(newPrvObj), '');
1135
- return newJsonStr === '';
1136
- }
1137
- else {
1138
- return false;
1139
- }
1140
- }
1141
- const jsonString = (obj) => {
1142
- return JSON.stringify(obj, null, 2);
1143
- };
1144
- const isRepeatable = (obj) => {
1145
- return ((obj.repeatable &&
1146
- ((obj.minOccur === undefined && obj.maxOccur === undefined) ||
1147
- (obj.minOccur !== undefined && obj.maxOccur !== undefined && obj.maxOccur !== 0) ||
1148
- (obj.minOccur !== undefined && obj.maxOccur !== undefined && obj.minOccur !== 0 && obj.maxOccur !== 0) ||
1149
- (obj.minOccur !== undefined && obj.minOccur >= 0) ||
1150
- (obj.maxOccur !== undefined && obj.maxOccur !== 0))) || false);
1151
- };
1152
-
1153
1142
  class Scriptable extends BaseNode {
1154
1143
  _events = {};
1155
1144
  _rules = {};
@@ -1312,6 +1301,12 @@ class Scriptable extends BaseNode {
1312
1301
  }
1313
1302
  }
1314
1303
 
1304
+ var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
1305
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1306
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1307
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1308
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1309
+ };
1315
1310
  class Container extends Scriptable {
1316
1311
  _children = [];
1317
1312
  _childrenReference;
@@ -1581,13 +1576,13 @@ class Container extends Scriptable {
1581
1576
  }
1582
1577
  }
1583
1578
  }
1584
- __decorate([
1579
+ __decorate$2([
1585
1580
  dependencyTracked()
1586
1581
  ], Container.prototype, "maxItems", null);
1587
- __decorate([
1582
+ __decorate$2([
1588
1583
  dependencyTracked()
1589
1584
  ], Container.prototype, "minItems", null);
1590
- __decorate([
1585
+ __decorate$2([
1591
1586
  dependencyTracked()
1592
1587
  ], Container.prototype, "activeChild", null);
1593
1588
 
@@ -2453,16 +2448,17 @@ class Form extends Container {
2453
2448
  super.dispatch(action);
2454
2449
  }
2455
2450
  }
2451
+ executeAction(action) {
2452
+ if ((action.type !== 'submit') || this._invalidFields.length === 0) {
2453
+ super.executeAction(action);
2454
+ }
2455
+ }
2456
2456
  submit(action, context) {
2457
2457
  if (this.validate().length === 0) {
2458
2458
  const payload = action?.payload || {};
2459
2459
  submit(context, payload?.success, payload?.error, payload?.submit_as, payload?.data);
2460
2460
  }
2461
2461
  }
2462
- reset() {
2463
- super.reset();
2464
- this._invalidFields = [];
2465
- }
2466
2462
  getElement(id) {
2467
2463
  if (id == this.id) {
2468
2464
  return this;
@@ -2489,6 +2485,7 @@ class Form extends Container {
2489
2485
  }
2490
2486
  }
2491
2487
 
2488
+ // Type constants used to define functions.
2492
2489
  var dataTypes = {
2493
2490
  TYPE_NUMBER: 0,
2494
2491
  TYPE_ANY: 1,
@@ -2560,9 +2557,11 @@ const {
2560
2557
  TYPE_CLASS: TYPE_CLASS$1,
2561
2558
  TYPE_ARRAY_ARRAY,
2562
2559
  } = dataTypes;
2560
+
2563
2561
  const {
2564
2562
  TOK_EXPREF: TOK_EXPREF$3,
2565
2563
  } = tokenDefinitions;
2564
+
2566
2565
  const TYPE_NAME_TABLE = {
2567
2566
  [TYPE_NUMBER]: 'number',
2568
2567
  [TYPE_ANY$1]: 'any',
@@ -2577,10 +2576,13 @@ const TYPE_NAME_TABLE = {
2577
2576
  [TYPE_CLASS$1]: 'class',
2578
2577
  [TYPE_ARRAY_ARRAY]: 'Array<array>',
2579
2578
  };
2579
+
2580
2580
  function getTypeName(inputObj, useValueOf = true) {
2581
2581
  if (inputObj === null) return TYPE_NULL;
2582
2582
  let obj = inputObj;
2583
2583
  if (useValueOf) {
2584
+ // check for the case where there's a child named 'valueOf' that's not a function
2585
+ // if so, then it's an object...
2584
2586
  if (typeof inputObj.valueOf === 'function') obj = inputObj.valueOf.call(inputObj);
2585
2587
  else return TYPE_OBJECT;
2586
2588
  }
@@ -2596,6 +2598,8 @@ function getTypeName(inputObj, useValueOf = true) {
2596
2598
  case '[object Null]':
2597
2599
  return TYPE_NULL;
2598
2600
  case '[object Object]':
2601
+ // Check if it's an expref. If it has, it's been
2602
+ // tagged with a jmespathType attr of 'Expref';
2599
2603
  if (obj.jmespathType === TOK_EXPREF$3) {
2600
2604
  return TYPE_EXPREF;
2601
2605
  }
@@ -2604,17 +2608,23 @@ function getTypeName(inputObj, useValueOf = true) {
2604
2608
  return TYPE_OBJECT;
2605
2609
  }
2606
2610
  }
2611
+
2607
2612
  function getTypeNames(inputObj) {
2613
+ // return the types with and without using valueOf
2614
+ // needed for the cases where we really need an object passed to a function -- not it's value
2608
2615
  const type1 = getTypeName(inputObj);
2609
2616
  const type2 = getTypeName(inputObj, false);
2610
2617
  return [type1, type2];
2611
2618
  }
2619
+
2612
2620
  function matchType(actuals, expectedList, argValue, context, toNumber, toString) {
2613
2621
  const actual = actuals[0];
2614
2622
  if (expectedList.findIndex(
2615
2623
  type => type === TYPE_ANY$1 || actual === type,
2616
2624
  ) !== -1
2617
2625
  ) return argValue;
2626
+ // Can't coerce Objects to any other type,
2627
+ // and cannot coerce anything to a Class
2618
2628
  let wrongType = false;
2619
2629
  if (actual === TYPE_OBJECT || (expectedList.length === 1 && expectedList[0] === TYPE_CLASS$1)) {
2620
2630
  wrongType = true;
@@ -2634,9 +2644,11 @@ function matchType(actuals, expectedList, argValue, context, toNumber, toString)
2634
2644
  if (wrongType) {
2635
2645
  throw new Error(`TypeError: ${context} expected argument to be type ${TYPE_NAME_TABLE[expectedList[0]]} but received type ${TYPE_NAME_TABLE[actual]} instead.`);
2636
2646
  }
2647
+ // no exact match in the list of possible types, see if we can coerce an array type
2637
2648
  let expected = -1;
2638
2649
  if (actual === TYPE_ARRAY$1) {
2639
2650
  if (expectedList.includes(TYPE_ARRAY_STRING$1) && expectedList.includes(TYPE_ARRAY_NUMBER)) {
2651
+ // choose the array type based on the first element
2640
2652
  if (argValue.length > 0 && typeof argValue[0] === 'string') expected = TYPE_ARRAY_STRING$1;
2641
2653
  else expected = TYPE_ARRAY_NUMBER;
2642
2654
  }
@@ -2646,6 +2658,7 @@ function matchType(actuals, expectedList, argValue, context, toNumber, toString)
2646
2658
  e => [TYPE_ARRAY_STRING$1, TYPE_ARRAY_NUMBER, TYPE_ARRAY$1].includes(e),
2647
2659
  );
2648
2660
  }
2661
+ // no match, just take the first type
2649
2662
  if (expected === -1) [expected] = expectedList;
2650
2663
  if (expected === TYPE_ANY$1) return argValue;
2651
2664
  if (expected === TYPE_ARRAY_STRING$1
@@ -2655,8 +2668,12 @@ function matchType(actuals, expectedList, argValue, context, toNumber, toString)
2655
2668
  if (actual === TYPE_ARRAY_NUMBER || actual === TYPE_ARRAY_STRING$1) return argValue;
2656
2669
  return argValue === null ? [] : [argValue];
2657
2670
  }
2671
+ // The expected type can either just be array,
2672
+ // or it can require a specific subtype (array of numbers).
2658
2673
  const subtype = expected === TYPE_ARRAY_NUMBER ? TYPE_NUMBER : TYPE_STRING$1;
2659
2674
  if (actual === TYPE_ARRAY$1) {
2675
+ // Otherwise we need to check subtypes.
2676
+ // We're going to modify the array, so take a copy
2660
2677
  const returnArray = argValue.slice();
2661
2678
  for (let i = 0; i < returnArray.length; i += 1) {
2662
2679
  const indexType = getTypeNames(returnArray[i]);
@@ -2677,6 +2694,7 @@ function matchType(actuals, expectedList, argValue, context, toNumber, toString)
2677
2694
  } else {
2678
2695
  if (expected === TYPE_NUMBER) {
2679
2696
  if ([TYPE_STRING$1, TYPE_BOOLEAN, TYPE_NULL].includes(actual)) return toNumber(argValue);
2697
+ /* TYPE_ARRAY, TYPE_EXPREF, TYPE_OBJECT, TYPE_ARRAY, TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING */
2680
2698
  return 0;
2681
2699
  }
2682
2700
  if (expected === TYPE_STRING$1) {
@@ -2699,31 +2717,42 @@ function isArray(obj) {
2699
2717
  }
2700
2718
  return false;
2701
2719
  }
2720
+
2702
2721
  function isObject(obj) {
2703
2722
  if (obj !== null) {
2704
2723
  return Object.prototype.toString.call(obj) === '[object Object]';
2705
2724
  }
2706
2725
  return false;
2707
2726
  }
2727
+
2708
2728
  function getValueOf(a) {
2709
2729
  if (a === null || a === undefined) return a;
2710
2730
  if (isArray(a)) {
2711
2731
  return a.map(i => getValueOf(i));
2712
2732
  }
2733
+ // if we have a child named 'valueOf' then we're an object,
2734
+ // and just return the object.
2713
2735
  if (typeof (a.valueOf) !== 'function') return a;
2714
2736
  return a.valueOf();
2715
2737
  }
2738
+
2716
2739
  function strictDeepEqual(lhs, rhs) {
2717
2740
  const first = getValueOf(lhs);
2718
2741
  const second = getValueOf(rhs);
2742
+ // Check the scalar case first.
2719
2743
  if (first === second) {
2720
2744
  return true;
2721
2745
  }
2746
+
2747
+ // Check if they are the same type.
2722
2748
  const firstType = Object.prototype.toString.call(first);
2723
2749
  if (firstType !== Object.prototype.toString.call(second)) {
2724
2750
  return false;
2725
2751
  }
2752
+ // We know that first and second have the same type so we can just check the
2753
+ // first type from now on.
2726
2754
  if (isArray(first) === true) {
2755
+ // Short circuit if they're not the same length;
2727
2756
  if (first.length !== second.length) {
2728
2757
  return false;
2729
2758
  }
@@ -2735,7 +2764,9 @@ function strictDeepEqual(lhs, rhs) {
2735
2764
  return true;
2736
2765
  }
2737
2766
  if (isObject(first) === true) {
2767
+ // An object is equal if it has the same key/value pairs.
2738
2768
  const keysSeen = {};
2769
+ // eslint-disable-next-line no-restricted-syntax
2739
2770
  for (const key in first) {
2740
2771
  if (hasOwnProperty.call(first, key)) {
2741
2772
  if (strictDeepEqual(first[key], second[key]) === false) {
@@ -2744,6 +2775,9 @@ function strictDeepEqual(lhs, rhs) {
2744
2775
  keysSeen[key] = true;
2745
2776
  }
2746
2777
  }
2778
+ // Now check that there aren't any keys in second that weren't
2779
+ // in first.
2780
+ // eslint-disable-next-line no-restricted-syntax
2747
2781
  for (const key2 in second) {
2748
2782
  if (hasOwnProperty.call(second, key2)) {
2749
2783
  if (keysSeen[key2] !== true) {
@@ -2769,22 +2803,42 @@ const {
2769
2803
  TOK_NE: TOK_NE$2,
2770
2804
  TOK_FLATTEN: TOK_FLATTEN$2,
2771
2805
  } = tokenDefinitions;
2806
+
2772
2807
  const {
2773
2808
  TYPE_STRING,
2774
2809
  TYPE_ARRAY_STRING,
2775
2810
  TYPE_ARRAY,
2776
2811
  } = dataTypes;
2812
+
2777
2813
  function isFalse(value) {
2814
+ // From the spec:
2815
+ // A false value corresponds to the following values:
2816
+ // Empty list
2817
+ // Empty object
2818
+ // Empty string
2819
+ // False boolean
2820
+ // null value
2821
+ // (new) use JS truthy evaluation. This changes the spec behavior.
2822
+ // Where in the past a zero (0) would be True, it's now false
2823
+
2824
+ // First check the scalar values.
2778
2825
  if (value === null) return true;
2826
+ // in case it's an object with a valueOf defined
2779
2827
  const obj = getValueOf(value);
2780
2828
  if (obj === '' || obj === false || obj === null) {
2781
2829
  return true;
2782
2830
  }
2783
2831
  if (isArray(obj) && obj.length === 0) {
2832
+ // Check for an empty array.
2784
2833
  return true;
2785
2834
  }
2786
2835
  if (isObject(obj)) {
2836
+ // Check for an empty object.
2837
+ // eslint-disable-next-line no-restricted-syntax
2787
2838
  for (const key in obj) {
2839
+ // If there are any keys, then
2840
+ // the object is not empty so the object
2841
+ // is not false.
2788
2842
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
2789
2843
  return false;
2790
2844
  }
@@ -2793,9 +2847,11 @@ function isFalse(value) {
2793
2847
  }
2794
2848
  return !obj;
2795
2849
  }
2850
+
2796
2851
  function objValues(obj) {
2797
2852
  return Object.values(obj);
2798
2853
  }
2854
+
2799
2855
  class TreeInterpreter {
2800
2856
  constructor(runtime, globals, toNumber, toString, debug, language) {
2801
2857
  this.runtime = runtime;
@@ -2805,20 +2861,27 @@ class TreeInterpreter {
2805
2861
  this.debug = debug;
2806
2862
  this.language = language;
2807
2863
  }
2864
+
2808
2865
  search(node, value) {
2809
2866
  return this.visit(node, value);
2810
2867
  }
2868
+
2811
2869
  visit(n, v) {
2812
2870
  const visitFunctions = {
2813
2871
  Field: (node, value) => {
2872
+ // we used to check isObject(value) here -- but it is possible for an array-based
2873
+ // object to have properties. So we'll allow the child check on objects and arrays.
2814
2874
  if (value !== null && (isObject(value) || isArray(value))) {
2815
2875
  let field = value[node.name];
2876
+ // fields can be objects with overridden methods. e.g. valueOf
2877
+ // so don't resolve to a function...
2816
2878
  if (typeof field === 'function') field = undefined;
2817
2879
  if (field === undefined) {
2818
2880
  try {
2819
2881
  this.debug.push(`Failed to find: '${node.name}'`);
2820
2882
  const available = Object.keys(value).map(a => `'${a}'`).toString();
2821
2883
  if (available.length) this.debug.push(`Available fields: ${available}`);
2884
+ // eslint-disable-next-line no-empty
2822
2885
  } catch (e) {}
2823
2886
  return null;
2824
2887
  }
@@ -2826,6 +2889,7 @@ class TreeInterpreter {
2826
2889
  }
2827
2890
  return null;
2828
2891
  },
2892
+
2829
2893
  Subexpression: (node, value) => {
2830
2894
  let result = this.visit(node.children[0], value);
2831
2895
  for (let i = 1; i < node.children.length; i += 1) {
@@ -2834,10 +2898,12 @@ class TreeInterpreter {
2834
2898
  }
2835
2899
  return result;
2836
2900
  },
2901
+
2837
2902
  IndexExpression: (node, value) => {
2838
2903
  const left = this.visit(node.children[0], value);
2839
2904
  return this.visit(node.children[1], left);
2840
2905
  },
2906
+
2841
2907
  Index: (node, value) => {
2842
2908
  if (isArray(value)) {
2843
2909
  let index = this.toNumber(this.visit(node.value, value));
@@ -2863,6 +2929,7 @@ class TreeInterpreter {
2863
2929
  this.debug.push(`left side of index expression ${value} is not an array or object.`);
2864
2930
  return null;
2865
2931
  },
2932
+
2866
2933
  Slice: (node, value) => {
2867
2934
  if (!isArray(value)) return null;
2868
2935
  const sliceParams = node.children.slice(0).map(
@@ -2882,7 +2949,9 @@ class TreeInterpreter {
2882
2949
  }
2883
2950
  return result;
2884
2951
  },
2952
+
2885
2953
  Projection: (node, value) => {
2954
+ // Evaluate left child.
2886
2955
  const base = this.visit(node.children[0], value);
2887
2956
  if (!isArray(base)) return null;
2888
2957
  const collected = [];
@@ -2894,7 +2963,9 @@ class TreeInterpreter {
2894
2963
  });
2895
2964
  return collected;
2896
2965
  },
2966
+
2897
2967
  ValueProjection: (node, value) => {
2968
+ // Evaluate left child.
2898
2969
  const projection = this.visit(node.children[0], value);
2899
2970
  if (!isObject(getValueOf(projection))) return null;
2900
2971
  const collected = [];
@@ -2905,6 +2976,7 @@ class TreeInterpreter {
2905
2976
  });
2906
2977
  return collected;
2907
2978
  },
2979
+
2908
2980
  FilterProjection: (node, value) => {
2909
2981
  const base = this.visit(node.children[0], value);
2910
2982
  if (!isArray(base)) return null;
@@ -2912,6 +2984,7 @@ class TreeInterpreter {
2912
2984
  const matched = this.visit(node.children[2], b);
2913
2985
  return !isFalse(matched);
2914
2986
  });
2987
+
2915
2988
  const finalResults = [];
2916
2989
  filtered.forEach(f => {
2917
2990
  const current = this.visit(node.children[1], f);
@@ -2919,9 +2992,11 @@ class TreeInterpreter {
2919
2992
  });
2920
2993
  return finalResults;
2921
2994
  },
2995
+
2922
2996
  Comparator: (node, value) => {
2923
2997
  const first = this.visit(node.children[0], value);
2924
2998
  const second = this.visit(node.children[1], value);
2999
+
2925
3000
  if (node.name === TOK_EQ$2) return strictDeepEqual(first, second);
2926
3001
  if (node.name === TOK_NE$2) return !strictDeepEqual(first, second);
2927
3002
  if (node.name === TOK_GT$2) return first > second;
@@ -2930,6 +3005,7 @@ class TreeInterpreter {
2930
3005
  if (node.name === TOK_LTE$2) return first <= second;
2931
3006
  throw new Error(`Unknown comparator: ${node.name}`);
2932
3007
  },
3008
+
2933
3009
  [TOK_FLATTEN$2]: (node, value) => {
2934
3010
  const original = this.visit(node.children[0], value);
2935
3011
  if (!isArray(original)) return null;
@@ -2943,11 +3019,14 @@ class TreeInterpreter {
2943
3019
  });
2944
3020
  return merged;
2945
3021
  },
3022
+
2946
3023
  Identity: (_node, value) => value,
3024
+
2947
3025
  MultiSelectList: (node, value) => {
2948
3026
  if (value === null) return null;
2949
3027
  return node.children.map(child => this.visit(child, value));
2950
3028
  },
3029
+
2951
3030
  MultiSelectHash: (node, value) => {
2952
3031
  if (value === null) return null;
2953
3032
  const collected = {};
@@ -2956,21 +3035,26 @@ class TreeInterpreter {
2956
3035
  });
2957
3036
  return collected;
2958
3037
  },
3038
+
2959
3039
  OrExpression: (node, value) => {
2960
3040
  let matched = this.visit(node.children[0], value);
2961
3041
  if (isFalse(matched)) matched = this.visit(node.children[1], value);
2962
3042
  return matched;
2963
3043
  },
3044
+
2964
3045
  AndExpression: (node, value) => {
2965
3046
  const first = this.visit(node.children[0], value);
3047
+
2966
3048
  if (isFalse(first) === true) return first;
2967
3049
  return this.visit(node.children[1], value);
2968
3050
  },
3051
+
2969
3052
  AddExpression: (node, value) => {
2970
3053
  const first = this.visit(node.children[0], value);
2971
3054
  const second = this.visit(node.children[1], value);
2972
3055
  return this.applyOperator(first, second, '+');
2973
3056
  },
3057
+
2974
3058
  ConcatenateExpression: (node, value) => {
2975
3059
  let first = this.visit(node.children[0], value);
2976
3060
  let second = this.visit(node.children[1], value);
@@ -2978,6 +3062,7 @@ class TreeInterpreter {
2978
3062
  second = matchType(getTypeNames(second), [TYPE_STRING, TYPE_ARRAY_STRING], second, 'concatenate', this.toNumber, this.toString);
2979
3063
  return this.applyOperator(first, second, '&');
2980
3064
  },
3065
+
2981
3066
  UnionExpression: (node, value) => {
2982
3067
  let first = this.visit(node.children[0], value);
2983
3068
  let second = this.visit(node.children[1], value);
@@ -2985,52 +3070,72 @@ class TreeInterpreter {
2985
3070
  second = matchType(getTypeNames(second), [TYPE_ARRAY], second, 'union', this.toNumber, this.toString);
2986
3071
  return first.concat(second);
2987
3072
  },
3073
+
2988
3074
  SubtractExpression: (node, value) => {
2989
3075
  const first = this.visit(node.children[0], value);
2990
3076
  const second = this.visit(node.children[1], value);
2991
3077
  return this.applyOperator(first, second, '-');
2992
3078
  },
3079
+
2993
3080
  MultiplyExpression: (node, value) => {
2994
3081
  const first = this.visit(node.children[0], value);
2995
3082
  const second = this.visit(node.children[1], value);
2996
3083
  return this.applyOperator(first, second, '*');
2997
3084
  },
3085
+
2998
3086
  DivideExpression: (node, value) => {
2999
3087
  const first = this.visit(node.children[0], value);
3000
3088
  const second = this.visit(node.children[1], value);
3001
3089
  return this.applyOperator(first, second, '/');
3002
3090
  },
3091
+
3003
3092
  PowerExpression: (node, value) => {
3004
3093
  const first = this.visit(node.children[0], value);
3005
3094
  const second = this.visit(node.children[1], value);
3006
3095
  return this.applyOperator(first, second, '^');
3007
3096
  },
3097
+
3008
3098
  NotExpression: (node, value) => {
3009
3099
  const first = this.visit(node.children[0], value);
3010
3100
  return isFalse(first);
3011
3101
  },
3102
+
3012
3103
  UnaryMinusExpression: (node, value) => {
3013
3104
  const first = this.visit(node.children[0], value);
3014
3105
  return first * -1;
3015
3106
  },
3107
+
3016
3108
  Literal: node => node.value,
3109
+
3017
3110
  Number: node => node.value,
3111
+
3018
3112
  [TOK_PIPE$2]: (node, value) => {
3019
3113
  const left = this.visit(node.children[0], value);
3020
3114
  return this.visit(node.children[1], left);
3021
3115
  },
3116
+
3022
3117
  [TOK_CURRENT$2]: (_node, value) => value,
3118
+
3023
3119
  [TOK_GLOBAL$2]: node => {
3024
3120
  const result = this.globals[node.name];
3025
3121
  return result === undefined ? null : result;
3026
3122
  },
3123
+
3027
3124
  Function: (node, value) => {
3125
+ // Special case for if()
3126
+ // we need to make sure the results are called only after the condition is evaluated
3127
+ // Otherwise we end up with both results invoked -- which could include side effects
3128
+ // For "if", the last parameter to callFunction is false (bResolved) to indicate there's
3129
+ // no point in validating the argument type.
3028
3130
  if (node.name === 'if') return this.runtime.callFunction(node.name, node.children, value, this, false);
3029
3131
  const resolvedArgs = node.children.map(child => this.visit(child, value));
3030
3132
  return this.runtime.callFunction(node.name, resolvedArgs, value, this);
3031
3133
  },
3134
+
3032
3135
  ExpressionReference: node => {
3033
3136
  const [refNode] = node.children;
3137
+ // Tag the node with a specific attribute so the type
3138
+ // checker verify the type.
3034
3139
  refNode.jmespathType = TOK_EXPREF$2;
3035
3140
  return refNode;
3036
3141
  },
@@ -3039,6 +3144,8 @@ class TreeInterpreter {
3039
3144
  if (!fn) throw new Error(`Unknown/missing node type ${(n && n.type) || ''}`);
3040
3145
  return fn(n, v);
3041
3146
  }
3147
+
3148
+ // eslint-disable-next-line class-methods-use-this
3042
3149
  computeSliceParams(arrayLength, sliceParams) {
3043
3150
  function capSliceRange(arrayLen, actual, stp) {
3044
3151
  let actualValue = actual;
@@ -3052,6 +3159,7 @@ class TreeInterpreter {
3052
3159
  }
3053
3160
  return actualValue;
3054
3161
  }
3162
+
3055
3163
  let [start, stop, step] = sliceParams;
3056
3164
  if (step === null) {
3057
3165
  step = 1;
@@ -3061,11 +3169,13 @@ class TreeInterpreter {
3061
3169
  throw error;
3062
3170
  }
3063
3171
  const stepValueNegative = step < 0;
3172
+
3064
3173
  if (start === null) {
3065
3174
  start = stepValueNegative ? arrayLength - 1 : 0;
3066
3175
  } else {
3067
3176
  start = capSliceRange(arrayLength, start, step);
3068
3177
  }
3178
+
3069
3179
  if (stop === null) {
3070
3180
  stop = stepValueNegative ? -1 : arrayLength;
3071
3181
  } else {
@@ -3073,8 +3183,10 @@ class TreeInterpreter {
3073
3183
  }
3074
3184
  return [start, stop, step];
3075
3185
  }
3186
+
3076
3187
  applyOperator(first, second, operator) {
3077
3188
  if (isArray(first) && isArray(second)) {
3189
+ // balance the size of the arrays
3078
3190
  const shorter = first.length < second.length ? first : second;
3079
3191
  const diff = Math.abs(first.length - second.length);
3080
3192
  shorter.length += diff;
@@ -3085,8 +3197,10 @@ class TreeInterpreter {
3085
3197
  }
3086
3198
  return result;
3087
3199
  }
3200
+
3088
3201
  if (isArray(first)) return first.map(a => this.applyOperator(a, second, operator));
3089
3202
  if (isArray(second)) return second.map(a => this.applyOperator(first, a, operator));
3203
+
3090
3204
  if (operator === '*') return this.toNumber(first) * this.toNumber(second);
3091
3205
  if (operator === '&') return first + second;
3092
3206
  if (operator === '+') {
@@ -3104,6 +3218,8 @@ class TreeInterpreter {
3104
3218
  }
3105
3219
  }
3106
3220
 
3221
+ /* eslint-disable no-underscore-dangle */
3222
+
3107
3223
  const {
3108
3224
  TOK_UNQUOTEDIDENTIFIER: TOK_UNQUOTEDIDENTIFIER$1,
3109
3225
  TOK_QUOTEDIDENTIFIER: TOK_QUOTEDIDENTIFIER$1,
@@ -3143,8 +3259,16 @@ const {
3143
3259
  TOK_LPAREN: TOK_LPAREN$1,
3144
3260
  TOK_LITERAL: TOK_LITERAL$1,
3145
3261
  } = tokenDefinitions;
3262
+
3263
+ // The "&", "[", "<", ">" tokens
3264
+ // are not in basicToken because
3265
+ // there are two token variants
3266
+ // ("&&", "[?", "<=", ">="). This is specially handled
3267
+ // below.
3268
+
3146
3269
  const basicTokens = {
3147
3270
  '.': TOK_DOT$1,
3271
+ // "*": TOK_STAR,
3148
3272
  ',': TOK_COMMA$1,
3149
3273
  ':': TOK_COLON$1,
3150
3274
  '{': TOK_LBRACE$1,
@@ -3154,6 +3278,7 @@ const basicTokens = {
3154
3278
  ')': TOK_RPAREN$1,
3155
3279
  '@': TOK_CURRENT$1,
3156
3280
  };
3281
+
3157
3282
  const globalStartToken = '$';
3158
3283
  const operatorStartToken = {
3159
3284
  '<': true,
@@ -3161,34 +3286,42 @@ const operatorStartToken = {
3161
3286
  '=': true,
3162
3287
  '!': true,
3163
3288
  };
3289
+
3164
3290
  const skipChars = {
3165
3291
  ' ': true,
3166
3292
  '\t': true,
3167
3293
  '\n': true,
3168
3294
  };
3295
+
3169
3296
  function isNum(ch) {
3170
3297
  return (ch >= '0' && ch <= '9') || (ch === '.');
3171
3298
  }
3299
+
3172
3300
  function isAlphaNum(ch) {
3173
3301
  return (ch >= 'a' && ch <= 'z')
3174
3302
  || (ch >= 'A' && ch <= 'Z')
3175
3303
  || (ch >= '0' && ch <= '9')
3176
3304
  || ch === '_';
3177
3305
  }
3306
+
3178
3307
  function isIdentifier(stream, pos) {
3179
3308
  const ch = stream[pos];
3309
+ // $ is special -- it's allowed to be part of an identifier if it's the first character
3180
3310
  if (ch === '$') {
3181
3311
  return stream.length > pos && isAlphaNum(stream[pos + 1]);
3182
3312
  }
3313
+ // return whether character 'isAlpha'
3183
3314
  return (ch >= 'a' && ch <= 'z')
3184
3315
  || (ch >= 'A' && ch <= 'Z')
3185
3316
  || ch === '_';
3186
3317
  }
3318
+
3187
3319
  class Lexer {
3188
3320
  constructor(allowedGlobalNames = [], debug = []) {
3189
3321
  this._allowedGlobalNames = allowedGlobalNames;
3190
3322
  this.debug = debug;
3191
3323
  }
3324
+
3192
3325
  tokenize(stream) {
3193
3326
  const tokens = [];
3194
3327
  this._current = 0;
@@ -3197,6 +3330,7 @@ class Lexer {
3197
3330
  let token;
3198
3331
  while (this._current < stream.length) {
3199
3332
  const prev = tokens.length ? tokens.slice(-1)[0].type : null;
3333
+
3200
3334
  if (this._isGlobal(prev, stream, this._current)) {
3201
3335
  tokens.push(this._consumeGlobal(stream));
3202
3336
  } else if (isIdentifier(stream, this._current)) {
@@ -3221,6 +3355,8 @@ class Lexer {
3221
3355
  token = this._consumeNumber(stream);
3222
3356
  tokens.push(token);
3223
3357
  } else if (stream[this._current] === '[') {
3358
+ // No need to increment this._current. This happens
3359
+ // in _consumeLBracket
3224
3360
  token = this._consumeLBracket(stream);
3225
3361
  tokens.push(token);
3226
3362
  } else if (stream[this._current] === '"') {
@@ -3250,6 +3386,7 @@ class Lexer {
3250
3386
  } else if (operatorStartToken[stream[this._current]] !== undefined) {
3251
3387
  tokens.push(this._consumeOperator(stream));
3252
3388
  } else if (skipChars[stream[this._current]] !== undefined) {
3389
+ // Ignore whitespace.
3253
3390
  this._current += 1;
3254
3391
  } else if (stream[this._current] === '&') {
3255
3392
  start = this._current;
@@ -3258,6 +3395,9 @@ class Lexer {
3258
3395
  this._current += 1;
3259
3396
  tokens.push({ type: TOK_AND$1, value: '&&', start });
3260
3397
  } else if (prev === TOK_COMMA$1 || prev === TOK_LPAREN$1) {
3398
+ // based on previous token we'll know if this & is a JMESPath expression-type
3399
+ // or if it's a concatenation operator
3400
+ // if we're a function arg then it's an expression-type
3261
3401
  tokens.push({ type: TOK_EXPREF$1, value: '&', start });
3262
3402
  } else {
3263
3403
  tokens.push({ type: TOK_CONCATENATE$1, value: '&', start });
@@ -3277,6 +3417,8 @@ class Lexer {
3277
3417
  } else if (stream[this._current] === '*') {
3278
3418
  start = this._current;
3279
3419
  this._current += 1;
3420
+ // based on previous token we'll know if this asterix is a star -- not a multiply
3421
+ // might be better to list the prev tokens that are valid for multiply?
3280
3422
  const prevToken = tokens.length && tokens.slice(-1)[0].type;
3281
3423
  if (tokens.length === 0 || [
3282
3424
  TOK_LBRACKET$1,
@@ -3316,6 +3458,7 @@ class Lexer {
3316
3458
  }
3317
3459
  return tokens;
3318
3460
  }
3461
+
3319
3462
  _consumeUnquotedIdentifier(stream) {
3320
3463
  const start = this._current;
3321
3464
  this._current += 1;
@@ -3324,12 +3467,14 @@ class Lexer {
3324
3467
  }
3325
3468
  return stream.slice(start, this._current);
3326
3469
  }
3470
+
3327
3471
  _consumeQuotedIdentifier(stream) {
3328
3472
  const start = this._current;
3329
3473
  this._current += 1;
3330
3474
  const maxLength = stream.length;
3331
3475
  let foundNonAlpha = !isIdentifier(stream, start + 1);
3332
3476
  while (stream[this._current] !== '"' && this._current < maxLength) {
3477
+ // You can escape a double quote and you can escape an escape.
3333
3478
  let current = this._current;
3334
3479
  if (!isAlphaNum(stream[current])) foundNonAlpha = true;
3335
3480
  if (stream[current] === '\\' && (stream[current + 1] === '\\'
@@ -3342,19 +3487,26 @@ class Lexer {
3342
3487
  }
3343
3488
  this._current += 1;
3344
3489
  const val = stream.slice(start, this._current);
3490
+ // Check for unnecessary double quotes.
3491
+ // json-formula uses double quotes to escape characters that don't belong in names names.
3492
+ // e.g. "purchase-order".address
3493
+ // If we find a double-quoted entity with spaces or all legal characters, issue a warning
3345
3494
  try {
3346
3495
  if (!foundNonAlpha || val.includes(' ')) {
3347
3496
  this.debug.push(`Suspicious quotes: ${val}`);
3348
3497
  this.debug.push(`Did you intend a literal? '${val.replace(/"/g, '')}'?`);
3349
3498
  }
3499
+ // eslint-disable-next-line no-empty
3350
3500
  } catch (e) {}
3351
3501
  return JSON.parse(val);
3352
3502
  }
3503
+
3353
3504
  _consumeRawStringLiteral(stream) {
3354
3505
  const start = this._current;
3355
3506
  this._current += 1;
3356
3507
  const maxLength = stream.length;
3357
3508
  while (stream[this._current] !== "'" && this._current < maxLength) {
3509
+ // You can escape a single quote and you can escape an escape.
3358
3510
  let current = this._current;
3359
3511
  if (stream[current] === '\\' && (stream[current + 1] === '\\'
3360
3512
  || stream[current + 1] === "'")) {
@@ -3368,6 +3520,7 @@ class Lexer {
3368
3520
  const literal = stream.slice(start + 1, this._current - 1);
3369
3521
  return literal.replaceAll("\\'", "'");
3370
3522
  }
3523
+
3371
3524
  _consumeNumber(stream) {
3372
3525
  const start = this._current;
3373
3526
  this._current += 1;
@@ -3384,11 +3537,13 @@ class Lexer {
3384
3537
  }
3385
3538
  return { type: TOK_NUMBER$1, value, start };
3386
3539
  }
3540
+
3387
3541
  _consumeUnaryMinus() {
3388
3542
  const start = this._current;
3389
3543
  this._current += 1;
3390
3544
  return { type: TOK_UNARY_MINUS$1, value: '-', start };
3391
3545
  }
3546
+
3392
3547
  _consumeLBracket(stream) {
3393
3548
  const start = this._current;
3394
3549
  this._current += 1;
@@ -3402,22 +3557,28 @@ class Lexer {
3402
3557
  }
3403
3558
  return { type: TOK_LBRACKET$1, value: '[', start };
3404
3559
  }
3560
+
3405
3561
  _isGlobal(prev, stream, pos) {
3562
+ // global tokens occur only at the start of an expression
3406
3563
  if (prev !== null && prev === TOK_DOT$1) return false;
3407
3564
  const ch = stream[pos];
3408
3565
  if (ch !== globalStartToken) return false;
3566
+ // $ is special -- it's allowed to be part of an identifier if it's the first character
3409
3567
  let i = pos + 1;
3410
3568
  while (i < stream.length && isAlphaNum(stream[i])) i += 1;
3411
3569
  const global = stream.slice(pos, i);
3412
3570
  return this._allowedGlobalNames.includes(global);
3413
3571
  }
3572
+
3414
3573
  _consumeGlobal(stream) {
3415
3574
  const start = this._current;
3416
3575
  this._current += 1;
3417
3576
  while (this._current < stream.length && isAlphaNum(stream[this._current])) this._current += 1;
3418
3577
  const global = stream.slice(start, this._current);
3578
+
3419
3579
  return { type: TOK_GLOBAL$1, name: global, start };
3420
3580
  }
3581
+
3421
3582
  _consumeOperator(stream) {
3422
3583
  const start = this._current;
3423
3584
  const startingChar = stream[start];
@@ -3443,17 +3604,20 @@ class Lexer {
3443
3604
  }
3444
3605
  return { type: TOK_GT$1, value: '>', start };
3445
3606
  }
3607
+ // startingChar is '='
3446
3608
  if (stream[this._current] === '=') {
3447
3609
  this._current += 1;
3448
3610
  return { type: TOK_EQ$1, value: '==', start };
3449
3611
  }
3450
3612
  return { type: TOK_EQ$1, value: '=', start };
3451
3613
  }
3614
+
3452
3615
  _consumeLiteral(stream) {
3453
3616
  function _looksLikeJSON(str) {
3454
3617
  if (str === '') return false;
3455
3618
  if ('[{"'.includes(str[0])) return true;
3456
3619
  if (['true', 'false', 'null'].includes(str)) return true;
3620
+
3457
3621
  if ('-0123456789'.includes(str[0])) {
3458
3622
  try {
3459
3623
  JSON.parse(str);
@@ -3465,6 +3629,7 @@ class Lexer {
3465
3629
  return false;
3466
3630
  }
3467
3631
  }
3632
+
3468
3633
  this._current += 1;
3469
3634
  const start = this._current;
3470
3635
  const maxLength = stream.length;
@@ -3472,12 +3637,14 @@ class Lexer {
3472
3637
  let inQuotes = false;
3473
3638
  while ((inQuotes || stream[this._current] !== '`') && this._current < maxLength) {
3474
3639
  let current = this._current;
3640
+ // bypass escaped double quotes when we're inside quotes
3475
3641
  if (inQuotes && stream[current] === '\\' && stream[current + 1] === '"') current += 2;
3476
3642
  else {
3477
3643
  if (stream[current] === '"') inQuotes = !inQuotes;
3478
3644
  if (inQuotes && stream[current + 1] === '`') current += 2;
3479
3645
  else if (stream[current] === '\\' && (stream[current + 1] === '\\'
3480
3646
  || stream[current + 1] === '`')) {
3647
+ // You can escape a literal char or you can escape the escape.
3481
3648
  current += 2;
3482
3649
  } else {
3483
3650
  current += 1;
@@ -3490,13 +3657,16 @@ class Lexer {
3490
3657
  if (_looksLikeJSON(literalString)) {
3491
3658
  literal = JSON.parse(literalString);
3492
3659
  } else {
3660
+ // Try to JSON parse it as "<literal>"
3493
3661
  literal = JSON.parse(`"${literalString}"`);
3494
3662
  }
3663
+ // +1 gets us to the ending "`", +1 to move on to the next char.
3495
3664
  this._current += 1;
3496
3665
  return literal;
3497
3666
  }
3498
3667
  }
3499
3668
 
3669
+ /* eslint-disable no-underscore-dangle */
3500
3670
  const {
3501
3671
  TOK_LITERAL,
3502
3672
  TOK_COLON,
@@ -3538,6 +3708,7 @@ const {
3538
3708
  TOK_LBRACKET,
3539
3709
  TOK_LPAREN,
3540
3710
  } = tokenDefinitions;
3711
+
3541
3712
  const bindingPower = {
3542
3713
  [TOK_EOF]: 0,
3543
3714
  [TOK_UNQUOTEDIDENTIFIER]: 0,
@@ -3577,10 +3748,12 @@ const bindingPower = {
3577
3748
  [TOK_LBRACKET]: 55,
3578
3749
  [TOK_LPAREN]: 60,
3579
3750
  };
3751
+
3580
3752
  class Parser {
3581
3753
  constructor(allowedGlobalNames = []) {
3582
3754
  this._allowedGlobalNames = allowedGlobalNames;
3583
3755
  }
3756
+
3584
3757
  parse(expression, debug) {
3585
3758
  this._loadTokens(expression, debug);
3586
3759
  this.index = 0;
@@ -3595,12 +3768,14 @@ class Parser {
3595
3768
  }
3596
3769
  return ast;
3597
3770
  }
3771
+
3598
3772
  _loadTokens(expression, debug) {
3599
3773
  const lexer = new Lexer(this._allowedGlobalNames, debug);
3600
3774
  const tokens = lexer.tokenize(expression);
3601
3775
  tokens.push({ type: TOK_EOF, value: '', start: expression.length });
3602
3776
  this.tokens = tokens;
3603
3777
  }
3778
+
3604
3779
  expression(rbp) {
3605
3780
  const leftToken = this._lookaheadToken(0);
3606
3781
  this._advance();
@@ -3613,21 +3788,28 @@ class Parser {
3613
3788
  }
3614
3789
  return left;
3615
3790
  }
3791
+
3616
3792
  _lookahead(number) {
3617
3793
  return this.tokens[this.index + number].type;
3618
3794
  }
3795
+
3619
3796
  _lookaheadToken(number) {
3620
3797
  return this.tokens[this.index + number];
3621
3798
  }
3799
+
3622
3800
  _advance() {
3623
3801
  this.index += 1;
3624
3802
  }
3803
+
3625
3804
  _getIndex() {
3626
3805
  return this.index;
3627
3806
  }
3807
+
3628
3808
  _setIndex(index) {
3629
3809
  this.index = index;
3630
3810
  }
3811
+
3812
+ // eslint-disable-next-line consistent-return
3631
3813
  nud(token) {
3632
3814
  let left;
3633
3815
  let right;
@@ -3656,6 +3838,8 @@ class Parser {
3656
3838
  case TOK_STAR:
3657
3839
  left = { type: 'Identity' };
3658
3840
  if (this._lookahead(0) === TOK_RBRACKET) {
3841
+ // This can happen in a multiselect,
3842
+ // [a, b, *]
3659
3843
  right = { type: 'Identity' };
3660
3844
  } else {
3661
3845
  right = this._parseProjectionRHS(bindingPower.Star);
@@ -3702,6 +3886,8 @@ class Parser {
3702
3886
  this._errorToken(token);
3703
3887
  }
3704
3888
  }
3889
+
3890
+ // eslint-disable-next-line consistent-return
3705
3891
  led(tokenName, left) {
3706
3892
  let condition;
3707
3893
  let right;
@@ -3722,6 +3908,7 @@ class Parser {
3722
3908
  right = this._parseDotRHS(rbp);
3723
3909
  return { type: 'Subexpression', children: [left, right] };
3724
3910
  }
3911
+ // Creating a projection.
3725
3912
  this._advance();
3726
3913
  right = this._parseProjectionRHS(rbp);
3727
3914
  return { type: 'ValueProjection', children: [left, right] };
@@ -3799,6 +3986,7 @@ class Parser {
3799
3986
  this._errorToken(this._lookaheadToken(0));
3800
3987
  }
3801
3988
  }
3989
+
3802
3990
  _match(tokenType) {
3803
3991
  if (this._lookahead(0) === tokenType) {
3804
3992
  this._advance();
@@ -3809,6 +3997,8 @@ class Parser {
3809
3997
  throw error;
3810
3998
  }
3811
3999
  }
4000
+
4001
+ // eslint-disable-next-line class-methods-use-this
3812
4002
  _errorToken(token) {
3813
4003
  const error = new Error(`Invalid token (${
3814
4004
  token.type}): "${
@@ -3816,14 +4006,17 @@ class Parser {
3816
4006
  error.name = 'ParserError';
3817
4007
  throw error;
3818
4008
  }
4009
+
3819
4010
  _parseChainedIndexExpression() {
3820
4011
  const oldIndex = this._getIndex();
3821
4012
  if (this._lookahead(0) === TOK_COLON) {
3822
4013
  return this._parseSliceExpression();
3823
4014
  }
4015
+ // look ahead of the first expression to determine the type
3824
4016
  const first = this.expression(0);
3825
4017
  const token = this._lookahead(0);
3826
4018
  if (token === TOK_COLON) {
4019
+ // now that we know the type revert back to the old position and parse
3827
4020
  this._setIndex(oldIndex);
3828
4021
  return this._parseSliceExpression();
3829
4022
  }
@@ -3833,6 +4026,7 @@ class Parser {
3833
4026
  value: first,
3834
4027
  };
3835
4028
  }
4029
+
3836
4030
  _parseUnchainedIndexExpression() {
3837
4031
  const oldIndex = this._getIndex();
3838
4032
  const firstToken = this._lookahead(0);
@@ -3861,6 +4055,7 @@ class Parser {
3861
4055
  this._setIndex(oldIndex);
3862
4056
  return this._parseMultiselectList();
3863
4057
  }
4058
+
3864
4059
  _projectIfSlice(left, right) {
3865
4060
  const indexExpr = { type: 'IndexExpression', children: [left, right] };
3866
4061
  if (right.type === 'Slice') {
@@ -3871,16 +4066,20 @@ class Parser {
3871
4066
  }
3872
4067
  return indexExpr;
3873
4068
  }
4069
+
3874
4070
  _parseSliceExpression() {
4071
+ // [start:end:step] where each part is optional, as well as the last
4072
+ // colon.
3875
4073
  const parts = [null, null, null];
3876
4074
  let index = 0;
3877
4075
  let currentToken = this._lookahead(0);
3878
4076
  while (currentToken !== TOK_RBRACKET && index < 3) {
3879
- if (currentToken === TOK_COLON && index < 2) {
4077
+ if (currentToken === TOK_COLON && index < 2) { // there can't be more than 2 colons
3880
4078
  index += 1;
3881
4079
  this._advance();
3882
4080
  } else {
3883
4081
  parts[index] = this.expression(0);
4082
+ // check next token to be either colon or rbracket
3884
4083
  const t = this._lookahead(0);
3885
4084
  if (t !== TOK_COLON && t !== TOK_RBRACKET) {
3886
4085
  const error = new Error(`Syntax error, unexpected token: ${
@@ -3897,10 +4096,13 @@ class Parser {
3897
4096
  children: parts,
3898
4097
  };
3899
4098
  }
4099
+
3900
4100
  _parseComparator(left, comparator) {
3901
4101
  const right = this.expression(bindingPower[comparator]);
3902
4102
  return { type: 'Comparator', name: comparator, children: [left, right] };
3903
4103
  }
4104
+
4105
+ // eslint-disable-next-line consistent-return
3904
4106
  _parseDotRHS(rbp) {
3905
4107
  const lookahead = this._lookahead(0);
3906
4108
  const exprTokens = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER, TOK_STAR];
@@ -3916,6 +4118,7 @@ class Parser {
3916
4118
  return this._parseMultiselectHash();
3917
4119
  }
3918
4120
  }
4121
+
3919
4122
  _parseProjectionRHS(rbp) {
3920
4123
  let right;
3921
4124
  if (bindingPower[this._lookahead(0)] < 10) {
@@ -3936,6 +4139,7 @@ class Parser {
3936
4139
  }
3937
4140
  return right;
3938
4141
  }
4142
+
3939
4143
  _parseMultiselectList() {
3940
4144
  const expressions = [];
3941
4145
  while (this._lookahead(0) !== TOK_RBRACKET) {
@@ -3951,6 +4155,7 @@ class Parser {
3951
4155
  this._match(TOK_RBRACKET);
3952
4156
  return { type: 'MultiSelectList', children: expressions };
3953
4157
  }
4158
+
3954
4159
  _parseMultiselectHash() {
3955
4160
  const pairs = [];
3956
4161
  const identifierTypes = [TOK_UNQUOTEDIDENTIFIER, TOK_QUOTEDIDENTIFIER];
@@ -3983,6 +4188,20 @@ class Parser {
3983
4188
  }
3984
4189
  }
3985
4190
 
4191
+ /*
4192
+ Copyright 2021 Adobe. All rights reserved.
4193
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4194
+ you may not use this file except in compliance with the License. You may obtain a copy
4195
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
4196
+
4197
+ Unless required by applicable law or agreed to in writing, software distributed under
4198
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
4199
+ OF ANY KIND, either express or implied. See the License for the specific language
4200
+ governing permissions and limitations under the License.
4201
+ */
4202
+
4203
+ // get the offset in MS, given a date and timezone
4204
+ // timezone is an IANA name. e.g. 'America/New_York'
3986
4205
  function offsetMS(dateObj, timeZone) {
3987
4206
  const tzOffset = new Intl.DateTimeFormat('en-US', { timeZone, timeZoneName: 'longOffset' }).format(dateObj);
3988
4207
  const offset = /GMT([+\-−])?(\d{1,2}):?(\d{0,2})?/.exec(tzOffset);
@@ -3991,11 +4210,16 @@ function offsetMS(dateObj, timeZone) {
3991
4210
  const result = (((hours || 0) * 60) + 1 * (minutes || 0)) * 60 * 1000;
3992
4211
  return sign === '-' ? result * -1 : result;
3993
4212
  }
4213
+
3994
4214
  function round(num, digits) {
3995
4215
  const precision = 10 ** digits;
3996
4216
  return Math.round(num * precision) / precision;
3997
4217
  }
4218
+
3998
4219
  const MS_IN_DAY = 24 * 60 * 60 * 1000;
4220
+
4221
+ // If we create a non-UTC date, then we need to adjust from the default JavaScript timezone
4222
+ // to the default timezone
3999
4223
  function adjustTimeZone(dateObj, timeZone) {
4000
4224
  if (dateObj === null) return null;
4001
4225
  let baseDate = Date.UTC(
@@ -4008,10 +4232,32 @@ function adjustTimeZone(dateObj, timeZone) {
4008
4232
  dateObj.getMilliseconds(),
4009
4233
  );
4010
4234
  baseDate += offsetMS(dateObj, timeZone);
4235
+
4236
+ // get the offset for the default JS environment
4237
+ // return days since the epoch
4011
4238
  return new Date(baseDate);
4012
4239
  }
4240
+
4013
4241
  function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4014
4242
  return {
4243
+ /**
4244
+ * Returns the logical AND result of all parameters.
4245
+ * If the parameters are not boolean they will be cast to boolean as per the following rules
4246
+ * * null -> false
4247
+ * * number -> false if the number is 0, true otherwise
4248
+ * * string -> false if the string is empty, true otherwise. String "false" resolves to true
4249
+ * * array -> true
4250
+ * * object -> true
4251
+ * @param {any} firstOperand logical expression
4252
+ * @param {...any} [additionalOperands] any number of additional expressions
4253
+ * @returns {boolean} The logical result of applying AND to all parameters
4254
+ * @example
4255
+ * and(10 > 8, length('foo') < 5) // returns true
4256
+ * @example
4257
+ * and(`null`, length('foo') < 5) // returns false
4258
+ * @function
4259
+ * @category openFormula
4260
+ */
4015
4261
  and: {
4016
4262
  _func: resolvedArgs => {
4017
4263
  let result = !!valueOf(resolvedArgs[0]);
@@ -4022,6 +4268,17 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4022
4268
  },
4023
4269
  _signature: [{ types: [dataTypes.TYPE_ANY], variadic: true }],
4024
4270
  },
4271
+
4272
+ /**
4273
+ * Returns a lower-case string of the `input` string using locale-specific mappings.
4274
+ * e.g. Strings with German lowercase letter 'ß' can be compared to 'ss'
4275
+ * @param {string} input string to casefold
4276
+ * @returns {string} A new string converted to lower case
4277
+ * @function casefold
4278
+ * @example
4279
+ * casefold('AbC') // returns 'abc'
4280
+ * @category JSONFormula
4281
+ */
4025
4282
  casefold: {
4026
4283
  _func: (args, _data, interpreter) => {
4027
4284
  const str = toString(args[0]);
@@ -4031,6 +4288,34 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4031
4288
  { types: [dataTypes.TYPE_STRING] },
4032
4289
  ],
4033
4290
  },
4291
+
4292
+ /**
4293
+ * Return difference between two date values.
4294
+ * @param {number} start_date The starting date.
4295
+ * Dates should be entered by using the [datetime]{@link datetime} function
4296
+ * @param {number} end_date The end date -- must be greater or equal to start_date.
4297
+ * Dates should be entered by using the [datetime]{@link datetime} function
4298
+ * @param {string} unit One of:
4299
+ * * `y` the number of whole years between start_date and end_date
4300
+ * * `m` the number of whole months between start_date and end_date.
4301
+ * * `d` the number of days between start_date and end_date
4302
+ * * `md` the number of days between start_date and end_date after subtracting whole months.
4303
+ * * `ym` the number of whole months between start_date and end_date
4304
+ * after subtracting whole years.
4305
+ * * `yd` the number of days between start_date and end_date, assuming start_date
4306
+ * and end_date were no more than one year apart
4307
+ * @returns {integer} The number of days/months/years difference
4308
+ * @function
4309
+ * @category openFormula
4310
+ * @example
4311
+ * datedif(datetime(2001, 1, 1), datetime(2003, 1, 1), 'y') // returns 2
4312
+ * @example
4313
+ * datedif(datetime(2001, 6, 1), datetime(2003, 8, 15), 'D') // returns 440
4314
+ * // 440 days between June 1, 2001, and August 15, 2002 (440)
4315
+ * @example
4316
+ * datedif(datetime(2001, 6, 1), datetime(2003, 8, 15), 'YD') // returns 440
4317
+ * // 75 days between June 1 and August 15, ignoring the years of the dates (75)
4318
+ */
4034
4319
  datedif: {
4035
4320
  _func: args => {
4036
4321
  const d1 = toNumber(args[0]);
@@ -4044,6 +4329,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4044
4329
  const yearDiff = date2.getFullYear() - date1.getFullYear();
4045
4330
  let monthDiff = date2.getMonth() - date1.getMonth();
4046
4331
  const dayDiff = date2.getDate() - date1.getDate();
4332
+
4047
4333
  if (unit === 'y') {
4048
4334
  let y = yearDiff;
4049
4335
  if (monthDiff < 0) y -= 1;
@@ -4072,6 +4358,32 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4072
4358
  { types: [dataTypes.TYPE_STRING] },
4073
4359
  ],
4074
4360
  },
4361
+
4362
+ /**
4363
+ * Return a date/time value.
4364
+ * @param {integer} year Integer value representing the year.
4365
+ * Values from 0 to 99 map to the years 1900 to 1999. All other values are the actual year
4366
+ * @param {integer} month Integer value representing the month, beginning with 1 for
4367
+ * January to 12 for December.
4368
+ * @param {integer} day Integer value representing the day of the month.
4369
+ * @param {integer} [hours] Integer value between 0 and 23 representing the hour of the day.
4370
+ * Defaults to 0.
4371
+ * @param {integer} [minutes] Integer value representing the minute segment of a time.
4372
+ * The default is 0 minutes past the hour.
4373
+ * @param {integer} [seconds] Integer value representing the second segment of a time.
4374
+ * The default is 0 seconds past the minute.
4375
+ * @param {integer} [milliseconds] Integer value representing the millisecond segment of a time.
4376
+ * The default is 0 milliseconds past the second.
4377
+ * @param {string} [timeZoneName] according to IANA time zone names. e.g. "America/Toronto"
4378
+ * @returns {number} A date/time value represented by number of seconds since 1 January 1970.
4379
+ * @kind function
4380
+ * @function
4381
+ * @category JSONFormula
4382
+ * @example
4383
+ * datetime(2010, 10, 10) // returns representation of October 10, 2010
4384
+ * @example
4385
+ * datetime(2010, 2, 28) // returns representation of February 28, 2010
4386
+ */
4075
4387
  datetime: {
4076
4388
  _func: args => {
4077
4389
  const year = toNumber(args[0]);
@@ -4082,6 +4394,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4082
4394
  const seconds = args.length > 5 ? toNumber(args[5]) : 0;
4083
4395
  const ms = args.length > 6 ? toNumber(args[6]) : 0;
4084
4396
  const tz = args.length > 7 ? toString(args[7]) : null;
4397
+ // javascript months starts from 0
4085
4398
  let jsDate = new Date(year, month - 1, day, hours, minutes, seconds, ms);
4086
4399
  if (tz) {
4087
4400
  jsDate = adjustTimeZone(jsDate, tz);
@@ -4099,6 +4412,18 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4099
4412
  { types: [dataTypes.TYPE_STRING], optional: true },
4100
4413
  ],
4101
4414
  },
4415
+
4416
+ /**
4417
+ * Returns the day of a date, represented by a serial number.
4418
+ * The day is given as an integer ranging from 1 to 31.
4419
+ * @param {number} The date of the day you are trying to find.
4420
+ * Dates should be entered by using the [datetime]{@link datetime} function
4421
+ * @return {number}
4422
+ * @function day
4423
+ * @category openFormula
4424
+ * @example
4425
+ * day(datetime(2008,5,23)) //returns 23
4426
+ */
4102
4427
  day: {
4103
4428
  _func: args => {
4104
4429
  const date = toNumber(args[0]);
@@ -4109,6 +4434,19 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4109
4434
  { types: [dataTypes.TYPE_NUMBER] },
4110
4435
  ],
4111
4436
  },
4437
+
4438
+ /**
4439
+ * Searches a nested hierarchy of objects to return an array of elements that match a `name`.
4440
+ * The name can be either a key into a map or an array index.
4441
+ * This is similar to the JSONPath deep scan operator (..)
4442
+ * @param {object} object The starting object or array where we start the search
4443
+ * @param {string} name The name (or index position) of the elements to find
4444
+ * @returns {any}
4445
+ * @function
4446
+ * @category JSONFormula
4447
+ * @example
4448
+ * deepScan({a : {b1 : {c : 2}, b2 : {c : 3}}}, 'c') //returns [2, 3]
4449
+ */
4112
4450
  deepScan: {
4113
4451
  _func: resolvedArgs => {
4114
4452
  const [source, n] = resolvedArgs;
@@ -4129,6 +4467,16 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4129
4467
  { types: [dataTypes.TYPE_STRING, dataTypes.TYPE_NUMBER] },
4130
4468
  ],
4131
4469
  },
4470
+
4471
+ /**
4472
+ * returns an array of a given object's property `[key, value]` pairs.
4473
+ * @param {object} obj Object whose `[key, value]` pairs need to be extracted
4474
+ * @returns {any[]} an array of [key, value] pairs
4475
+ * @function entries
4476
+ * @category JSONFormula
4477
+ * @example
4478
+ * entries({a: 1, b: 2}) //returns [['a', 1], ['b', 2]]
4479
+ */
4132
4480
  entries: {
4133
4481
  _func: args => {
4134
4482
  const obj = valueOf(args[0]);
@@ -4146,11 +4494,27 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4146
4494
  },
4147
4495
  ],
4148
4496
  },
4497
+
4498
+ /**
4499
+ * Returns the serial number of the end of a month, given `startDate` plus `monthAdd` months
4500
+ * @param {number} startDate The base date to start from.
4501
+ * Dates should be entered by using the [datetime]{@link datetime} function
4502
+ * @param {integer} monthAdd Number of months to add to start date
4503
+ * @return {integer} the number of days in the computed month
4504
+ * @function
4505
+ * @category openFormula
4506
+ * @example
4507
+ * eomonth(datetime(2011, 1, 1), 1) //returns datetime(2011, 2, 28)
4508
+ * @example
4509
+ * eomonth(datetime(2011, 1, 1), -3) //returns datetime(2010, 10, 31)
4510
+ */
4149
4511
  eomonth: {
4150
4512
  _func: args => {
4151
4513
  const date = toNumber(args[0]);
4152
4514
  const months = toNumber(args[1]);
4153
4515
  const jsDate = new Date(date * MS_IN_DAY);
4516
+ // We can give the constructor a month value > 11 and it will increment the years
4517
+ // Since day is 1-based, giving zero will yield the last day of the previous month
4154
4518
  const newDate = new Date(jsDate.getFullYear(), jsDate.getMonth() + months + 1, 0);
4155
4519
  return newDate.getTime() / MS_IN_DAY;
4156
4520
  },
@@ -4159,6 +4523,16 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4159
4523
  { types: [dataTypes.TYPE_NUMBER] },
4160
4524
  ],
4161
4525
  },
4526
+
4527
+ /**
4528
+ * Returns e (the base of natural logarithms) raised to a power x. (i.e. e<sup>x</sup>)
4529
+ * @param x {number} A numeric expression representing the power of e.
4530
+ * @returns {number} e (the base of natural logarithms) raised to a power x
4531
+ * @function exp
4532
+ * @category openFormula
4533
+ * @example
4534
+ * exp(10) //returns e^10
4535
+ */
4162
4536
  exp: {
4163
4537
  _func: args => {
4164
4538
  const value = toNumber(args[0]);
@@ -4168,10 +4542,37 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4168
4542
  { types: [dataTypes.TYPE_NUMBER] },
4169
4543
  ],
4170
4544
  },
4545
+
4546
+ /**
4547
+ * Return constant boolean false value.
4548
+ * Note that expressions may also use the JSON literal false: `` `false` ``
4549
+ * @returns {boolean} constant boolean value `false`
4550
+ * @function
4551
+ * @category openFormula
4552
+ */
4171
4553
  false: {
4172
4554
  _func: () => false,
4173
4555
  _signature: [],
4174
4556
  },
4557
+
4558
+ /**
4559
+ * finds and returns the index of query in text from a start position
4560
+ * @param {string} query string to search
4561
+ * @param {string} text text in which the query has to be searched
4562
+ * @param {number} [start] starting position: defaults to 0
4563
+ * @returns {number|null} the index of the query to be searched in the text. If not found
4564
+ * returns null
4565
+ * @function
4566
+ * @category openFormula
4567
+ * @example
4568
+ * find('m', 'abm') //returns 2
4569
+ * @example
4570
+ * find('M', 'abMcdM', 3) //returns 2
4571
+ * @example
4572
+ * find('M', 'ab') //returns `null`
4573
+ * @example
4574
+ * find('M', 'abMcdM', 2) //returns 2
4575
+ */
4175
4576
  find: {
4176
4577
  _func: args => {
4177
4578
  const query = toString(args[0]);
@@ -4189,6 +4590,16 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4189
4590
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4190
4591
  ],
4191
4592
  },
4593
+
4594
+ /**
4595
+ * returns an object by transforming a list of key-value `pairs` into an object.
4596
+ * @param {any[]} pairs list of key-value pairs to create the object from
4597
+ * @returns {object}
4598
+ * @category JSONFormula
4599
+ * @function fromEntries
4600
+ * @example
4601
+ * fromEntries([['a', 1], ['b', 2]]) //returns {a: 1, b: 2}
4602
+ */
4192
4603
  fromEntries: {
4193
4604
  _func: args => {
4194
4605
  const array = args[0];
@@ -4198,19 +4609,51 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4198
4609
  { types: [dataTypes.TYPE_ARRAY_ARRAY] },
4199
4610
  ],
4200
4611
  },
4612
+
4613
+ /**
4614
+ * Extract the hour (0 through 23) from a time/datetime representation
4615
+ * @param {number} The datetime/time for which the hour is to be returned.
4616
+ * Dates should be specified using the [datetime]{@link datetime} or [time]{@link time} function
4617
+ * @return {number}
4618
+ * @function hour
4619
+ * @category openFormula
4620
+ * @example
4621
+ * hour(datetime(2008,5,23,12, 0, 0)) //returns 12
4622
+ * hour(time(12, 0, 0)) //returns 12
4623
+ */
4201
4624
  hour: {
4202
4625
  _func: args => {
4626
+ // grab just the fraction part
4203
4627
  const time = toNumber(args[0]) % 1;
4204
4628
  if (time < 0) {
4205
4629
  return null;
4206
4630
  }
4631
+ // Normally we'd round to 15 digits, but since we're also multiplying by 24,
4632
+ // a reasonable precision is around 14 digits.
4633
+
4207
4634
  const hour = round(time * 24, 14);
4635
+
4208
4636
  return Math.floor(hour % 24);
4209
4637
  },
4210
4638
  _signature: [
4211
4639
  { types: [dataTypes.TYPE_NUMBER] },
4212
4640
  ],
4213
4641
  },
4642
+
4643
+ /**
4644
+ * Return one of two values `result1` or `result2`, depending on the `condition`
4645
+ * @returns {boolean} True
4646
+ * @param {any} condition logical expression to evaluate
4647
+ * @param {any} result1 if logical condition is true
4648
+ * @param {any} result2 if logical condition is false
4649
+ * @return {any} either result1 or result2
4650
+ * @function
4651
+ * @category openFormula
4652
+ * @example
4653
+ * if(true(), 1, 2) // returns 1
4654
+ * @example
4655
+ * if(false(), 1, 2) // returns 2
4656
+ */
4214
4657
  if: {
4215
4658
  _func: (unresolvedArgs, data, interpreter) => {
4216
4659
  const conditionNode = unresolvedArgs[0];
@@ -4227,6 +4670,22 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4227
4670
  { types: [dataTypes.TYPE_ANY] },
4228
4671
  { types: [dataTypes.TYPE_ANY] }],
4229
4672
  },
4673
+
4674
+ /**
4675
+ * Return a selected number of text characters from the left or
4676
+ * in case of array selected number of elements from the start
4677
+ * @param {string|array} subject The text/array of characters/elements to extract.
4678
+ * @param {number} [elements] number of elements to pick. Defaults to 1
4679
+ * @return {string|array}
4680
+ * @function left
4681
+ * @category openFormula
4682
+ * @example
4683
+ * left('Sale Price', 4) //returns 'Sale'
4684
+ * @example
4685
+ * left('Sweden') // returns 'S'
4686
+ * @example
4687
+ * left([4, 5, 6], 2) // returns [4, 5]
4688
+ */
4230
4689
  left: {
4231
4690
  _func: args => {
4232
4691
  const numEntries = args.length > 1 ? toNumber(args[1]) : 1;
@@ -4242,6 +4701,18 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4242
4701
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4243
4702
  ],
4244
4703
  },
4704
+
4705
+ /**
4706
+ * Converts all the alphabetic characters in a string to lowercase. If the value
4707
+ * is not a string it will be converted into string
4708
+ * using the default toString method
4709
+ * @param {string} input input string
4710
+ * @returns {string} the lower case value of the input string
4711
+ * @function lower
4712
+ * @category openFormula
4713
+ * @example
4714
+ * lower('E. E. Cummings') //returns e. e. cummings
4715
+ */
4245
4716
  lower: {
4246
4717
  _func: args => {
4247
4718
  const value = toString(args[0]);
@@ -4251,6 +4722,27 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4251
4722
  { types: [dataTypes.TYPE_STRING] },
4252
4723
  ],
4253
4724
  },
4725
+
4726
+ /**
4727
+ * Returns extracted text, given an original text, starting position, and length.
4728
+ * or in case of array, extracts a subset of the array from start till the length
4729
+ * number of elements.
4730
+ * Returns null if the `startPos` is greater than the length of the array
4731
+ * @param {string|array} subject the text string or array of characters or elements to extract.
4732
+ * @param {number} startPos the position of the first character or element to extract.
4733
+ * The position starts with 0
4734
+ * @param {number} length The number of characters or elements to return from text. If it
4735
+ * is greater then the length of `subject` the argument is set to the length of the subject.
4736
+ * @return {string|array}
4737
+ * @function mid
4738
+ * @category openFormula
4739
+ * @example
4740
+ * mid("Fluid Flow",1,5) //returns 'Fluid'
4741
+ * @example
4742
+ * mid("Fluid Flow",7,20) //returns 'Flow'
4743
+ * @example
4744
+ * mid("Fluid Flow",20,5) //returns `null`
4745
+ */
4254
4746
  mid: {
4255
4747
  _func: args => {
4256
4748
  const startPos = toNumber(args[1]);
@@ -4268,12 +4760,27 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4268
4760
  { types: [dataTypes.TYPE_NUMBER] },
4269
4761
  ],
4270
4762
  },
4763
+
4764
+ /**
4765
+ * Extract the minute (0 through 59) from a time/datetime representation
4766
+ * @param {number} The datetime/time for which the minute is to be returned.
4767
+ * Dates should be specified using the [datetime]{@link datetime} or [time]{@link time} function
4768
+ * @return {number}
4769
+ * @function minute
4770
+ * @category openFormula
4771
+ * @example
4772
+ * month(datetime(2008,5,23,12, 10, 0)) //returns 10
4773
+ * month(time(12, 10, 0)) //returns 10
4774
+ */
4271
4775
  minute: {
4272
4776
  _func: args => {
4273
4777
  const time = toNumber(args[0]) % 1;
4274
4778
  if (time < 0) {
4275
4779
  return null;
4276
4780
  }
4781
+
4782
+ // Normally we'd round to 15 digits, but since we're also multiplying by 1440,
4783
+ // a reasonable precision is around 10 digits.
4277
4784
  const minute = Math.round(time * 1440, 10);
4278
4785
  return Math.floor(minute % 60);
4279
4786
  },
@@ -4281,6 +4788,20 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4281
4788
  { types: [dataTypes.TYPE_NUMBER] },
4282
4789
  ],
4283
4790
  },
4791
+
4792
+ /**
4793
+ * Return the remainder when one number is divided by another number.
4794
+ * The sign is the same as divisor
4795
+ * @param {number} dividend The number for which to find the remainder.
4796
+ * @param {number} divisor The number by which to divide number.
4797
+ * @return {number} Computes the remainder of `dividend`/`divisor`.
4798
+ * @function mod
4799
+ * @category openFormula
4800
+ * @example
4801
+ * mod(3, 2) //returns 1
4802
+ * @example
4803
+ * mod(-3, 2) //returns 1
4804
+ */
4284
4805
  mod: {
4285
4806
  _func: args => {
4286
4807
  const p1 = toNumber(args[0]);
@@ -4292,28 +4813,97 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4292
4813
  { types: [dataTypes.TYPE_NUMBER] },
4293
4814
  ],
4294
4815
  },
4816
+
4817
+ /**
4818
+ * Returns the month of a date represented by a serial number.
4819
+ * The month is given as an integer, ranging from 1 (January) to 12 (December).
4820
+ * @param {number} The date for which the month is to be returned.
4821
+ * Dates should be entered by using the [datetime]{@link datetime} function
4822
+ * @return {number}
4823
+ * @function month
4824
+ * @category openFormula
4825
+ * @example
4826
+ * month(datetime(2008,5,23)) //returns 5
4827
+ */
4295
4828
  month: {
4296
4829
  _func: args => {
4297
4830
  const date = toNumber(args[0]);
4298
4831
  const jsDate = new Date(date * MS_IN_DAY);
4832
+ // javascript months start from 0ß
4299
4833
  return jsDate.getMonth() + 1;
4300
4834
  },
4301
4835
  _signature: [
4302
4836
  { types: [dataTypes.TYPE_NUMBER] },
4303
4837
  ],
4304
4838
  },
4839
+
4840
+ /**
4841
+ * Compute logical NOT of a `value`. If the parameter is not boolean it will be cast to boolean
4842
+ * as per the following rules
4843
+ * * null -> false
4844
+ * * number -> false if the number is 0, true otherwise
4845
+ * * string -> false if the string is empty, true otherwise. String "false" resolves to true
4846
+ * * array -> true
4847
+ * * object -> true
4848
+ * Note that it is also possible to use the logical and operator: `A && B`
4849
+ * @param {any} value - any data type
4850
+ * @returns {boolean} The logical NOT applied to the input parameter
4851
+ * @example
4852
+ * not(length('bar') > 0) // returns false
4853
+ * @example
4854
+ * not(false()) // returns true
4855
+ * @example
4856
+ * not('abcd') // returns false
4857
+ * @example
4858
+ * not('') // returns true
4859
+ * @function
4860
+ * @category openFormula
4861
+ */
4305
4862
  not: {
4306
4863
  _func: resolveArgs => !valueOf(resolveArgs[0]),
4307
4864
  _signature: [{ types: [dataTypes.TYPE_ANY] }],
4308
4865
  },
4866
+
4867
+ /**
4868
+ * returns the time since epoch with days as exponent and time of day as fraction
4869
+ * @return {number} representation of current time as a number
4870
+ * @function now
4871
+ * @category openFormula
4872
+ */
4309
4873
  now: {
4310
4874
  _func: () => Date.now() / MS_IN_DAY,
4311
4875
  _signature: [],
4312
4876
  },
4877
+
4878
+ /**
4879
+ * Return constant null value.
4880
+ * Note that expressions may also use the JSON literal null: `` `null` ``
4881
+ * @returns {boolean} True
4882
+ * @function
4883
+ * @category JSONFormula
4884
+ */
4313
4885
  null: {
4314
4886
  _func: () => null,
4315
4887
  _signature: [],
4316
4888
  },
4889
+
4890
+ /**
4891
+ * Returns the logical OR result of two parameters.
4892
+ * If the parameters are not boolean they will be cast to boolean as per the following rules
4893
+ * * null -> false
4894
+ * * number -> false if the number is 0, true otherwise
4895
+ * * string -> false if the string is empty, true otherwise. String "false" resolves to true
4896
+ * * array -> true
4897
+ * * object -> true
4898
+ * @param {any} first logical expression
4899
+ * @param {...any} [operand] any number of additional expressions
4900
+ * @returns {boolean} The logical result of applying OR to all parameters
4901
+ * @example
4902
+ * or((x / 2) == y, (y * 2) == x)
4903
+ * // true
4904
+ * @function
4905
+ * @category openFormula
4906
+ */
4317
4907
  or: {
4318
4908
  _func: resolvedArgs => {
4319
4909
  let result = !!valueOf(resolvedArgs[0]);
@@ -4324,6 +4914,17 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4324
4914
  },
4325
4915
  _signature: [{ types: [dataTypes.TYPE_ANY], variadic: true }],
4326
4916
  },
4917
+
4918
+ /**
4919
+ * Computes `a` raised to a power `x`. (a<sup>x</sup>)
4920
+ * @param {number} a The base number. It can be any real number.
4921
+ * @param {number} x The exponent to which the base number is raised.
4922
+ * @return {number}
4923
+ * @function power
4924
+ * @category openFormula
4925
+ * @example
4926
+ * power(10, 2) //returns 100 (10 raised to power 2)
4927
+ */
4327
4928
  power: {
4328
4929
  _func: args => {
4329
4930
  const base = toNumber(args[0]);
@@ -4335,6 +4936,21 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4335
4936
  { types: [dataTypes.TYPE_NUMBER] },
4336
4937
  ],
4337
4938
  },
4939
+
4940
+ /**
4941
+ * Return the input string with the first letter of each word converted to an
4942
+ * uppercase letter and the rest of the letters in the word converted to lowercase.
4943
+ * @param {string} text the text to partially capitalize.
4944
+ * @returns {string}
4945
+ * @function proper
4946
+ * @category openFormula
4947
+ * @example
4948
+ * proper('this is a TITLE') //returns 'This Is A Title'
4949
+ * @example
4950
+ * proper('2-way street') //returns '2-Way Street'
4951
+ * @example
4952
+ * proper('76BudGet') //returns '76Budget'
4953
+ */
4338
4954
  proper: {
4339
4955
  _func: args => {
4340
4956
  const text = toString(args[0]);
@@ -4347,6 +4963,24 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4347
4963
  { types: [dataTypes.TYPE_STRING] },
4348
4964
  ],
4349
4965
  },
4966
+
4967
+ /**
4968
+ * Returns text where an old text is substituted at a given start position and
4969
+ * length, with a new text.
4970
+ * @param {string} text original text
4971
+ * @param {number} start index in the original text from where to begin the replacement.
4972
+ * @param {number} length number of characters to be replaced
4973
+ * @param {string} replacement string to replace at the start index
4974
+ * @returns {string}
4975
+ * @function replace
4976
+ * @category openFormula
4977
+ * @example
4978
+ * replace('abcdefghijk', 6, 5, '*') //returns abcde*k
4979
+ * @example
4980
+ * replace('2009',3,2,'10') //returns 2010
4981
+ * @example
4982
+ * replace('123456',1,3,'@') //returns @456
4983
+ */
4350
4984
  replace: {
4351
4985
  _func: args => {
4352
4986
  const oldText = toString(args[0]);
@@ -4356,6 +4990,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4356
4990
  if (startNum < 0) {
4357
4991
  return null;
4358
4992
  }
4993
+
4359
4994
  const lhs = oldText.substr(0, startNum);
4360
4995
  const rhs = oldText.substr(startNum + numChars);
4361
4996
  return lhs + newText + rhs;
@@ -4367,6 +5002,17 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4367
5002
  { types: [dataTypes.TYPE_STRING] },
4368
5003
  ],
4369
5004
  },
5005
+
5006
+ /**
5007
+ * Return text repeated Count times.
5008
+ * @param {string} text text to repeat
5009
+ * @param {number} count number of times to repeat the text
5010
+ * @returns {string}
5011
+ * @function rept
5012
+ * @category openFormula
5013
+ * @example
5014
+ * rept('x', 5) //returns 'xxxxx'
5015
+ */
4370
5016
  rept: {
4371
5017
  _func: args => {
4372
5018
  const text = toString(args[0]);
@@ -4381,6 +5027,23 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4381
5027
  { types: [dataTypes.TYPE_NUMBER] },
4382
5028
  ],
4383
5029
  },
5030
+
5031
+ /**
5032
+ * Return a selected number of text characters from the right of a `subject` or
5033
+ * in case of array selected number of elements from the end of `subject` array
5034
+ * Returns null if the number of elements is less than 0
5035
+ * @param {string|array} subject The text/array containing the characters/elements to extract.
5036
+ * @param {number} [elements] number of elements to pick. Defaults to 1
5037
+ * @return {string|array}
5038
+ * @function right
5039
+ * @category openFormula
5040
+ * @example
5041
+ * right('Sale Price', 4) //returns 'rice'
5042
+ * @example
5043
+ * left('Sweden') // returns 'n'
5044
+ * @example
5045
+ * left([4, 5, 6], 2) // returns [5, 6]
5046
+ */
4384
5047
  right: {
4385
5048
  _func: args => {
4386
5049
  const numEntries = args.length > 1 ? toNumber(args[1]) : 1;
@@ -4398,6 +5061,29 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4398
5061
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4399
5062
  ],
4400
5063
  },
5064
+
5065
+ /**
5066
+ * Round a number to a specified `precision`.
5067
+ * ### Remarks
5068
+ * * If `precision` is greater than zero, round to the specified number of decimal places.
5069
+ * * If `precision` is 0, round to the nearest integer.
5070
+ * * If `precision` is less than 0, round to the left of the decimal point.
5071
+ * @param {number} num number to round off
5072
+ * @param {number} precision number is rounded to the specified precision.
5073
+ * @returns {number}
5074
+ * @function round
5075
+ * @category openFormula
5076
+ * @example
5077
+ * round(2.15, 1) //returns 2.2
5078
+ * @example
5079
+ * round(626.3,-3) //returns 1000 (Rounds 626.3 to the nearest multiple of 1000)
5080
+ * @example
5081
+ * round(626.3, 0) //returns 626
5082
+ * @example
5083
+ * round(1.98,-1) //returns 0 (Rounds 1.98 to the nearest multiple of 10)
5084
+ * @example
5085
+ * round(-50.55,-2) // -100 (round -50.55 to the nearest multiple of 100)
5086
+ */
4401
5087
  round: {
4402
5088
  _func: args => {
4403
5089
  const number = toNumber(args[0]);
@@ -4409,15 +5095,38 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4409
5095
  { types: [dataTypes.TYPE_NUMBER] },
4410
5096
  ],
4411
5097
  },
5098
+
5099
+ /**
5100
+ * Perform a wildcard search. The search is case-sensitive and supports two forms of wildcards:
5101
+ * "*" finds a a sequence of characters and "?" finds a single character.
5102
+ * To use "*" or "?" as text values, precede them with a tilde ("~") character.
5103
+ * Note that the wildcard search is not greedy.
5104
+ * e.g. search('a*b', 'abb') will return [0, 'ab'] Not [0, 'abb']
5105
+ * @param {string} findText the search string -- which may include wild cards.
5106
+ * @param {string} withinText The string to search.
5107
+ * @param {integer} startPos The zero-based position of withinText to start searching.
5108
+ * Defaults to zero.
5109
+ * @returns {array} returns an array with two values:
5110
+ * The start position of the found text and the text string that was found.
5111
+ * If a match was not found, an empty array is returned.
5112
+ * @function search
5113
+ * @category openFormula
5114
+ * @example
5115
+ * search('a?c', 'acabc') //returns [2, 'abc']
5116
+ */
4412
5117
  search: {
4413
5118
  _func: args => {
4414
5119
  const findText = toString(args[0]);
4415
5120
  const withinText = toString(args[1]);
4416
5121
  const startPos = toNumber(args[2]);
4417
5122
  if (findText === null || withinText === null || withinText.length === 0) return [];
5123
+ // escape all characters that would otherwise create a regular expression
4418
5124
  const reString = findText.replace(/([[.\\^$()+{])/g, '\\$1')
5125
+ // add the single character wildcard
4419
5126
  .replace(/~?\?/g, match => match === '~?' ? '\\?' : '.')
5127
+ // add the multi-character wildcard
4420
5128
  .replace(/~?\*/g, match => match === '~*' ? '\\*' : '.*?')
5129
+ // get rid of the escape characters
4421
5130
  .replace(/~~/g, '~');
4422
5131
  const re = new RegExp(reString);
4423
5132
  const result = withinText.substring(startPos).match(re);
@@ -4429,13 +5138,28 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4429
5138
  { types: [dataTypes.TYPE_STRING] },
4430
5139
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4431
5140
  ],
5141
+
4432
5142
  },
5143
+ /**
5144
+ * Extract the second (0 through 59) from a time/datetime representation
5145
+ * @param {number} The datetime/time for which the second is to be returned.
5146
+ * Dates should be specified using the [datetime]{@link datetime} or [time]{@link time} function
5147
+ * @return {number}
5148
+ * @function second
5149
+ * @category openFormula
5150
+ * @example
5151
+ * second(datetime(2008,5,23,12, 10, 53)) //returns 53
5152
+ * second(time(12, 10, 53)) //returns 53
5153
+ */
4433
5154
  second: {
4434
5155
  _func: args => {
4435
5156
  const time = toNumber(args[0]) % 1;
4436
5157
  if (time < 0) {
4437
5158
  return null;
4438
5159
  }
5160
+
5161
+ // Normally we'd round to 15 digits, but since we're also multiplying by 86400,
5162
+ // a reasonable precision is around 10 digits.
4439
5163
  const seconds = round(time * 86400, 10);
4440
5164
  return Math.floor(seconds % 60);
4441
5165
  },
@@ -4443,6 +5167,19 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4443
5167
  { types: [dataTypes.TYPE_NUMBER] },
4444
5168
  ],
4445
5169
  },
5170
+
5171
+ /**
5172
+ * split a string into an array, given a separator
5173
+ * @param {string} string string to split
5174
+ * @param {string} separator separator where the split should occur
5175
+ * @return {string[]}
5176
+ * @function split
5177
+ * @category openFormula
5178
+ * @example
5179
+ * split('abcdef', '') //returns ['a', 'b', 'c', 'd', 'e', 'f']
5180
+ * @example
5181
+ * split('abcdef', 'e') //returns ['abcd', 'f']
5182
+ */
4446
5183
  split: {
4447
5184
  _func: args => {
4448
5185
  const str = toString(args[0]);
@@ -4454,6 +5191,16 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4454
5191
  { types: [dataTypes.TYPE_STRING] },
4455
5192
  ],
4456
5193
  },
5194
+
5195
+ /**
5196
+ * Return the square root of a number
5197
+ * @param {number} num number whose square root has to be calculated
5198
+ * @return {number}
5199
+ * @function sqrt
5200
+ * @category openFormula
5201
+ * @example
5202
+ * sqrt(4) //returns 2
5203
+ */
4457
5204
  sqrt: {
4458
5205
  _func: args => {
4459
5206
  const result = Math.sqrt(toNumber(args[0]));
@@ -4466,6 +5213,20 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4466
5213
  { types: [dataTypes.TYPE_NUMBER] },
4467
5214
  ],
4468
5215
  },
5216
+
5217
+ /**
5218
+ * Estimates standard deviation based on a sample.
5219
+ * `stdev` assumes that its arguments are a sample of the entire population.
5220
+ * If your data represents a entire population,
5221
+ * then compute the standard deviation using [stdevp]{@link stdevp}.
5222
+ * @param {number[]} numbers The array of numbers comprising the population
5223
+ * @returns {number}
5224
+ * @category openFormula
5225
+ * @function stdev
5226
+ * @example
5227
+ * stdev([1345, 1301, 1368]) //returns 34.044089061098404
5228
+ * stdevp([1345, 1301, 1368]) //returns 27.797
5229
+ */
4469
5230
  stdev: {
4470
5231
  _func: args => {
4471
5232
  const values = args[0] || [];
@@ -4477,6 +5238,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4477
5238
  const sumSquare = coercedValues.reduce((a, b) => a + b * b, 0);
4478
5239
  const result = Math.sqrt((sumSquare - values.length * mean * mean) / (values.length - 1));
4479
5240
  if (Number.isNaN(result)) {
5241
+ // this would never happen
4480
5242
  return null;
4481
5243
  }
4482
5244
  return result;
@@ -4485,6 +5247,20 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4485
5247
  { types: [dataTypes.TYPE_ARRAY_NUMBER] },
4486
5248
  ],
4487
5249
  },
5250
+
5251
+ /**
5252
+ * Calculates standard deviation based on the entire population given as arguments.
5253
+ * `stdevp` assumes that its arguments are the entire population.
5254
+ * If your data represents a sample of the population,
5255
+ * then compute the standard deviation using [stdev]{@link stdev}.
5256
+ * @param {number[]} numbers The array of numbers comprising the population
5257
+ * @returns {number}
5258
+ * @category openFormula
5259
+ * @function stdevp
5260
+ * @example
5261
+ * stdevp([1345, 1301, 1368]) //returns 27.797
5262
+ * stdev([1345, 1301, 1368]) //returns 34.044
5263
+ */
4488
5264
  stdevp: {
4489
5265
  _func: args => {
4490
5266
  const values = args[0] || [];
@@ -4496,6 +5272,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4496
5272
  const meanSumSquare = coercedValues.reduce((a, b) => a + b * b, 0) / values.length;
4497
5273
  const result = Math.sqrt(meanSumSquare - mean * mean);
4498
5274
  if (Number.isNaN(result)) {
5275
+ // this would never happen
4499
5276
  return null;
4500
5277
  }
4501
5278
  return result;
@@ -4504,18 +5281,43 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4504
5281
  { types: [dataTypes.TYPE_ARRAY_NUMBER] },
4505
5282
  ],
4506
5283
  },
5284
+
5285
+ /**
5286
+ * Returns input `text`, with text `old` replaced by text `new` (when searching from the left).
5287
+ * If `which` parameter is omitted, every occurrence of `old` is replaced with `new`;
5288
+ * If `which` is provided, only that occurrence of `old` is replaced by `new`
5289
+ * (starting the count from 1).
5290
+ * If there is no match, or if `old` has length 0, `text` is returned unchanged.
5291
+ * Note that `old` and `new` may have different lengths. If `which` < 1, return `text` unchanged
5292
+ * @param {string} text The text for which to substitute characters.
5293
+ * @param {string} old The text to replace.
5294
+ * @param {string} new The text to replace `old` with.
5295
+ * @param {integer} [which] The one-based occurrence of `old` text to replace with `new` text.
5296
+ * @returns {string} replaced string
5297
+ * @function
5298
+ * @category openFormula
5299
+ * @example
5300
+ * substitute('Sales Data', 'Sales', 'Cost') //returns 'Cost Data'
5301
+ * @example
5302
+ * substitute('Quarter 1, 2008', '1', '2', 1) //returns 'Quarter 2, 2008'
5303
+ * @example
5304
+ * substitute('Quarter 1, 1008', '1', '2', 2) //returns 'Quarter 1, 2008'
5305
+ */
4507
5306
  substitute: {
4508
5307
  _func: args => {
4509
5308
  const src = toString(args[0]);
4510
5309
  const old = toString(args[1]);
4511
5310
  const replacement = toString(args[2]);
5311
+ // no third parameter? replace all instances
4512
5312
  if (args.length <= 3) return src.replaceAll(old, replacement);
4513
5313
  const whch = toNumber(args[3]);
4514
5314
  if (whch < 1) return src;
5315
+ // find the instance to replace
4515
5316
  let pos = -1;
4516
5317
  for (let i = 0; i < whch; i += 1) {
4517
5318
  pos += 1;
4518
5319
  const nextFind = src.slice(pos).indexOf(old);
5320
+ // no instance to match 'Which'
4519
5321
  if (nextFind === -1) return src;
4520
5322
  pos += nextFind;
4521
5323
  }
@@ -4528,6 +5330,21 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4528
5330
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4529
5331
  ],
4530
5332
  },
5333
+
5334
+ /**
5335
+ * Construct and returns time from hours, minutes, and seconds.
5336
+ * @param {integer} hours Integer value between 0 and 23 representing the hour of the day.
5337
+ * Defaults to 0.
5338
+ * @param {integer} minutes Integer value representing the minute segment of a time.
5339
+ * The default is 0 minutes past the hour.
5340
+ * @param {integer} seconds Integer value representing the second segment of a time.
5341
+ * The default is 0 seconds past the minute.
5342
+ * @return {number} Returns the fraction of the day consumed by the given time
5343
+ * @function time
5344
+ * @category openFormula
5345
+ * @example
5346
+ * time(12, 0, 0) //returns 0.5 (half day)
5347
+ */
4531
5348
  time: {
4532
5349
  _func: args => {
4533
5350
  const hours = toNumber(args[0]);
@@ -4545,23 +5362,65 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4545
5362
  { types: [dataTypes.TYPE_NUMBER] },
4546
5363
  ],
4547
5364
  },
5365
+
5366
+ /**
5367
+ * returns the number of days since epoch
5368
+ * @return number
5369
+ * @function today
5370
+ * @category openFormula
5371
+ */
4548
5372
  today: {
4549
5373
  _func: () => Math.floor(Date.now() / MS_IN_DAY),
4550
5374
  _signature: [],
4551
5375
  },
5376
+
5377
+ /**
5378
+ * Remove leading and trailing spaces, and replace all internal multiple spaces
5379
+ * with a single space.
5380
+ * @param {string} text string to trim
5381
+ * @return {string} removes all leading and trailing space.
5382
+ * Any other sequence of 2 or more spaces is replaced with a single space.
5383
+ * @function trim
5384
+ * @category openFormula
5385
+ * @example
5386
+ * trim(' ab c ') //returns 'ab c'
5387
+ */
4552
5388
  trim: {
4553
5389
  _func: args => {
4554
5390
  const text = toString(args[0]);
5391
+ // only removes the space character
5392
+ // other whitespace characters like \t \n left intact
4555
5393
  return text.split(' ').filter(x => x).join(' ');
4556
5394
  },
4557
5395
  _signature: [
4558
5396
  { types: [dataTypes.TYPE_STRING] },
4559
5397
  ],
4560
5398
  },
5399
+
5400
+ /**
5401
+ * Return constant boolean true value.
5402
+ * Note that expressions may also use the JSON literal true: `` `true` ``
5403
+ * @returns {boolean} True
5404
+ * @function
5405
+ * @category openFormula
5406
+ */
4561
5407
  true: {
4562
5408
  _func: () => true,
4563
5409
  _signature: [],
4564
5410
  },
5411
+
5412
+ /**
5413
+ * Truncates a number to an integer by removing the fractional part of the number.
5414
+ * @param {number} numA number to truncate
5415
+ * @param {number} [numB] A number specifying the precision of the truncation. Default is 0
5416
+ * @return {number}
5417
+ * @function trunc
5418
+ * @category openFormula
5419
+ * @example
5420
+ * trunc(8.9) //returns 8
5421
+ * trunc(-8.9) //returns -8
5422
+ * trunc(8.912, 2) //returns 8.91
5423
+ */
4565
5424
  trunc: {
4566
5425
  _func: args => {
4567
5426
  const number = toNumber(args[0]);
@@ -4574,8 +5433,20 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4574
5433
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4575
5434
  ],
4576
5435
  },
5436
+
5437
+ /**
5438
+ * takes an array and returns unique elements within it
5439
+ * @param {array} input input array
5440
+ * @return {array} array with duplicate elements removed
5441
+ * @function unique
5442
+ * @category JSONFormula
5443
+ * @example
5444
+ * unique([1, 2, 3, 4, 1, 1, 2]) //returns [1, 2, 3, 4]
5445
+ */
4577
5446
  unique: {
4578
5447
  _func: args => {
5448
+ // create an array of values for searching. That way if the array elements are
5449
+ // represented by objects with a valueOf(), then we'll locate them in the valueArray
4579
5450
  const valueArray = args[0].map(a => valueOf(a));
4580
5451
  return args[0].filter((v, index) => valueArray.indexOf(valueOf(v)) === index);
4581
5452
  },
@@ -4583,6 +5454,18 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4583
5454
  { types: [dataTypes.TYPE_ARRAY] },
4584
5455
  ],
4585
5456
  },
5457
+
5458
+ /**
5459
+ * Converts all the alphabetic characters in a string to uppercase.
5460
+ * If the value is not a string it will be converted into string
5461
+ * using the default toString method
5462
+ * @param {string} input input string
5463
+ * @returns {string} the upper case value of the input string
5464
+ * @function upper
5465
+ * @category openFormula
5466
+ * @example
5467
+ * upper('abcd') //returns 'ABCD'
5468
+ */
4586
5469
  upper: {
4587
5470
  _func: args => {
4588
5471
  const value = toString(args[0]);
@@ -4592,6 +5475,19 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4592
5475
  { types: [dataTypes.TYPE_STRING] },
4593
5476
  ],
4594
5477
  },
5478
+
5479
+ /**
5480
+ * Perform an indexed lookup on a map or array
5481
+ * @param {map | array} object on which to perform the lookup
5482
+ * @param {string | integer} index: a named child for a map or an integer offset for an array
5483
+ * @returns {any} the result of the lookup -- or `null` if not found.
5484
+ * @function
5485
+ * @category JSONFormula
5486
+ * @example
5487
+ * value({a: 1, b:2, c:3}, a) //returns 1
5488
+ * @example
5489
+ * value([1, 2, 3, 4], 2) //returns 3
5490
+ */
4595
5491
  value: {
4596
5492
  _func: args => {
4597
5493
  const obj = args[0] || {};
@@ -4601,6 +5497,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4601
5497
  debug.push(`Failed to find: '${index}'`);
4602
5498
  const available = Object.keys(obj).map(a => `'${a}'`).toString();
4603
5499
  if (available.length) debug.push(`Available fields: ${available}`);
5500
+
4604
5501
  return null;
4605
5502
  }
4606
5503
  return result;
@@ -4610,18 +5507,43 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4610
5507
  { types: [dataTypes.TYPE_STRING, dataTypes.TYPE_NUMBER] },
4611
5508
  ],
4612
5509
  },
5510
+
5511
+ /**
5512
+ * Extract the day of the week from a date; if text, uses current locale to convert to a date.
5513
+ * @param {number} The datetime for which the day of the week is to be returned.
5514
+ * Dates should be entered by using the [datetime]{@link datetime} function
5515
+ * @param {number} [returnType] A number that determines the
5516
+ * numeral representation (a number from 0 to 7) of the
5517
+ * day of week. Default is 1. Supports the following values
5518
+ * * 1 : Sunday (1), Monday (2), ..., Saturday (7)
5519
+ * * 2 : Monday (1), Tuesday (2), ..., Sunday(7)
5520
+ * * 3 : Monday (0), Tuesday (2), ...., Sunday(6)
5521
+ * @returns {number} day of the week
5522
+ * @function weekday
5523
+ * @category openFormula
5524
+ * @example
5525
+ * weekday(datetime(2006,5,21)) // 1
5526
+ * @example
5527
+ * weekday(datetime(2006,5,21), 2) // 7
5528
+ * @example
5529
+ * weekday(datetime(2006,5,21), 3) // 6
5530
+ */
4613
5531
  weekday: {
4614
5532
  _func: args => {
4615
5533
  const date = toNumber(args[0]);
4616
5534
  const type = args.length > 1 ? toNumber(args[1]) : 1;
4617
5535
  const jsDate = new Date(date * MS_IN_DAY);
4618
5536
  const day = jsDate.getDay();
5537
+ // day is in range [0-7) with 0 mapping to sunday
4619
5538
  switch (type) {
4620
5539
  case 1:
5540
+ // range = [1, 7], sunday = 1
4621
5541
  return day + 1;
4622
5542
  case 2:
5543
+ // range = [1, 7] sunday = 7
4623
5544
  return ((day + 6) % 7) + 1;
4624
5545
  case 3:
5546
+ // range = [0, 6] sunday = 6
4625
5547
  return (day + 6) % 7;
4626
5548
  default:
4627
5549
  return null;
@@ -4632,6 +5554,17 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4632
5554
  { types: [dataTypes.TYPE_NUMBER], optional: true },
4633
5555
  ],
4634
5556
  },
5557
+
5558
+ /**
5559
+ * Returns the year of a date represented by a serial number.
5560
+ * @param {number} The date for which the year is to be returned.
5561
+ * Dates should be entered by using the [datetime]{@link datetime} function
5562
+ * @return {number}
5563
+ * @function year
5564
+ * @category openFormula
5565
+ * @example
5566
+ * year(datetime(2008,5,23)) //returns 2008
5567
+ */
4635
5568
  year: {
4636
5569
  _func: args => {
4637
5570
  const date = toNumber(args[0]);
@@ -4642,6 +5575,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4642
5575
  { types: [dataTypes.TYPE_NUMBER] },
4643
5576
  ],
4644
5577
  },
5578
+
4645
5579
  charCode: {
4646
5580
  _func: args => {
4647
5581
  const code = toNumber(args[0]);
@@ -4654,6 +5588,7 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4654
5588
  { types: [dataTypes.TYPE_NUMBER] },
4655
5589
  ],
4656
5590
  },
5591
+
4657
5592
  codePoint: {
4658
5593
  _func: args => {
4659
5594
  const text = toString(args[0]);
@@ -4666,24 +5601,28 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4666
5601
  { types: [dataTypes.TYPE_STRING] },
4667
5602
  ],
4668
5603
  },
5604
+
4669
5605
  encodeUrlComponent: {
4670
5606
  _func: args => encodeURIComponent(args[0]),
4671
5607
  _signature: [
4672
5608
  { types: [dataTypes.TYPE_STRING] },
4673
5609
  ],
4674
5610
  },
5611
+
4675
5612
  encodeUrl: {
4676
5613
  _func: args => encodeURI(args[0]),
4677
5614
  _signature: [
4678
5615
  { types: [dataTypes.TYPE_STRING] },
4679
5616
  ],
4680
5617
  },
5618
+
4681
5619
  decodeUrlComponent: {
4682
5620
  _func: args => decodeURIComponent(args[0]),
4683
5621
  _signature: [
4684
5622
  { types: [dataTypes.TYPE_STRING] },
4685
5623
  ],
4686
5624
  },
5625
+
4687
5626
  decodeUrl: {
4688
5627
  _func: args => decodeURI(args[0]),
4689
5628
  _signature: [
@@ -4693,6 +5632,27 @@ function openFormulaFunctions(valueOf, toString, toNumber, debug = []) {
4693
5632
  };
4694
5633
  }
4695
5634
 
5635
+ /*
5636
+ Copyright 2014 James Saryerwinnie
5637
+
5638
+ Licensed under the Apache License, Version 2.0 (the "License");
5639
+ you may not use this file except in compliance with the License.
5640
+ You may obtain a copy of the License at
5641
+
5642
+ http://www.apache.org/licenses/LICENSE-2.0
5643
+
5644
+ Unless required by applicable law or agreed to in writing, software
5645
+ distributed under the License is distributed on an "AS IS" BASIS,
5646
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5647
+ See the License for the specific language governing permissions and
5648
+ limitations under the License.
5649
+
5650
+ NOTICE:
5651
+ This file is substantially modified from the original source taken from:
5652
+ https://github.com/jmespath/jmespath.js
5653
+
5654
+ */
5655
+
4696
5656
  function functions(
4697
5657
  runtime,
4698
5658
  isObject,
@@ -4715,6 +5675,7 @@ function functions(
4715
5675
  TYPE_ARRAY_NUMBER,
4716
5676
  TYPE_ARRAY_STRING,
4717
5677
  } = dataTypes;
5678
+
4718
5679
  function createKeyFunction(exprefNode, allowedTypes) {
4719
5680
  return x => {
4720
5681
  const current = runtime.interpreter.visit(exprefNode, x);
@@ -4726,11 +5687,46 @@ function functions(
4726
5687
  return current;
4727
5688
  };
4728
5689
  }
5690
+
4729
5691
  const functionMap = {
5692
+ // name: [function, <signature>]
5693
+ // The <signature> can be:
5694
+ //
5695
+ // {
5696
+ // args: [[type1, type2], [type1, type2]],
5697
+ // variadic: true|false
5698
+ // }
5699
+ //
5700
+ // Each arg in the arg list is a list of valid types
5701
+ // (if the function is overloaded and supports multiple
5702
+ // types. If the type is "any" then no type checking
5703
+ // occurs on the argument. Variadic is optional
5704
+ // and if not provided is assumed to be false.
5705
+ /**
5706
+ * Returns the absolute value of the provided argument `value`.
5707
+ * @param {number} value argument whose absolute value has to be returned
5708
+ * @return {number} returns the absolute value of the `value` argument
5709
+ * @function abs
5710
+ * @example
5711
+ * abs(-1) //returns 1
5712
+ * @category jmespath
5713
+ */
4730
5714
  abs: {
4731
5715
  _func: resolvedArgs => Math.abs(resolvedArgs[0]),
4732
5716
  _signature: [{ types: [TYPE_NUMBER] }],
4733
5717
  },
5718
+ /**
5719
+ * Returns the average of the elements in the provided array.
5720
+ * An empty array will produce a return value of `null`.
5721
+ * @param {number[]} elements array of elements whose average has to be computed
5722
+ * @return {number} average value
5723
+ * @function avg
5724
+ * @example
5725
+ * avg([]) //returns null
5726
+ * @example
5727
+ * avg([1, 2, 3]) //returns 2
5728
+ * @category jmespath
5729
+ */
4734
5730
  avg: {
4735
5731
  _func: resolvedArgs => {
4736
5732
  let sum = 0;
@@ -4742,15 +5738,59 @@ function functions(
4742
5738
  },
4743
5739
  _signature: [{ types: [TYPE_ARRAY_NUMBER] }],
4744
5740
  },
5741
+
5742
+ /**
5743
+ * Returns the next highest integer value of the argument `num` by rounding up if necessary.
5744
+ * @param {number} num number whose next highest integer value has to be computed
5745
+ * @return {number}
5746
+ * @function ceil
5747
+ * @example
5748
+ * ceil(10) //returns 10
5749
+ * @example
5750
+ * ceil(10.4) //return 11
5751
+ * @category jmespath
5752
+ */
4745
5753
  ceil: {
4746
5754
  _func: resolvedArgs => Math.ceil(resolvedArgs[0]),
4747
5755
  _signature: [{ types: [TYPE_NUMBER] }],
4748
5756
  },
5757
+ /**
5758
+ * Returns true if the given `subject` contains the provided `search` string.
5759
+ * If `subject` is an array, this function returns true if one of the elements
5760
+ * in the array is equal to the provided `search` value. If the provided `subject`
5761
+ * is a string, this function returns true if the string contains the provided
5762
+ * `search` argument.
5763
+ * @param {array|string} subject the subject in which the element has to be searched
5764
+ * @param {string|boolean|number|date} search element to search
5765
+ * @return {boolean}
5766
+ * @function contains
5767
+ * @example
5768
+ * contains([1, 2, 3, 4], 2) //returns true
5769
+ * @example
5770
+ * contains([1, 2, 3, 4], -1) //returns false
5771
+ * @example
5772
+ * contains('Abcd', 'd') //returns true
5773
+ * @example
5774
+ * contains('Abcd', 'x') //returns false
5775
+ * @category jmespath
5776
+ */
4749
5777
  contains: {
4750
5778
  _func: resolvedArgs => valueOf(resolvedArgs[0]).indexOf(valueOf(resolvedArgs[1])) >= 0,
4751
5779
  _signature: [{ types: [TYPE_STRING, TYPE_ARRAY] },
4752
5780
  { types: [TYPE_ANY] }],
4753
5781
  },
5782
+ /**
5783
+ * Returns true if the `subject` ends with the `suffix`, otherwise this function returns false.
5784
+ * @param {string} subject subject in which the `suffix` is being searched for
5785
+ * @param {string} suffix suffix to search in the subject
5786
+ * @return {boolean}
5787
+ * @function endsWith
5788
+ * @example
5789
+ * endsWith('Abcd', 'd') //returns true
5790
+ * @example
5791
+ * endsWith('Abcd', 'A') //returns false
5792
+ * @category jmespath
5793
+ */
4754
5794
  endsWith: {
4755
5795
  _func: resolvedArgs => {
4756
5796
  const searchStr = valueOf(resolvedArgs[0]);
@@ -4759,10 +5799,34 @@ function functions(
4759
5799
  },
4760
5800
  _signature: [{ types: [TYPE_STRING] }, { types: [TYPE_STRING] }],
4761
5801
  },
5802
+
5803
+ /**
5804
+ * Returns the next lowest integer value of the argument `num` by rounding down if necessary.
5805
+ * @param {number} num number whose next lowest integer value has to be returned
5806
+ * @return {number}
5807
+ * @function floor
5808
+ * @example
5809
+ * floor(10.4) //returns 10
5810
+ * @example
5811
+ * floor(10) //returns 10
5812
+ * @category jmespath
5813
+ */
4762
5814
  floor: {
4763
5815
  _func: resolvedArgs => Math.floor(resolvedArgs[0]),
4764
5816
  _signature: [{ types: [TYPE_NUMBER] }],
4765
5817
  },
5818
+
5819
+ /**
5820
+ * Returns all the elements from the provided `stringsarray`
5821
+ * array joined together using the `glue` argument as a separator between each.
5822
+ * @param {string} glue
5823
+ * @param {string[]} stringsarray
5824
+ * @return {string}
5825
+ * @function join
5826
+ * @example
5827
+ * join(',', ['a', 'b', 'c']) //returns 'a,b,c'
5828
+ * @category jmespath
5829
+ */
4766
5830
  join: {
4767
5831
  _func: resolvedArgs => {
4768
5832
  const joinChar = resolvedArgs[0];
@@ -4774,6 +5838,17 @@ function functions(
4774
5838
  { types: [TYPE_ARRAY_STRING] },
4775
5839
  ],
4776
5840
  },
5841
+
5842
+ /**
5843
+ * Returns an array containing the keys of the provided object `obj`. If the passed
5844
+ * object is null, the value returned is an empty array
5845
+ * @param {object} obj the object whose keys need to be extracted
5846
+ * @return {array}
5847
+ * @function keys
5848
+ * @example
5849
+ * keys({a : 3, b : 4}) //returns ['a', 'b']
5850
+ * @category jmespath
5851
+ */
4777
5852
  keys: {
4778
5853
  _func: resolvedArgs => {
4779
5854
  if (resolvedArgs[0] === null) return [];
@@ -4781,14 +5856,54 @@ function functions(
4781
5856
  },
4782
5857
  _signature: [{ types: [TYPE_ANY] }],
4783
5858
  },
5859
+
5860
+ /**
5861
+ * Returns the length of the given argument `subject` using the following types rules:
5862
+ * * string: returns the number of code points in the string
5863
+ * * array: returns the number of elements in the array
5864
+ * * object: returns the number of key-value pairs in the object
5865
+ * @param {string | array | object} subject subject whose length has to be calculated
5866
+ * @return {number}
5867
+ * @function length
5868
+ * @example
5869
+ * length([]) //returns 0
5870
+ * @example
5871
+ * length('') //returns 0
5872
+ * @example
5873
+ * length('abcd') //returns 4
5874
+ * @example
5875
+ * length([1, 2, 3, 4]) //returns 4
5876
+ * @example
5877
+ * length({}) // returns 0
5878
+ * @example
5879
+ * length({a : 3, b : 4}) //returns 2
5880
+ * @category jmespath
5881
+ */
4784
5882
  length: {
4785
5883
  _func: resolvedArgs => {
4786
5884
  const arg = valueOf(resolvedArgs[0]);
4787
5885
  if (isObject(arg)) return Object.keys(arg).length;
5886
+
4788
5887
  return isArray(arg) ? arg.length : toString(arg).length;
4789
5888
  },
4790
5889
  _signature: [{ types: [TYPE_STRING, TYPE_ARRAY, TYPE_OBJECT] }],
4791
5890
  },
5891
+
5892
+ /**
5893
+ * Apply the `expr` to every element in the `elements` array and return the array of results.
5894
+ * An elements of length N will produce a return array of length N. Unlike a projection,
5895
+ * `[*].bar`, `map()` will include the result of applying the `expr` for every element
5896
+ * in the elements array, even if the result is `null`.
5897
+ * @param {expression} expr expression to evaluate on each element
5898
+ * @param {array} elements array of elements on which the expression will be evaluated
5899
+ * @return {array}
5900
+ * @function map
5901
+ * @example
5902
+ * map(&(@ + 1), [1, 2, 3, 4]) // returns [2, 3, 4, 5]
5903
+ * @example
5904
+ * map(&length(@), ['doe', 'nick', 'chris']) // returns [3,4, 5]
5905
+ * @category jmespath
5906
+ */
4792
5907
  map: {
4793
5908
  _func: resolvedArgs => {
4794
5909
  const exprefNode = resolvedArgs[0];
@@ -4796,15 +5911,35 @@ function functions(
4796
5911
  },
4797
5912
  _signature: [{ types: [TYPE_EXPREF] }, { types: [TYPE_ARRAY] }],
4798
5913
  },
5914
+
5915
+ /**
5916
+ * Returns the highest value in the provided `collection` arguments.
5917
+ * If all collections are empty `null` is returned.
5918
+ * max() can work on numbers or strings.
5919
+ * If a mix of numbers and strings are provided, the type of the first value will be used.
5920
+ * @param {number[]|string[]} collection array in which the maximum element is to be calculated
5921
+ * @return {number}
5922
+ * @function max
5923
+ * @example
5924
+ * max([1, 2, 3], [4, 5, 6], 7) //returns 7
5925
+ * @example
5926
+ * max([]) // returns null
5927
+ * @example
5928
+ * max(['a', 'a1', 'b']) // returns 'b'
5929
+ * @category jmespath
5930
+ */
4799
5931
  max: {
4800
5932
  _func: args => {
5933
+ // flatten the args into a single array
4801
5934
  const array = args.reduce((prev, cur) => {
4802
5935
  if (Array.isArray(cur)) prev.push(...cur);
4803
5936
  else prev.push(cur);
4804
5937
  return prev;
4805
5938
  }, []);
5939
+
4806
5940
  const first = array.find(r => r !== null);
4807
5941
  if (array.length === 0 || first === undefined) return null;
5942
+ // use the first value to determine the comparison type
4808
5943
  const isNumber = getTypeName(first, true) === TYPE_NUMBER;
4809
5944
  const compare = isNumber
4810
5945
  ? (prev, cur) => {
@@ -4815,10 +5950,25 @@ function functions(
4815
5950
  const current = toString(cur);
4816
5951
  return prev.localeCompare(current) === 1 ? prev : current;
4817
5952
  };
5953
+
4818
5954
  return array.reduce(compare, isNumber ? toNumber(first) : toString(first));
4819
5955
  },
4820
5956
  _signature: [{ types: [TYPE_ARRAY, TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING], variadic: true }],
4821
5957
  },
5958
+
5959
+ /**
5960
+ * Returns the maximum element in an array using the expression `expr` as the comparison key.
5961
+ * The entire element is returned.
5962
+ * @param {array} elements the array in which the maximum element is to be found
5963
+ * @param {expression} expr the expr to use as the `comparison` key
5964
+ * @return {any}
5965
+ * @function maxBy
5966
+ * @example
5967
+ * maxBy(['abcd', 'e', 'def'], &length(@)) //returns 'abcd'
5968
+ * @example
5969
+ * maxBy([{year: 2010}, {year: 2020}, {year: 1910}], &year) //returns {year: 2020}
5970
+ * @category jmespath
5971
+ */
4822
5972
  maxBy: {
4823
5973
  _func: resolvedArgs => {
4824
5974
  const exprefNode = resolvedArgs[1];
@@ -4838,6 +5988,22 @@ function functions(
4838
5988
  },
4839
5989
  _signature: [{ types: [TYPE_ARRAY] }, { types: [TYPE_EXPREF] }],
4840
5990
  },
5991
+
5992
+ /**
5993
+ * Accepts 0 or more objects as arguments, and returns a single object with
5994
+ * subsequent objects merged. Each subsequent object’s key/value pairs are
5995
+ * added to the preceding object. This function is used to combine multiple
5996
+ * objects into one. You can think of this as the first object being the base object,
5997
+ * and each subsequent argument being overrides that are applied to the base object.
5998
+ * @param {...object} args
5999
+ * @return {object}
6000
+ * @function merge
6001
+ * @example
6002
+ * merge({a: 1, b: 2}, {c : 3, d: 4}) // returns {a :1, b: 2, c: 3, d: 4}
6003
+ * @example
6004
+ * merge({a: 1, b: 2}, {a : 3, d: 4}) // returns {a :3, b: 2, d: 4}
6005
+ * @category jmespath
6006
+ */
4841
6007
  merge: {
4842
6008
  _func: resolvedArgs => {
4843
6009
  const merged = {};
@@ -4850,15 +6016,35 @@ function functions(
4850
6016
  },
4851
6017
  _signature: [{ types: [TYPE_OBJECT], variadic: true }],
4852
6018
  },
6019
+
6020
+ /**
6021
+ * Returns the lowest value in the provided `collection` arguments.
6022
+ * If all collections are empty `null` is returned.
6023
+ * min() can work on numbers or strings.
6024
+ * If a mix of numbers and strings are provided, the type of the first value will be used.
6025
+ * @param {number[]|string[]} collection array in which the minimum element is to be calculated
6026
+ * @return {number}
6027
+ * @function min
6028
+ * @example
6029
+ * min([1, 2, 3], [4, 5, 6], 7) //returns 1
6030
+ * @example
6031
+ * min([]) // returns null
6032
+ * @example
6033
+ * min(['a', 'a1', 'b']) // returns 'a'
6034
+ * @category jmespath
6035
+ */
4853
6036
  min: {
4854
6037
  _func: args => {
6038
+ // flatten the args into a single array
4855
6039
  const array = args.reduce((prev, cur) => {
4856
6040
  if (Array.isArray(cur)) prev.push(...cur);
4857
6041
  else prev.push(cur);
4858
6042
  return prev;
4859
6043
  }, []);
6044
+
4860
6045
  const first = array.find(r => r !== null);
4861
6046
  if (array.length === 0 || first === undefined) return null;
6047
+ // use the first value to determine the comparison type
4862
6048
  const isNumber = getTypeName(first, true) === TYPE_NUMBER;
4863
6049
  const compare = isNumber
4864
6050
  ? (prev, cur) => {
@@ -4869,10 +6055,25 @@ function functions(
4869
6055
  const current = toString(cur);
4870
6056
  return prev.localeCompare(current) === 1 ? current : prev;
4871
6057
  };
6058
+
4872
6059
  return array.reduce(compare, isNumber ? toNumber(first) : toString(first));
4873
6060
  },
4874
6061
  _signature: [{ types: [TYPE_ARRAY, TYPE_ARRAY_NUMBER, TYPE_ARRAY_STRING], variadic: true }],
4875
6062
  },
6063
+
6064
+ /**
6065
+ * Returns the minimum element in `elements` array using the expression `expr`
6066
+ * as the comparison key.
6067
+ * @param {array} elements
6068
+ * @param {expression} expr expression that returns either a string or a number
6069
+ * @return {any}
6070
+ * @function minBy
6071
+ * @example
6072
+ * minBy(['abcd', 'e', 'def'], &length(@)) //returns 'e'
6073
+ * @example
6074
+ * minBy([{year: 2010}, {year: 2020}, {year: 1910}], &year) //returns {year: 1910}
6075
+ * @category jmespath
6076
+ */
4876
6077
  minBy: {
4877
6078
  _func: resolvedArgs => {
4878
6079
  const exprefNode = resolvedArgs[1];
@@ -4892,10 +6093,48 @@ function functions(
4892
6093
  },
4893
6094
  _signature: [{ types: [TYPE_ARRAY] }, { types: [TYPE_EXPREF] }],
4894
6095
  },
6096
+
6097
+ /**
6098
+ * Returns the first argument that does not resolve to `null`.
6099
+ * This function accepts one or more arguments, and will evaluate
6100
+ * them in order until a non null argument is encounted. If all
6101
+ * arguments values resolve to null, then a value of null is returned.
6102
+ * @param {...any} argument
6103
+ * @return {any}
6104
+ * @function notNull
6105
+ * @example
6106
+ * notNull(1, 2, 3, 4, `null`) //returns 1
6107
+ * @example
6108
+ * notNull(`null`, 2, 3, 4, `null`) //returns 2
6109
+ * @category jmespath
6110
+ */
4895
6111
  notNull: {
4896
6112
  _func: resolvedArgs => resolvedArgs.find(arg => getTypeName(arg) !== TYPE_NULL) || null,
4897
6113
  _signature: [{ types: [TYPE_ANY], variadic: true }],
4898
6114
  },
6115
+
6116
+ /**
6117
+ * executes a user-supplied reducer expression `expr` on each element of the
6118
+ * array, in order, passing in the return value from the calculation on the preceding element.
6119
+ * The final result of running the reducer across all elements of the `elements` array is a
6120
+ * single value.
6121
+ * The expression can access the following properties
6122
+ * * accumulated: accumulated value based on the previous calculations. Initial value is `null`
6123
+ * * current: current element to process
6124
+ * * index: index of the `current` element in the array
6125
+ * * array: original array
6126
+ * @param {expression} expr reducer expr to be executed on each element
6127
+ * @param {array} elements array of elements on which the expression will be evaluated
6128
+ * @return {any}
6129
+ * @function reduce
6130
+ * @example
6131
+ * reduce(&(accumulated + current), [1, 2, 3]) //returns 6
6132
+ * @example
6133
+ * reduce(&(accumulated - current), [3, 3, 3]) //returns -9
6134
+ * @example
6135
+ * reduce(&if(accumulated == `null`, current, accumulated * current), [3, 3, 3]) //returns 27
6136
+ * @category jmespath
6137
+ */
4899
6138
  reduce: {
4900
6139
  _func: resolvedArgs => {
4901
6140
  const exprefNode = resolvedArgs[0];
@@ -4912,10 +6151,23 @@ function functions(
4912
6151
  { types: [TYPE_ANY], optional: true },
4913
6152
  ],
4914
6153
  },
6154
+
6155
+ /**
6156
+ * Register a function to allow code re-use. The registered function may take one parameter.
6157
+ * If more parameters are needed, combine them in an array or map.
6158
+ * @param {string} functionName Name of the function to register
6159
+ * @param {expression} expr Expression to execute with this function call
6160
+ * @return {{}} returns an empty object
6161
+ * @function register
6162
+ * @example
6163
+ * register('product', &@[0] * @[1]) // can now call: product([2,21]) => returns 42
6164
+ * @category jmespath
6165
+ */
4915
6166
  register: {
4916
6167
  _func: resolvedArgs => {
4917
6168
  const functionName = resolvedArgs[0];
4918
6169
  const exprefNode = resolvedArgs[1];
6170
+
4919
6171
  if (functionMap[functionName]) {
4920
6172
  debug.push(`Cannot re-register '${functionName}'`);
4921
6173
  return {};
@@ -4931,6 +6183,15 @@ function functions(
4931
6183
  { types: [TYPE_EXPREF] },
4932
6184
  ],
4933
6185
  },
6186
+ /**
6187
+ * Reverses the order of the `argument`.
6188
+ * @param {string|array} argument
6189
+ * @return {array}
6190
+ * @function reverse
6191
+ * @example
6192
+ * reverse(['a', 'b', 'c']) //returns ['c', 'b', 'a']
6193
+ * @category jmespath
6194
+ */
4934
6195
  reverse: {
4935
6196
  _func: resolvedArgs => {
4936
6197
  const originalStr = valueOf(resolvedArgs[0]);
@@ -4948,6 +6209,18 @@ function functions(
4948
6209
  },
4949
6210
  _signature: [{ types: [TYPE_STRING, TYPE_ARRAY] }],
4950
6211
  },
6212
+
6213
+ /**
6214
+ * This function accepts an array `list` argument and returns the sorted elements of
6215
+ * the `list` as an array. The array must be a list of strings or numbers.
6216
+ * Sorting strings is based on code points. Locale is not taken into account.
6217
+ * @param {number[]|string[]} list
6218
+ * @return {number[]|string[]}
6219
+ * @function sort
6220
+ * @example
6221
+ * sort([1, 2, 4, 3, 1]) // returns [1, 1, 2, 3, 4]
6222
+ * @category jmespath
6223
+ */
4951
6224
  sort: {
4952
6225
  _func: resolvedArgs => {
4953
6226
  const sortedArray = resolvedArgs[0].slice(0);
@@ -4965,6 +6238,24 @@ function functions(
4965
6238
  },
4966
6239
  _signature: [{ types: [TYPE_ARRAY, TYPE_ARRAY_STRING, TYPE_ARRAY_NUMBER] }],
4967
6240
  },
6241
+
6242
+ /**
6243
+ * Sort an array using an expression `expr` as the sort key. For each element
6244
+ * in the array of elements, the `expr` expression is applied and the resulting
6245
+ * value is used as the key used when sorting the elements. If the result of
6246
+ * evaluating the `expr` against the current array element results in type
6247
+ * other than a number or a string, a type error will occur.
6248
+ * @param {array} elements
6249
+ * @param {expression} expr
6250
+ * @return {array}
6251
+ * @function sortBy
6252
+ * @example
6253
+ * sortBy(['abcd', 'e', 'def'], &length(@)) //returns ['e', 'def', 'abcd']
6254
+ * @example
6255
+ * // returns [{year: 1910}, {year: 2010}, {year: 2020}]
6256
+ * sortBy([{year: 2010}, {year: 2020}, {year: 1910}], &year)
6257
+ * @category jmespath
6258
+ */
4968
6259
  sortBy: {
4969
6260
  _func: resolvedArgs => {
4970
6261
  const sortedArray = resolvedArgs[0].slice(0);
@@ -4978,6 +6269,13 @@ function functions(
4978
6269
  if ([TYPE_NUMBER, TYPE_STRING].indexOf(requiredType) < 0) {
4979
6270
  throw new Error('TypeError');
4980
6271
  }
6272
+ // In order to get a stable sort out of an unstable
6273
+ // sort algorithm, we decorate/sort/undecorate (DSU)
6274
+ // by creating a new list of [index, element] pairs.
6275
+ // In the cmp function, if the evaluated elements are
6276
+ // equal, then the index will be used as the tiebreaker.
6277
+ // After the decorated list has been sorted, it will be
6278
+ // undecorated to extract the original elements.
4981
6279
  const decorated = [];
4982
6280
  for (let i = 0; i < sortedArray.length; i += 1) {
4983
6281
  decorated.push([i, sortedArray[i]]);
@@ -5002,8 +6300,12 @@ function functions(
5002
6300
  if (exprA < exprB) {
5003
6301
  return -1;
5004
6302
  }
6303
+ // If they're equal compare the items by their
6304
+ // order to maintain relative order of equal keys
6305
+ // (i.e. to get a stable sort).
5005
6306
  return a[0] - b[0];
5006
6307
  });
6308
+ // Undecorate: extract out the original list elements.
5007
6309
  for (let j = 0; j < decorated.length; j += 1) {
5008
6310
  [, sortedArray[j]] = decorated[j];
5009
6311
  }
@@ -5011,10 +6313,32 @@ function functions(
5011
6313
  },
5012
6314
  _signature: [{ types: [TYPE_ARRAY] }, { types: [TYPE_EXPREF] }],
5013
6315
  },
6316
+
6317
+ /**
6318
+ * Returns true if the `subject` starts with the `prefix`, otherwise returns false.
6319
+ * @param {string} subject subject in which the `prefix` is being searched for
6320
+ * @param {string} prefix prefix to search in the subject
6321
+ * @return {boolean}
6322
+ * @function startsWith
6323
+ * @example
6324
+ * startsWith('jack is at home', 'jack') // returns true
6325
+ * @category jmespath
6326
+ */
5014
6327
  startsWith: {
5015
6328
  _func: resolvedArgs => valueOf(resolvedArgs[0]).startsWith(valueOf(resolvedArgs[1])),
5016
6329
  _signature: [{ types: [TYPE_STRING] }, { types: [TYPE_STRING] }],
5017
6330
  },
6331
+
6332
+ /**
6333
+ * Returns the sum of the provided `collection` array argument.
6334
+ * An empty array will produce a return value of 0.
6335
+ * @param {number[]} collection array whose element's sum has to be computed
6336
+ * @return {number}
6337
+ * @function sum
6338
+ * @example
6339
+ * sum([1, 2, 3]) //returns 6
6340
+ * @category jmespath
6341
+ */
5018
6342
  sum: {
5019
6343
  _func: resolvedArgs => {
5020
6344
  let sum = 0;
@@ -5025,6 +6349,20 @@ function functions(
5025
6349
  },
5026
6350
  _signature: [{ types: [TYPE_ARRAY_NUMBER] }],
5027
6351
  },
6352
+
6353
+ /**
6354
+ * converts the passed `arg` to an array. The conversion happens as per the following rules
6355
+ * * array - Returns the passed in value.
6356
+ * * number/string/object/boolean - Returns a one element array containing the argument.
6357
+ * @param {any} arg
6358
+ * @return {array}
6359
+ * @function toArray
6360
+ * @example
6361
+ * toArray(1) // returns [1]
6362
+ * @example
6363
+ * toArray(null()) // returns [`null`]
6364
+ * @category jmespath
6365
+ */
5028
6366
  toArray: {
5029
6367
  _func: resolvedArgs => {
5030
6368
  if (getTypeName(resolvedArgs[0]) === TYPE_ARRAY) {
@@ -5032,8 +6370,31 @@ function functions(
5032
6370
  }
5033
6371
  return [resolvedArgs[0]];
5034
6372
  },
6373
+
5035
6374
  _signature: [{ types: [TYPE_ANY] }],
5036
6375
  },
6376
+
6377
+ /**
6378
+ * converts the passed arg to a number. The conversion happens as per the following rules
6379
+ * * string - Returns the parsed number.
6380
+ * * number - Returns the passed in value.
6381
+ * * array - null
6382
+ * * object - null
6383
+ * * boolean - null
6384
+ * * null - null
6385
+ * @param {any} arg
6386
+ * @return {number}
6387
+ * @function toNumber
6388
+ * @example
6389
+ * toNumber(1) //returns 1
6390
+ * @example
6391
+ * toNumber('10') //returns 10
6392
+ * @example
6393
+ * toNumber({a: 1}) //returns null
6394
+ * @example
6395
+ * toNumber(true()) //returns null
6396
+ * @category jmespath
6397
+ */
5037
6398
  toNumber: {
5038
6399
  _func: resolvedArgs => {
5039
6400
  const typeName = getTypeName(resolvedArgs[0]);
@@ -5047,6 +6408,20 @@ function functions(
5047
6408
  },
5048
6409
  _signature: [{ types: [TYPE_ANY] }],
5049
6410
  },
6411
+
6412
+ /**
6413
+ * converts the passed `arg` to a string. The conversion happens as per the following rules
6414
+ * * string - Returns the passed in value.
6415
+ * * number/array/object/boolean - The JSON encoded value of the object.
6416
+ * @param {any} arg
6417
+ * @return {string}
6418
+ * @function toString
6419
+ * @example
6420
+ * toString(1) //returns '1'
6421
+ * @example
6422
+ * toString(true()) //returns 'true'
6423
+ * @category jmespath
6424
+ */
5050
6425
  toString: {
5051
6426
  _func: resolvedArgs => {
5052
6427
  if (getTypeName(resolvedArgs[0]) === TYPE_STRING) {
@@ -5054,8 +6429,30 @@ function functions(
5054
6429
  }
5055
6430
  return JSON.stringify(resolvedArgs[0]);
5056
6431
  },
6432
+
5057
6433
  _signature: [{ types: [TYPE_ANY] }],
5058
6434
  },
6435
+
6436
+ /**
6437
+ * Returns the JavaScript type of the given `subject` argument as a string value.
6438
+ *
6439
+ * The return value MUST be one of the following:
6440
+ * * number
6441
+ * * string
6442
+ * * boolean
6443
+ * * array
6444
+ * * object
6445
+ * * null
6446
+ * @param {any} subject
6447
+ * @return {string}
6448
+ *
6449
+ * @function type
6450
+ * @example
6451
+ * type(1) //returns 'number'
6452
+ * @example
6453
+ * type('') //returns 'string'
6454
+ * @category jmespath
6455
+ */
5059
6456
  type: {
5060
6457
  _func: resolvedArgs => ({
5061
6458
  [TYPE_NUMBER]: 'number',
@@ -5068,6 +6465,18 @@ function functions(
5068
6465
  }[getTypeName(resolvedArgs[0])]),
5069
6466
  _signature: [{ types: [TYPE_ANY] }],
5070
6467
  },
6468
+
6469
+ /**
6470
+ * Returns the values of the provided object `obj`. Note that because JSON hashes are
6471
+ * inherently unordered, the values associated with the provided object obj are
6472
+ * inherently unordered.
6473
+ * @param {object} obj
6474
+ * @return {array}
6475
+ * @function values
6476
+ * @example
6477
+ * values({a : 3, b : 4}) //returns [3, 4]
6478
+ * @category jmespath
6479
+ */
5071
6480
  values: {
5072
6481
  _func: resolvedArgs => {
5073
6482
  const arg = valueOf(resolvedArgs[0]);
@@ -5076,6 +6485,19 @@ function functions(
5076
6485
  },
5077
6486
  _signature: [{ types: [TYPE_ANY] }],
5078
6487
  },
6488
+
6489
+ /**
6490
+ * Returns a convolved (zipped) array containing grouped arrays of values from
6491
+ * the array arguments from index 0, 1, 2, etc.
6492
+ * This function accepts a variable number of arguments.
6493
+ * The length of the returned array is equal to the length of the shortest array.
6494
+ * @param {...array} arrays array of arrays to zip together
6495
+ * @return {array} An array of arrays with elements zipped together
6496
+ * @function zip
6497
+ * @example
6498
+ * zip([1, 2, 3], [4, 5, 6]) //returns [[1, 4], [2, 5], [3, 6]]
6499
+ * @category jmespath
6500
+ */
5079
6501
  zip: {
5080
6502
  _func: args => {
5081
6503
  const count = args.reduce((min, current) => Math.min(min, current.length), args[0].length);
@@ -5094,13 +6516,17 @@ function functions(
5094
6516
  return functionMap;
5095
6517
  }
5096
6518
 
6519
+ /* eslint-disable max-classes-per-file */
6520
+
6521
+ // Type constants used to define functions.
5097
6522
  const {
5098
6523
  TYPE_CLASS,
5099
6524
  TYPE_ANY,
5100
6525
  } = dataTypes;
6526
+
5101
6527
  function getToNumber(stringToNumber, debug = []) {
5102
6528
  return value => {
5103
- const n = getValueOf(value);
6529
+ const n = getValueOf(value); // in case it's an object that implements valueOf()
5104
6530
  if (n === null) return null;
5105
6531
  if (n instanceof Array) {
5106
6532
  debug.push('Converted array to zero');
@@ -5116,20 +6542,26 @@ function getToNumber(stringToNumber, debug = []) {
5116
6542
  }
5117
6543
  function toString(a) {
5118
6544
  if (a === null || a === undefined) return '';
6545
+ // don't call a 'toString' method, since we could have a child named 'toString()'
5119
6546
  return a.toString();
5120
6547
  }
6548
+
5121
6549
  const defaultStringToNumber = (str => {
5122
6550
  const n = +str;
5123
6551
  return Number.isNaN(n) ? 0 : n;
5124
6552
  });
6553
+
5125
6554
  function isClass(obj) {
5126
6555
  if (obj === null) return false;
5127
6556
  if (Array.isArray(obj)) return false;
5128
6557
  return obj.constructor.name !== 'Object';
5129
6558
  }
6559
+
5130
6560
  function matchClass(arg, expectedList) {
6561
+ // checking isClass() generates a dependency -- so call it only if necessary
5131
6562
  return expectedList.includes(TYPE_CLASS) && isClass(arg);
5132
6563
  }
6564
+
5133
6565
  class Runtime {
5134
6566
  constructor(debug, toNumber, customFunctions = {}) {
5135
6567
  this.strictDeepEqual = strictDeepEqual;
@@ -5144,16 +6576,25 @@ class Runtime {
5144
6576
  toString,
5145
6577
  debug,
5146
6578
  );
6579
+
5147
6580
  Object.entries(
5148
6581
  openFormulaFunctions(getValueOf, toString, toNumber, debug),
5149
6582
  ).forEach(([fname, func]) => {
5150
6583
  this.functionTable[fname] = func;
5151
6584
  });
6585
+
5152
6586
  Object.entries(customFunctions).forEach(([fname, func]) => {
5153
6587
  this.functionTable[fname] = func;
5154
6588
  });
5155
6589
  }
6590
+
6591
+ // eslint-disable-next-line class-methods-use-this
5156
6592
  _validateArgs(argName, args, signature, bResolved) {
6593
+ // Validating the args requires validating
6594
+ // the correct arity and the correct type of each arg.
6595
+ // If the last argument is declared as variadic, then we need
6596
+ // a minimum number of args to be required. Otherwise it has to
6597
+ // be an exact amount.
5157
6598
  if (signature.length === 0) {
5158
6599
  return;
5159
6600
  }
@@ -5172,31 +6613,39 @@ class Runtime {
5172
6613
  + `takes ${signature.length}${pluralized
5173
6614
  } but received ${args.length}`);
5174
6615
  }
6616
+ // if the arguments are unresolved, there's no point in validating types
5175
6617
  if (!bResolved) return;
5176
6618
  let currentSpec;
5177
6619
  let actualType;
5178
6620
  const limit = Math.min(signature.length, args.length);
5179
6621
  for (let i = 0; i < limit; i += 1) {
5180
6622
  currentSpec = signature[i].types;
6623
+ // Try to avoid checks that will introspect the object and generate dependencies
5181
6624
  if (!matchClass(args[i], currentSpec) && !currentSpec.includes(TYPE_ANY)) {
5182
6625
  actualType = getTypeNames(args[i]);
6626
+ // eslint-disable-next-line no-param-reassign
5183
6627
  args[i] = matchType(actualType, currentSpec, args[i], argName, this.toNumber, toString);
5184
6628
  }
5185
6629
  }
5186
6630
  }
6631
+
5187
6632
  callFunction(name, resolvedArgs, data, interpreter, bResolved = true) {
6633
+ // this check will weed out 'valueOf', 'toString' etc
5188
6634
  if (!Object.prototype.hasOwnProperty.call(this.functionTable, name)) throw new Error(`Unknown function: ${name}()`);
6635
+
5189
6636
  const functionEntry = this.functionTable[name];
5190
6637
  this._validateArgs(name, resolvedArgs, functionEntry._signature, bResolved);
5191
6638
  return functionEntry._func.call(this, resolvedArgs, data, interpreter);
5192
6639
  }
5193
6640
  }
6641
+
5194
6642
  class Formula {
5195
6643
  constructor(debug, customFunctions, stringToNumberFn) {
5196
6644
  this.debug = debug;
5197
6645
  this.toNumber = getToNumber(stringToNumberFn || defaultStringToNumber, debug);
5198
6646
  this.runtime = new Runtime(debug, this.toNumber, customFunctions);
5199
6647
  }
6648
+
5200
6649
  compile(stream, allowedGlobalNames = []) {
5201
6650
  let ast;
5202
6651
  try {
@@ -5208,7 +6657,11 @@ class Formula {
5208
6657
  }
5209
6658
  return ast;
5210
6659
  }
6660
+
5211
6661
  search(node, data, globals = {}, language = 'en-US') {
6662
+ // This needs to be improved. Both the interpreter and runtime depend on
6663
+ // each other. The runtime needs the interpreter to support exprefs.
6664
+ // There's likely a clean way to avoid the cyclic dependency.
5212
6665
  this.runtime.interpreter = new TreeInterpreter(
5213
6666
  this.runtime,
5214
6667
  globals,
@@ -5217,6 +6670,7 @@ class Formula {
5217
6670
  this.debug,
5218
6671
  language,
5219
6672
  );
6673
+
5220
6674
  try {
5221
6675
  return this.runtime.interpreter.search(node, data);
5222
6676
  } catch (e) {
@@ -5226,7 +6680,33 @@ class Formula {
5226
6680
  }
5227
6681
  }
5228
6682
 
6683
+ /*
6684
+ Copyright 2021 Adobe. All rights reserved.
6685
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
6686
+ you may not use this file except in compliance with the License. You may obtain a copy
6687
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6688
+
6689
+ Unless required by applicable law or agreed to in writing, software distributed under
6690
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
6691
+ OF ANY KIND, either express or implied. See the License for the specific language
6692
+ governing permissions and limitations under the License.
6693
+ */
6694
+
6695
+ /**
6696
+ * Returns an instance of JSON JsonFormula Expression that can be executed later on with
6697
+ * multiple instances of JSON Data. The instance of the class has a single search
6698
+ * function that can be used to evaluate the expression on a json payload. The advantage
6699
+ * of using this over {jsonJsonFormula} function is that it can be performant if a single expression
6700
+ * has to be used for multiple json data instances.
6701
+ */
5229
6702
  class JsonFormula {
6703
+ /**
6704
+ * @param customFunctions {*} custom functions needed by a hosting application.
6705
+ * @param stringToNumber {function} A function that converts string values to numbers.
6706
+ * Can be used to convert currencies/dates to numbers
6707
+ * @param language
6708
+ * @param debug {array} will be populated with any errors/warnings
6709
+ */
5230
6710
  constructor(
5231
6711
  customFunctions = {},
5232
6712
  stringToNumber = null,
@@ -5237,10 +6717,25 @@ class JsonFormula {
5237
6717
  this.debug = debug;
5238
6718
  this.formula = new Formula(debug, customFunctions, stringToNumber);
5239
6719
  }
6720
+
6721
+ /**
6722
+ * Evaluates the JsonFormula on a particular json payload and return the result
6723
+ * @param json {object} the json data on which the expression needs to be evaluated
6724
+ * @param globals {*} global objects that can be accessed via custom functions.
6725
+ * @returns {*} the result of the expression being evaluated
6726
+ */
5240
6727
  search(expression, json, globals = {}, language = 'en-US') {
5241
6728
  const ast = this.compile(expression, Object.keys(globals));
5242
6729
  return this.run(ast, json, language, globals);
5243
6730
  }
6731
+
6732
+ /**
6733
+ * Execute a previously compiled expression against a json object and return the result
6734
+ * @param ast {object} The abstract syntax tree returned from compile()
6735
+ * @param json {object} the json data on which the expression needs to be evaluated
6736
+ * @param globals {*} set of objects available in global scope
6737
+ * @returns {*} the result of the expression being evaluated
6738
+ */
5244
6739
  run(ast, json, language, globals) {
5245
6740
  return this.formula.search(
5246
6741
  ast,
@@ -5249,6 +6744,14 @@ class JsonFormula {
5249
6744
  language,
5250
6745
  );
5251
6746
  }
6747
+
6748
+ /*
6749
+ * Creates a compiled expression that can be executed later on with some data.
6750
+ * @param expression {string} the expression to evaluate
6751
+ * @param allowedGlobalNames {string[]} A list of names of the global variables
6752
+ * being used in the expression.
6753
+ * @param debug {array} will be populated with any errors/warnings
6754
+ */
5252
6755
  compile(expression, allowedGlobalNames = []) {
5253
6756
  this.debug.length = 0;
5254
6757
  return this.formula.compile(expression, allowedGlobalNames);
@@ -5301,8 +6804,7 @@ class RuleEngine {
5301
6804
  }
5302
6805
 
5303
6806
  const defaults = {
5304
- visible: true,
5305
- enabled: true
6807
+ visible: true
5306
6808
  };
5307
6809
  class Fieldset extends Container {
5308
6810
  constructor(params, _options) {
@@ -5345,6 +6847,12 @@ class Fieldset extends Container {
5345
6847
  }
5346
6848
  }
5347
6849
 
6850
+ var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
6851
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
6852
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
6853
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6854
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6855
+ };
5348
6856
  class InstanceManager extends Fieldset {
5349
6857
  get maxOccur() {
5350
6858
  return this._jsonModel.maxItems;
@@ -5362,22 +6870,13 @@ class InstanceManager extends Fieldset {
5362
6870
  return this.removeItem(action);
5363
6871
  }
5364
6872
  }
5365
- __decorate([
6873
+ __decorate$1([
5366
6874
  dependencyTracked()
5367
6875
  ], InstanceManager.prototype, "maxOccur", null);
5368
- __decorate([
6876
+ __decorate$1([
5369
6877
  dependencyTracked()
5370
6878
  ], InstanceManager.prototype, "minOccur", null);
5371
6879
 
5372
- class ValidationError {
5373
- fieldName;
5374
- errorMessages;
5375
- constructor(fieldName = '', errorMessages = []) {
5376
- this.errorMessages = errorMessages;
5377
- this.fieldName = fieldName;
5378
- }
5379
- }
5380
-
5381
6880
  const dateRegex = /^(\d{4})-(\d{1,2})-(\d{1,2})$/;
5382
6881
  const days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
5383
6882
  const daysInMonth = (leapYear, month) => {
@@ -5651,9 +7150,33 @@ const Constraints = {
5651
7150
  }
5652
7151
  };
5653
7152
 
7153
+ /*************************************************************************
7154
+ * ADOBE CONFIDENTIAL
7155
+ * ___________________
7156
+ *
7157
+ * Copyright 2022 Adobe
7158
+ * All Rights Reserved.
7159
+ *
7160
+ * NOTICE: All information contained herein is, and remains
7161
+ * the property of Adobe and its suppliers, if any. The intellectual
7162
+ * and technical concepts contained herein are proprietary to Adobe
7163
+ * and its suppliers and are protected by all applicable intellectual
7164
+ * property laws, including trade secret and copyright laws.
7165
+ * Dissemination of this information or reproduction of this material
7166
+ * is strictly forbidden unless prior written permission is obtained
7167
+ * from Adobe.
7168
+ **************************************************************************/
7169
+ /**
7170
+ * https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
7171
+ * Credit: https://git.corp.adobe.com/dc/dfl/blob/master/src/patterns/parseDateTimeSkeleton.js
7172
+ * Created a separate library to be used elsewhere as well.
7173
+ */
5654
7174
  const DATE_TIME_REGEX =
7175
+ // eslint-disable-next-line max-len
5655
7176
  /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvV]{1,5}|[zZOvVxX]{1,3}|S{1,3}|'(?:[^']|'')*')|[^a-zA-Z']+/g;
7177
+
5656
7178
  const ShorthandStyles$1 = ["full", "long", "medium", "short"];
7179
+
5657
7180
  function getSkeleton(skeleton, language) {
5658
7181
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
5659
7182
  const parsed = parseDateStyle(skeleton, language);
@@ -5674,13 +7197,24 @@ function getSkeleton(skeleton, language) {
5674
7197
  }
5675
7198
  return skeleton;
5676
7199
  }
7200
+
7201
+ /**
7202
+ *
7203
+ * @param skeleton shorthand style for the date concatenated with shorthand style of time. The
7204
+ * Shorthand style for both date and time is one of ['full', 'long', 'medium', 'short'].
7205
+ * @param language {string} language to parse the date shorthand style
7206
+ * @returns {[*,string][]}
7207
+ */
5677
7208
  function parseDateStyle(skeleton, language) {
5678
7209
  const options = {};
7210
+ // the skeleton could have two keywords -- one for date, one for time
5679
7211
  const styles = skeleton.split(/\s/).filter(s => s.length);
5680
7212
  options.dateStyle = styles[0];
5681
7213
  if (styles.length > 1) options.timeStyle = styles[1];
7214
+
5682
7215
  const testDate = new Date(2000, 2, 1, 2, 3, 4);
5683
7216
  const parts = new Intl.DateTimeFormat(language, options).formatToParts(testDate);
7217
+ // oddly, the formatted month name can be different from the standalone month name
5684
7218
  const formattedMarch = parts.find(p => p.type === 'month').value;
5685
7219
  const longMarch = new Intl.DateTimeFormat(language, {month: 'long'}).formatToParts(testDate)[0].value;
5686
7220
  const shortMarch = new Intl.DateTimeFormat(language, {month: 'short'}).formatToParts(testDate)[0].value;
@@ -5704,6 +7238,11 @@ function parseDateStyle(skeleton, language) {
5704
7238
  });
5705
7239
  return result;
5706
7240
  }
7241
+
7242
+ /**
7243
+ * Parse Date time skeleton into Intl.DateTimeFormatOptions parts
7244
+ * Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
7245
+ */
5707
7246
  function parseDateTimeSkeleton(skeleton, language) {
5708
7247
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
5709
7248
  return parseDateStyle(skeleton, language);
@@ -5712,9 +7251,11 @@ function parseDateTimeSkeleton(skeleton, language) {
5712
7251
  skeleton.replace(DATE_TIME_REGEX, match => {
5713
7252
  const len = match.length;
5714
7253
  switch (match[0]) {
7254
+ // Era
5715
7255
  case 'G':
5716
7256
  result.push(['era', len === 4 ? 'long' : len === 5 ? 'narrow' : 'short', len]);
5717
7257
  break;
7258
+ // Year
5718
7259
  case 'y':
5719
7260
  result.push(['year', len === 2 ? '2-digit' : 'numeric', len]);
5720
7261
  break;
@@ -5725,13 +7266,16 @@ function parseDateTimeSkeleton(skeleton, language) {
5725
7266
  throw new RangeError(
5726
7267
  '`Y/u/U/r` (year) patterns are not supported, use `y` instead'
5727
7268
  );
7269
+ // Quarter
5728
7270
  case 'q':
5729
7271
  case 'Q':
5730
7272
  throw new RangeError('`q/Q` (quarter) patterns are not supported');
7273
+ // Month
5731
7274
  case 'M':
5732
7275
  case 'L':
5733
7276
  result.push(['month', ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1], len]);
5734
7277
  break;
7278
+ // Week
5735
7279
  case 'w':
5736
7280
  case 'W':
5737
7281
  throw new RangeError('`w/W` (week) patterns are not supported');
@@ -5744,6 +7288,7 @@ function parseDateTimeSkeleton(skeleton, language) {
5744
7288
  throw new RangeError(
5745
7289
  '`D/F/g` (day) patterns are not supported, use `d` instead'
5746
7290
  );
7291
+ // Weekday
5747
7292
  case 'E':
5748
7293
  result.push(['weekday', ['short', 'short', 'short', 'long', 'narrow', 'narrow'][len - 1], len]);
5749
7294
  break;
@@ -5759,14 +7304,16 @@ function parseDateTimeSkeleton(skeleton, language) {
5759
7304
  }
5760
7305
  result.push(['weekday', ['short', 'long', 'narrow', 'short'][len - 3], len]);
5761
7306
  break;
5762
- case 'a':
7307
+ // Period
7308
+ case 'a': // AM, PM
5763
7309
  result.push(['hour12', true, 1]);
5764
7310
  break;
5765
- case 'b':
5766
- case 'B':
7311
+ case 'b': // am, pm, noon, midnight
7312
+ case 'B': // flexible day periods
5767
7313
  throw new RangeError(
5768
7314
  '`b/B` (period) patterns are not supported, use `a` instead'
5769
7315
  );
7316
+ // Hour
5770
7317
  case 'h':
5771
7318
  result.push(['hourCycle', 'h12']);
5772
7319
  result.push(['hour', ['numeric', '2-digit'][len - 1], len]);
@@ -5789,9 +7336,11 @@ function parseDateTimeSkeleton(skeleton, language) {
5789
7336
  throw new RangeError(
5790
7337
  '`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead'
5791
7338
  );
7339
+ // Minute
5792
7340
  case 'm':
5793
7341
  result.push(['minute', ['numeric', '2-digit'][len - 1], len]);
5794
7342
  break;
7343
+ // Second
5795
7344
  case 's':
5796
7345
  result.push(['second', ['numeric', '2-digit'][len - 1], len]);
5797
7346
  break;
@@ -5802,19 +7351,23 @@ function parseDateTimeSkeleton(skeleton, language) {
5802
7351
  throw new RangeError(
5803
7352
  '`S/A` (millisecond) patterns are not supported, use `s` instead'
5804
7353
  );
5805
- case 'O':
7354
+ // Zone
7355
+ case 'O': // timeZone GMT-8 or GMT-08:00
5806
7356
  result.push(['timeZoneName', len < 4 ? 'shortOffset' : 'longOffset', len]);
5807
7357
  result.push(['x-timeZoneName', len < 4 ? 'O' : 'OOOO', len]);
5808
7358
  break;
5809
- case 'X':
5810
- case 'x':
5811
- case 'Z':
7359
+ case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
7360
+ case 'x': // 1, 2, 3, 4: The ISO8601 varios formats
7361
+ case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
7362
+ // Z, ZZ, ZZZ should produce -0800
7363
+ // ZZZZ should produce GMT-08:00
7364
+ // ZZZZZ should produce -8:00 or -07:52:58
5812
7365
  result.push(['timeZoneName', 'longOffset', 1]);
5813
7366
  result.push(['x-timeZoneName', match, 1]);
5814
7367
  break;
5815
- case 'z':
5816
- case 'v':
5817
- case 'V':
7368
+ case 'z': // 1..3, 4: specific non-location format
7369
+ case 'v': // 1, 4: generic non-location format
7370
+ case 'V': // 1, 2, 3, 4: time zone ID or city
5818
7371
  throw new RangeError(
5819
7372
  'z/v/V` (timeZone) patterns are not supported, use `X/x/Z/O` instead'
5820
7373
  );
@@ -5829,6 +7382,29 @@ function parseDateTimeSkeleton(skeleton, language) {
5829
7382
  return result;
5830
7383
  }
5831
7384
 
7385
+ /*************************************************************************
7386
+ * ADOBE CONFIDENTIAL
7387
+ * ___________________
7388
+ *
7389
+ * Copyright 2022 Adobe
7390
+ * All Rights Reserved.
7391
+ *
7392
+ * NOTICE: All information contained herein is, and remains
7393
+ * the property of Adobe and its suppliers, if any. The intellectual
7394
+ * and technical concepts contained herein are proprietary to Adobe
7395
+ * and its suppliers and are protected by all applicable intellectual
7396
+ * property laws, including trade secret and copyright laws.
7397
+ * Dissemination of this information or reproduction of this material
7398
+ * is strictly forbidden unless prior written permission is obtained
7399
+ * from Adobe.
7400
+ **************************************************************************/
7401
+
7402
+ /**
7403
+ * in some cases, DateTimeFormat doesn't respect the 'numeric' vs. '2-digit' setting
7404
+ * for time values. The function corrects that
7405
+ * @param formattedParts instance of Intl.DateTimeFormatPart[]
7406
+ * @param parsed
7407
+ */
5832
7408
  function fixDigits(formattedParts, parsed) {
5833
7409
  ['hour', 'minute', 'second'].forEach(type => {
5834
7410
  const defn = formattedParts.find(f => f.type === type);
@@ -5838,29 +7414,55 @@ function fixDigits(formattedParts, parsed) {
5838
7414
  if (fmt === 'numeric' && defn.value.length === 2 && defn.value.charAt(0) === '0') defn.value = defn.value.slice(1);
5839
7415
  });
5840
7416
  }
7417
+
5841
7418
  function fixYear(formattedParts, parsed) {
7419
+ // two digit years are handled differently in DateTimeFormat. 00 becomes 1900
7420
+ // providing a two digit year 0010 gets formatted to 10 and when parsed becomes 1910
7421
+ // Hence we need to pad the year with 0 as required by the skeleton and mentioned in
7422
+ // unicode. https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-year
5842
7423
  const defn = formattedParts.find(f => f.type === 'year');
5843
7424
  if (!defn) return;
7425
+ // eslint-disable-next-line no-unused-vars
5844
7426
  const chars = parsed.find(pair => pair[0] === 'year')[2];
5845
7427
  while(defn.value.length < chars) {
5846
7428
  defn.value = `0${defn.value}`;
5847
7429
  }
5848
7430
  }
7431
+
7432
+ /**
7433
+ *
7434
+ * @param dateValue {Date}
7435
+ * @param language {string}
7436
+ * @param skeleton {string}
7437
+ * @param timeZone {string}
7438
+ * @returns {T}
7439
+ */
5849
7440
  function formatDateToParts(dateValue, language, skeleton, timeZone) {
7441
+ // DateTimeFormat renames some of the options in its formatted output
7442
+ //@ts-ignore
5850
7443
  const mappings = key => ({
5851
7444
  hour12: 'dayPeriod',
5852
7445
  fractionalSecondDigits: 'fractionalSecond',
5853
7446
  })[key] || key;
7447
+
7448
+ // produces an array of name/value pairs of skeleton parts
5854
7449
  const allParameters = parseDateTimeSkeleton(skeleton, language);
5855
7450
  allParameters.push(['timeZone', timeZone]);
7451
+
5856
7452
  const parsed = allParameters.filter(p => !p[0].startsWith('x-'));
5857
7453
  const nonStandard = allParameters.filter(p => p[0].startsWith('x-'));
7454
+ // reduce to a set of options that can be used to format
5858
7455
  const options = Object.fromEntries(parsed);
5859
7456
  delete options.literal;
7457
+
5860
7458
  const df = new Intl.DateTimeFormat(language, options);
7459
+ // formattedParts will have all the pieces we need for our date -- but not in the correct order
5861
7460
  const formattedParts = df.formatToParts(dateValue);
7461
+
5862
7462
  fixDigits(formattedParts, allParameters);
5863
7463
  fixYear(formattedParts, parsed);
7464
+ // iterate through the original parsed components and use its ordering and literals,
7465
+ // and add the formatted pieces
5864
7466
  return parsed.reduce((result, cur) => {
5865
7467
  if (cur[0] === 'literal') result.push(cur);
5866
7468
  else {
@@ -5870,25 +7472,37 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
5870
7472
  const category = tz[0];
5871
7473
  if (category === 'Z') {
5872
7474
  if (tz.length < 4) {
7475
+ // handle 'Z', 'ZZ', 'ZZZ' Time Zone: ISO8601 basic hms? / RFC 822
5873
7476
  v.value = v.value.replace(/(GMT|:)/g, '');
5874
7477
  if (v.value === '') v.value = '+0000';
5875
7478
  } else if (tz.length === 5) {
7479
+ // 'ZZZZZ' Time Zone: ISO8601 extended hms?
5876
7480
  if (v.value === 'GMT') v.value = 'Z';
5877
7481
  else v.value = v.value.replace(/GMT/, '');
5878
7482
  }
5879
7483
  }
5880
7484
  if (category === 'X' || category === 'x') {
5881
7485
  if (tz.length === 1) {
7486
+ // 'X' ISO8601 basic hm?, with Z for 0
7487
+ // -08, +0530, Z
7488
+ // 'x' ISO8601 basic hm?, without Z for 0
5882
7489
  v.value = v.value.replace(/(GMT|:(00)?)/g, '');
5883
7490
  }
5884
7491
  if (tz.length === 2) {
7492
+ // 'XX' ISO8601 basic hm, with Z
7493
+ // -0800, Z
7494
+ // 'xx' ISO8601 basic hm, without Z
5885
7495
  v.value = v.value.replace(/(GMT|:)/g, '');
5886
7496
  }
5887
7497
  if (tz.length === 3) {
7498
+ // 'XXX' ISO8601 extended hm, with Z
7499
+ // -08:00, Z
7500
+ // 'xxx' ISO8601 extended hm, without Z
5888
7501
  v.value = v.value.replace(/GMT/g, '');
5889
7502
  }
5890
7503
  if (category === 'X' && v.value === '') v.value = 'Z';
5891
7504
  } else if (tz === 'O') {
7505
+ // eliminate 'GMT', leading and trailing zeros
5892
7506
  v.value = v.value.replace(/GMT/g, '').replace(/0(\d+):/, '$1:').replace(/:00/, '');
5893
7507
  if (v.value === '') v.value = '+0';
5894
7508
  }
@@ -5898,12 +7512,18 @@ function formatDateToParts(dateValue, language, skeleton, timeZone) {
5898
7512
  return result;
5899
7513
  }, []);
5900
7514
  }
7515
+
7516
+ /**
7517
+ *
7518
+ * @param dateValue {Date}
7519
+ * @param language {string}
7520
+ * @param skeleton {string}
7521
+ * @param timeZone {string}
7522
+ */
5901
7523
  function formatDate(dateValue, language, skeleton, timeZone) {
5902
- if (skeleton.startsWith('date|')) {
5903
- skeleton = skeleton.split('|')[1];
5904
- }
5905
7524
  if (ShorthandStyles$1.find(type => skeleton.includes(type))) {
5906
7525
  const options = {timeZone};
7526
+ // the skeleton could have two keywords -- one for date, one for time
5907
7527
  const parts = skeleton.split(/\s/).filter(s => s.length);
5908
7528
  if (ShorthandStyles$1.indexOf(parts[0]) > -1) {
5909
7529
  options.dateStyle = parts[0];
@@ -5939,7 +7559,9 @@ const currencies = {
5939
7559
  'ru-RU': 'RUB',
5940
7560
  'tr-TR': 'TRY'
5941
7561
  };
7562
+
5942
7563
  const locales = Object.keys(currencies);
7564
+
5943
7565
  const getCurrency = function (locale) {
5944
7566
  if (locales.indexOf(locale) > -1) {
5945
7567
  return currencies[locale]
@@ -5953,7 +7575,8 @@ const getCurrency = function (locale) {
5953
7575
  };
5954
7576
 
5955
7577
  const NUMBER_REGEX =
5956
- /(?:[#]+|[@]+(#+)?|[0]+|[,]|[.]|[-]|[+]|[%]|[¤]{1,4}(?:\/([a-zA-Z]{3}))?|[;]|[K]{1,2}|E{1,2}[+]?|'(?:[^']|'')*')|[^a-zA-Z']+/g;
7578
+ // eslint-disable-next-line max-len
7579
+ /(?:[#]+|[@]+(?:#+)?|[0]+|[,]|[.]|[-]|[+]|[%]|[¤]{1,4}(?:\/([a-zA-Z]{3}))?|[;]|[K]{1,2}|E{1,2}[+]?|'(?:[^']|'')*')|[^a-zA-Z']+/g;
5957
7580
  const supportedUnits = ['acre', 'bit', 'byte', 'celsius', 'centimeter', 'day',
5958
7581
  'degree', 'fahrenheit', 'fluid-ounce', 'foot', 'gallon', 'gigabit',
5959
7582
  'gigabyte', 'gram', 'hectare', 'hour', 'inch', 'kilobit', 'kilobyte',
@@ -5961,6 +7584,8 @@ const supportedUnits = ['acre', 'bit', 'byte', 'celsius', 'centimeter', 'day',
5961
7584
  'mile-scandinavian', 'milliliter', 'millimeter', 'millisecond', 'minute', 'month',
5962
7585
  'ounce', 'percent', 'petabyte', 'pound', 'second', 'stone', 'terabit', 'terabyte', 'week', 'yard', 'year'].join('|');
5963
7586
  const ShorthandStyles = [/^currency(?:\/([a-zA-Z]{3}))?$/, /^decimal$/, /^integer$/, /^percent$/, new RegExp(`^unit\/(${supportedUnits})$`)];
7587
+
7588
+
5964
7589
  function parseNumberSkeleton(skeleton, language) {
5965
7590
  const options = {};
5966
7591
  const order = [];
@@ -5989,7 +7614,6 @@ function parseNumberSkeleton(skeleton, language) {
5989
7614
  break;
5990
7615
  case 4:
5991
7616
  options.style = 'percent';
5992
- options.maximumFractionDigits = 2;
5993
7617
  break;
5994
7618
  case 5:
5995
7619
  options.style = "unit";
@@ -6006,7 +7630,7 @@ function parseNumberSkeleton(skeleton, language) {
6006
7630
  options.minimumIntegerDigits = 1;
6007
7631
  options.maximumFractionDigits = 0;
6008
7632
  options.minimumFractionDigits = 0;
6009
- skeleton.replace(NUMBER_REGEX, (match, maxSignificantDigits, currencySymbol, offset) => {
7633
+ skeleton.replace(NUMBER_REGEX, (match, offset) => {
6010
7634
  const len = match.length;
6011
7635
  switch(match[0]) {
6012
7636
  case '#':
@@ -6019,10 +7643,10 @@ function parseNumberSkeleton(skeleton, language) {
6019
7643
  if (options?.minimumSignificantDigits) {
6020
7644
  throw "@ symbol should occur together"
6021
7645
  }
6022
- const hashes = maxSignificantDigits || "";
6023
- order.push(['@', len - hashes.length]);
6024
- options.minimumSignificantDigits = len - hashes.length;
6025
- options.maximumSignificantDigits = len;
7646
+ order.push(['@', len]);
7647
+ options.minimumSignificantDigits = len;
7648
+ const hashes = match.match(/#+/) || "";
7649
+ options.maximumSignificantDigits = len + hashes.length;
6026
7650
  order.push(['digit', hashes.length]);
6027
7651
  break;
6028
7652
  case ',':
@@ -6047,9 +7671,6 @@ function parseNumberSkeleton(skeleton, language) {
6047
7671
  }
6048
7672
  if (options?.decimal === true) {
6049
7673
  options.minimumFractionDigits = len;
6050
- if (!options.maximumFractionDigits) {
6051
- options.maximumFractionDigits = len;
6052
- }
6053
7674
  } else {
6054
7675
  options.minimumIntegerDigits = len;
6055
7676
  }
@@ -6073,20 +7694,18 @@ function parseNumberSkeleton(skeleton, language) {
6073
7694
  case '¤':
6074
7695
  if (offset !== 0 && offset !== skeleton.length - 1) {
6075
7696
  console.error("currency display should be either in the beginning or at the end");
6076
- } else {
6077
- options.style = 'currency';
6078
- options.currencyDisplay = ['symbol', 'code', 'name', 'narrowSymbol'][len - 1];
6079
- options.currency = currencySymbol || getCurrency(language);
6080
- order.push(['currency', len]);
6081
7697
  }
7698
+ options.style = 'currency';
7699
+ options.currencyDisplay = ['symbol', 'code', 'name', 'narrowSymbol'][len -1];
7700
+ options.currency = getCurrency(language);
7701
+ order.push(['currency', len]);
6082
7702
  break;
6083
7703
  case '%':
6084
7704
  if (offset !== 0 && offset !== skeleton.length - 1) {
6085
7705
  console.error("percent display should be either in the beginning or at the end");
6086
- } else {
6087
- order.push(['%', 1]);
6088
- options.style = 'percent';
6089
7706
  }
7707
+ order.push(['%', 1]);
7708
+ options.style = 'percent';
6090
7709
  break;
6091
7710
  case 'E':
6092
7711
  order.push(['E', len]);
@@ -6103,9 +7722,6 @@ function parseNumberSkeleton(skeleton, language) {
6103
7722
  }
6104
7723
 
6105
7724
  function formatNumber(numberValue, language, skeletn) {
6106
- if (skeletn.startsWith('num|')) {
6107
- skeletn = skel.split('|')[1];
6108
- }
6109
7725
  if (!skeletn) return numberValue
6110
7726
  language = language || "en";
6111
7727
  const {options, order} = parseNumberSkeleton(skeletn, language);
@@ -6116,6 +7732,7 @@ const getCategory = function (skeleton) {
6116
7732
  const chkCategory = skeleton?.match(/^(?:(num|date)\|)?(.+)/);
6117
7733
  return [chkCategory?.[1], chkCategory?.[2]]
6118
7734
  };
7735
+
6119
7736
  const format = function (value, locale, skeleton, timezone) {
6120
7737
  const [category, skelton] = getCategory(skeleton);
6121
7738
  switch (category) {
@@ -6131,6 +7748,12 @@ const format = function (value, locale, skeleton, timezone) {
6131
7748
  }
6132
7749
  };
6133
7750
 
7751
+ var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
7752
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
7753
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
7754
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
7755
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7756
+ };
6134
7757
  const validTypes = ['string', 'number', 'boolean', 'file', 'string[]', 'number[]', 'boolean[]', 'file[]', 'array', 'object'];
6135
7758
  class Field extends Scriptable {
6136
7759
  constructor(params, _options) {
@@ -6279,10 +7902,10 @@ class Field extends Scriptable {
6279
7902
  }
6280
7903
  }
6281
7904
  get editFormat() {
6282
- return this.withCategory(this._jsonModel.editFormat);
7905
+ return this._jsonModel.editFormat;
6283
7906
  }
6284
7907
  get displayFormat() {
6285
- return this.withCategory(this._jsonModel.displayFormat);
7908
+ return this._jsonModel.displayFormat;
6286
7909
  }
6287
7910
  get placeholder() {
6288
7911
  return this._jsonModel.placeholder;
@@ -6355,43 +7978,31 @@ class Field extends Scriptable {
6355
7978
  return this._jsonModel.value === undefined || this._jsonModel.value === null || this._jsonModel.value === '';
6356
7979
  }
6357
7980
  withCategory(df) {
6358
- if (df) {
6359
- const hasCategory = df?.match(/^(?:date|num)\|/);
6360
- if (hasCategory === null) {
6361
- if (this.format === 'date') {
6362
- df = `date|${df}`;
6363
- }
6364
- else if (this.type === 'number') {
6365
- df = `num|${df}`;
6366
- }
6367
- return df;
7981
+ const hasCategory = df?.match(/^(?:date|num)\|/);
7982
+ if (hasCategory == null) {
7983
+ if (this.format === 'date') {
7984
+ df = `date|${df}`;
7985
+ }
7986
+ else if (this.type === 'number') {
7987
+ df = `num|${df}`;
6368
7988
  }
7989
+ return df;
6369
7990
  }
6370
7991
  return df;
6371
7992
  }
6372
7993
  get editValue() {
6373
- const df = this.editFormat;
7994
+ const df = this.withCategory(this.editFormat);
6374
7995
  if (df && this.isNotEmpty(this.value) && this.valid !== false) {
6375
- try {
6376
- return format(this.value, this.language, df);
6377
- }
6378
- catch (e) {
6379
- return this.value;
6380
- }
7996
+ return format(this.value, this.language, df);
6381
7997
  }
6382
7998
  else {
6383
7999
  return this.value;
6384
8000
  }
6385
8001
  }
6386
8002
  get displayValue() {
6387
- const df = this.displayFormat;
8003
+ const df = this.withCategory(this.displayFormat);
6388
8004
  if (df && this.isNotEmpty(this.value) && this.valid !== false) {
6389
- try {
6390
- return format(this.value, this.language, df);
6391
- }
6392
- catch (e) {
6393
- return this.value;
6394
- }
8005
+ return format(this.value, this.language, df);
6395
8006
  }
6396
8007
  else {
6397
8008
  return this.value;
@@ -6531,7 +8142,7 @@ class Field extends Scriptable {
6531
8142
  const iv = this._jsonModel.minimum || this._jsonModel.default || 0;
6532
8143
  const fIVal = iv * factor;
6533
8144
  const qt = (fVal - fIVal) / fStep;
6534
- const valid = Math.abs(fVal - fIVal) % fStep < .001;
8145
+ const valid = (fVal - fIVal) % fStep < .001;
6535
8146
  let next, prev;
6536
8147
  if (!valid) {
6537
8148
  next = (Math.ceil(qt) * fStep + fIVal) / factor;
@@ -6734,8 +8345,6 @@ class Field extends Scriptable {
6734
8345
  getState() {
6735
8346
  return {
6736
8347
  ...super.getState(),
6737
- editFormat: this.editFormat,
6738
- displayFormat: this.displayFormat,
6739
8348
  editValue: this.editValue,
6740
8349
  displayValue: this.displayValue
6741
8350
  };