@openmrs/esm-expression-evaluator 11.0.1-pre.5771 → 11.0.1-pre.5775
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/extractor.js +18 -41
- package/package.json +1 -1
- package/src/evaluator.test.ts +9 -0
- package/src/extractor.test.ts +20 -0
- package/src/extractor.ts +18 -34
package/.turbo/turbo-build.log
CHANGED
package/dist/extractor.js
CHANGED
|
@@ -81,58 +81,39 @@ function visitExpression(expression, context) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
function visitUnaryExpression(expression, context) {
|
|
84
|
-
|
|
84
|
+
visitExpression(expression.argument, context);
|
|
85
85
|
}
|
|
86
86
|
function visitBinaryExpression(expression, context) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return [
|
|
90
|
-
left,
|
|
91
|
-
right
|
|
92
|
-
].filter(Boolean);
|
|
87
|
+
visitExpression(expression.left, context);
|
|
88
|
+
visitExpression(expression.right, context);
|
|
93
89
|
}
|
|
94
90
|
function visitConditionalExpression(expression, context) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return [
|
|
99
|
-
consequent,
|
|
100
|
-
test,
|
|
101
|
-
alternate
|
|
102
|
-
].filter(Boolean);
|
|
91
|
+
visitExpression(expression.consequent, context);
|
|
92
|
+
visitExpression(expression.test, context);
|
|
93
|
+
visitExpression(expression.alternate, context);
|
|
103
94
|
}
|
|
104
95
|
function visitCallExpression(expression, context) {
|
|
105
|
-
|
|
96
|
+
visitExpression(expression.callee, context);
|
|
106
97
|
expression.arguments?.map(handleNullableExpression(context));
|
|
107
|
-
return fn;
|
|
108
98
|
}
|
|
109
99
|
function visitArrowFunctionExpression(expression, context) {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const params = expression.params?.map(handleNullableExpression(newContext)) ?? [];
|
|
115
|
-
const bodyVariables = visitExpression(expression.body, newContext) ?? [];
|
|
116
|
-
if (bodyVariables && Array.isArray(bodyVariables)) {
|
|
117
|
-
for (const v of bodyVariables){
|
|
118
|
-
if (!params.includes(v)) {
|
|
119
|
-
context.variables.add(v);
|
|
120
|
-
}
|
|
100
|
+
const boundNames = new Set(context.boundNames);
|
|
101
|
+
for (const param of expression.params ?? []){
|
|
102
|
+
if (param?.type === 'Identifier') {
|
|
103
|
+
boundNames.add(param.name);
|
|
121
104
|
}
|
|
122
105
|
}
|
|
106
|
+
visitExpression(expression.body, {
|
|
107
|
+
...context,
|
|
108
|
+
boundNames
|
|
109
|
+
});
|
|
123
110
|
}
|
|
124
111
|
function visitMemberExpression(expression, context) {
|
|
125
112
|
visitExpression(expression.object, context);
|
|
126
113
|
// the `b` of `a[b]` is a variable the caller has to supply, whereas the `b` of `a.b` is a property name
|
|
127
114
|
if (expression.computed) {
|
|
128
115
|
visitExpression(expression.property, context);
|
|
129
|
-
return;
|
|
130
116
|
}
|
|
131
|
-
const newContext = {
|
|
132
|
-
...context
|
|
133
|
-
};
|
|
134
|
-
newContext.isLocalExpression = true;
|
|
135
|
-
visitExpression(expression.property, newContext);
|
|
136
117
|
}
|
|
137
118
|
function visitArrayExpression(expression, context) {
|
|
138
119
|
expression.elements?.map(handleNullableExpression(context));
|
|
@@ -149,12 +130,8 @@ function visitTemplateLiteral(expression, context) {
|
|
|
149
130
|
}
|
|
150
131
|
function visitTemplateElement(expression, context) {}
|
|
151
132
|
function visitIdentifier(expression, context) {
|
|
152
|
-
if (!(expression.name in context.globals)) {
|
|
153
|
-
|
|
154
|
-
context.variables.add(expression.name);
|
|
155
|
-
} else {
|
|
156
|
-
return expression.name;
|
|
157
|
-
}
|
|
133
|
+
if (!(expression.name in context.globals) && !context.boundNames.has(expression.name)) {
|
|
134
|
+
context.variables.add(expression.name);
|
|
158
135
|
}
|
|
159
136
|
}
|
|
160
137
|
function visitLiteral(expression, context) {}
|
|
@@ -166,7 +143,7 @@ function createContextInternal(globals_) {
|
|
|
166
143
|
globals: {
|
|
167
144
|
...globals_
|
|
168
145
|
},
|
|
169
|
-
|
|
146
|
+
boundNames: new Set(),
|
|
170
147
|
variables: new Set()
|
|
171
148
|
};
|
|
172
149
|
return context;
|
package/package.json
CHANGED
package/src/evaluator.test.ts
CHANGED
|
@@ -51,6 +51,15 @@ describe('OpenMRS Expression Evaluator', () => {
|
|
|
51
51
|
expect(evaluate('a.includes("v")', { a: 'value' })).toBe(true);
|
|
52
52
|
expect(evaluate('"value".includes("v")')).toBe(true);
|
|
53
53
|
expect(evaluate('(3.14159).toPrecision(3)')).toBe('3.14');
|
|
54
|
+
expect(evaluate('s.toUpperCase()', { s: 'active' })).toBe('ACTIVE');
|
|
55
|
+
expect(evaluate('n.toFixed(1)', { n: 3.14159 })).toBe('3.1');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should read properties of strings from the string itself', () => {
|
|
59
|
+
expect(evaluate('s.length', { s: 'active' })).toBe(6);
|
|
60
|
+
expect(evaluate('"active".length')).toBe(6);
|
|
61
|
+
expect(evaluate('s.length', { s: 'active' })).toBe(evaluate('s["length"]', { s: 'active' }));
|
|
62
|
+
expect(evaluate('s[0]', { s: 'active' })).toBe('a');
|
|
54
63
|
});
|
|
55
64
|
|
|
56
65
|
it('should give a useful error message for properties on missing objects', () => {
|
package/src/extractor.test.ts
CHANGED
|
@@ -47,6 +47,26 @@ describe('OpenMRS Expression Extractor', () => {
|
|
|
47
47
|
expect(extractVariableNames('[1, 2, 3].find(v => v === a)')).toEqual(['a']);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
it('extracts unbound variables from any kind of arrow function body', () => {
|
|
51
|
+
expect(extractVariableNames('a.map(x => f(b))')).toEqual(['a', 'f', 'b']);
|
|
52
|
+
expect(extractVariableNames('a.map(x => !b)')).toEqual(['a', 'b']);
|
|
53
|
+
expect(extractVariableNames('a.map(x => b.c)')).toEqual(['a', 'b']);
|
|
54
|
+
expect(extractVariableNames('a.map(x => x + b)')).toEqual(['a', 'b']);
|
|
55
|
+
expect(extractVariableNames('a.map(x => x ? b : c)')).toEqual(['a', 'b', 'c']);
|
|
56
|
+
expect(extractVariableNames('a.then(v => !isEmpty(b) ? v + 3 : v)')).toEqual(['a', 'isEmpty', 'b']);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('excludes arrow function parameters, including from nested arrow functions', () => {
|
|
60
|
+
expect(extractVariableNames('a.map(x => x.y)')).toEqual(['a']);
|
|
61
|
+
expect(extractVariableNames('a.reduce((acc, x) => acc + x, 0)')).toEqual(['a']);
|
|
62
|
+
expect(extractVariableNames('a.map(x => x.items.filter(y => y === x || y === b))')).toEqual(['a', 'b']);
|
|
63
|
+
expect(extractVariableNames('a.map(x => f(y => y + x + z))')).toEqual(['a', 'f', 'z']);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('only binds arrow function parameters within the arrow function body', () => {
|
|
67
|
+
expect(extractVariableNames('a.map(x => x) + x')).toEqual(['a', 'x']);
|
|
68
|
+
});
|
|
69
|
+
|
|
50
70
|
it('supports real-world use-cases', () => {
|
|
51
71
|
expect(extractVariableNames('!isEmpty(array)')).toEqual(['isEmpty', 'array']);
|
|
52
72
|
|
package/src/extractor.ts
CHANGED
|
@@ -89,42 +89,34 @@ function visitExpression(expression: jsep.Expression, context: EvaluationContext
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function visitUnaryExpression(expression: jsep.UnaryExpression, context: EvaluationContext) {
|
|
92
|
-
|
|
92
|
+
visitExpression(expression.argument, context);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function visitBinaryExpression(expression: jsep.BinaryExpression, context: EvaluationContext) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return [left, right].filter(Boolean);
|
|
96
|
+
visitExpression(expression.left, context);
|
|
97
|
+
visitExpression(expression.right, context);
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
function visitConditionalExpression(expression: jsep.ConditionalExpression, context: EvaluationContext) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return [consequent, test, alternate].filter(Boolean);
|
|
101
|
+
visitExpression(expression.consequent, context);
|
|
102
|
+
visitExpression(expression.test, context);
|
|
103
|
+
visitExpression(expression.alternate, context);
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
function visitCallExpression(expression: jsep.CallExpression, context: EvaluationContext) {
|
|
109
|
-
|
|
107
|
+
visitExpression(expression.callee, context);
|
|
110
108
|
expression.arguments?.map(handleNullableExpression(context));
|
|
111
|
-
return fn;
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
function visitArrowFunctionExpression(expression: ArrowExpression, context: EvaluationContext) {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const bodyVariables = visitExpression(expression.body, newContext) ?? [];
|
|
120
|
-
|
|
121
|
-
if (bodyVariables && Array.isArray(bodyVariables)) {
|
|
122
|
-
for (const v of bodyVariables) {
|
|
123
|
-
if (!params.includes(v)) {
|
|
124
|
-
context.variables.add(v);
|
|
125
|
-
}
|
|
112
|
+
const boundNames = new Set(context.boundNames);
|
|
113
|
+
for (const param of expression.params ?? []) {
|
|
114
|
+
if (param?.type === 'Identifier') {
|
|
115
|
+
boundNames.add((param as jsep.Identifier).name);
|
|
126
116
|
}
|
|
127
117
|
}
|
|
118
|
+
|
|
119
|
+
visitExpression(expression.body, { ...context, boundNames });
|
|
128
120
|
}
|
|
129
121
|
|
|
130
122
|
function visitMemberExpression(expression: jsep.MemberExpression, context: EvaluationContext) {
|
|
@@ -133,12 +125,7 @@ function visitMemberExpression(expression: jsep.MemberExpression, context: Evalu
|
|
|
133
125
|
// the `b` of `a[b]` is a variable the caller has to supply, whereas the `b` of `a.b` is a property name
|
|
134
126
|
if (expression.computed) {
|
|
135
127
|
visitExpression(expression.property, context);
|
|
136
|
-
return;
|
|
137
128
|
}
|
|
138
|
-
|
|
139
|
-
const newContext = { ...context };
|
|
140
|
-
newContext.isLocalExpression = true;
|
|
141
|
-
visitExpression(expression.property, newContext);
|
|
142
129
|
}
|
|
143
130
|
|
|
144
131
|
function visitArrayExpression(expression: jsep.ArrayExpression, context: EvaluationContext) {
|
|
@@ -161,12 +148,8 @@ function visitTemplateLiteral(expression: TemplateLiteral, context: EvaluationCo
|
|
|
161
148
|
function visitTemplateElement(expression: TemplateElement, context: EvaluationContext) {}
|
|
162
149
|
|
|
163
150
|
function visitIdentifier(expression: jsep.Identifier, context: EvaluationContext) {
|
|
164
|
-
if (!(expression.name in context.globals)) {
|
|
165
|
-
|
|
166
|
-
context.variables.add(expression.name);
|
|
167
|
-
} else {
|
|
168
|
-
return expression.name;
|
|
169
|
-
}
|
|
151
|
+
if (!(expression.name in context.globals) && !context.boundNames.has(expression.name)) {
|
|
152
|
+
context.variables.add(expression.name);
|
|
170
153
|
}
|
|
171
154
|
}
|
|
172
155
|
|
|
@@ -175,7 +158,8 @@ function visitLiteral(expression: jsep.Literal, context: EvaluationContext) {}
|
|
|
175
158
|
// Internals
|
|
176
159
|
interface EvaluationContext {
|
|
177
160
|
globals: typeof globalsAsync;
|
|
178
|
-
|
|
161
|
+
/** Parameters of the enclosing arrow functions, which the caller does not supply */
|
|
162
|
+
boundNames: Set<string>;
|
|
179
163
|
variables: Set<string>;
|
|
180
164
|
}
|
|
181
165
|
|
|
@@ -186,7 +170,7 @@ function createAsynchronousContext(): EvaluationContext {
|
|
|
186
170
|
function createContextInternal(globals_: typeof globalsAsync) {
|
|
187
171
|
const context = {
|
|
188
172
|
globals: { ...globals_ },
|
|
189
|
-
|
|
173
|
+
boundNames: new Set<string>(),
|
|
190
174
|
variables: new Set<string>(),
|
|
191
175
|
};
|
|
192
176
|
|