Skip to content

Commit 7706caf

Browse files
committed
adds support for prefix option, to put data on a controlled namespace
1 parent d3f7e50 commit 7706caf

File tree

13 files changed

+1096
-63
lines changed

13 files changed

+1096
-63
lines changed

lib/Expression.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
22

33
const ExpressionSync = require('./ExpressionSync');
4-
const transform = require('./transform');
54
const variables = require('./variables');
65
const utils = require('./utils');
76

@@ -136,7 +135,6 @@ class Expression extends ExpressionSync {
136135
async BinaryExpression(node, context) {
137136
const left = await this.visit(node.left, context, node);
138137
const right = () => this.visit(node.right, context, node);
139-
140138
return this.comparison(left, node.operator, right, context, node);
141139
}
142140

@@ -153,11 +151,16 @@ class Expression extends ExpressionSync {
153151
async ConditionalExpression(node, context) {
154152
const { test, consequent, alternate } = node;
155153
const truthy = await this.visit(test, context, node);
156-
return truthy ? await this.visit(consequent, context, node) : await this.visit(alternate, context, node);
154+
return this.visit(truthy ? consequent : alternate, context, node);
157155
}
158156

159157
async MemberExpression(node, context, parent) {
160158
const { computed, object, property, unset } = node;
159+
160+
if (object.name && this.options.prefix && !object.name.startsWith(this.options.prefix)) {
161+
object.name = `${this.options.prefix}.${object.name}`;
162+
}
163+
161164
const value = (await this.visit(object, context, node)) ?? context[object.name];
162165
const data = computed ? context : value;
163166

@@ -185,7 +188,13 @@ class Expression extends ExpressionSync {
185188
Object.assign(object, await this.visit(property, context, node));
186189
} else {
187190
const name = property.computed ? await this.visit(key, context, property) : (key.value || key.name);
188-
object[name] = await this.visit(value, context, property);
191+
const result = await this.visit(value, context, property);
192+
193+
if (property.shorthand && this.options.prefix) {
194+
property.shorthand = false;
195+
}
196+
197+
object[name] = result;
189198
}
190199
}
191200

lib/ExpressionSync.js

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const { default: getValue } = require('get-value');
34
const FunctionsSync = require('./FunctionsSync');
45
const Functions = require('./Functions');
56
const variables = require('./variables');
@@ -57,11 +58,29 @@ class ExpressionSync {
5758
this.state.expressionDepth--;
5859
}
5960

61+
createContext(context) {
62+
if (typeof context === 'function') {
63+
return context;
64+
}
65+
66+
if (utils.isPlainObject(context)) {
67+
return { ...context };
68+
}
69+
70+
return context;
71+
}
72+
6073
visit(node, context, parent) {
6174
this.incrementDepth();
6275

6376
try {
64-
Reflect.defineProperty(node, 'parent', { value: node.parent || parent });
77+
Reflect.defineProperty(node, 'parent', {
78+
configurable: true,
79+
enumerable: false,
80+
writable: true,
81+
value: node.parent || parent
82+
});
83+
6584
const visitor = this.visitors[node.type] || this[node.type];
6685

6786
if (typeof visitor !== 'function') {
@@ -70,13 +89,19 @@ class ExpressionSync {
7089
throw new TypeError(message);
7190
}
7291

73-
const block = node.type === 'ArrayExpression' || node.type === 'ObjectExpression';
74-
if (block) this.stack.push(node);
92+
// const block = node.type === 'ArrayExpression' || node.type === 'ObjectExpression';
93+
const ignore = ['Identifier', 'BinaryExpression'];
94+
95+
if (!ignore.includes(node.type)) {
96+
this.stack.push(node);
97+
} else {
98+
//
99+
}
75100

76101
const value = visitor.call(this, node, context, parent);
77102
const resolve = v => {
78103
if (v instanceof Promise) return v.then(v => resolve(v));
79-
if (block) this.stack.pop();
104+
if (!ignore.includes(node.type)) this.stack.pop();
80105
if (this.state.fail) return;
81106
return v;
82107
};
@@ -95,7 +120,7 @@ class ExpressionSync {
95120
return node.operator === '=' && node.right?.operator === '~' && this.options.regexOperator !== false;
96121
}
97122

98-
assignment(node) {
123+
assignment(node) {
99124
switch (node.operator) {
100125
case '=': // Assignment operator.
101126
case '*=': // Multiplication assignment.
@@ -119,14 +144,38 @@ class ExpressionSync {
119144
}
120145
}
121146

122-
postfix(node, value) {
147+
assignOperation(operator, left, right) {
148+
switch (operator) {
149+
case '=': return right;
150+
case '+=': return left + right;
151+
case '-=': return left - right;
152+
case '*=': return left * right;
153+
case '/=': return left / right;
154+
case '%=': return left % right;
155+
case '**=': return left ** right;
156+
case '<<=': return left << right;
157+
case '>>=': return left >> right;
158+
case '>>>=': return left >>> right;
159+
case '&=': return left & right;
160+
case '^=': return left ^ right;
161+
case '|=': return left | right;
162+
case '&&=': return left && right;
163+
case '||=': return left || right;
164+
case '??=': return left ?? right;
165+
default: {
166+
throw new SyntaxError(`Assignment operator "${operator}" is not supported`);
167+
}
168+
}
169+
}
170+
171+
postfix(node, value) {
123172
switch (node.operator) {
124173
case '++': return value + 1;
125174
case '--': return value - 1;
126175
}
127176
}
128177

129-
prefix(node, value) {
178+
prefix(node, value) {
130179
switch (node.operator) {
131180
case '++': return value + 1;
132181
case '--': return value - 1;
@@ -139,6 +188,12 @@ class ExpressionSync {
139188
const unset = obj => {
140189
if (utils.isObject(obj)) {
141190
Reflect.deleteProperty(obj, node.property.name);
191+
192+
const name = node.object.name;
193+
if (name && this.options.prefix && !name.startsWith(this.options.prefix)) {
194+
node.object.name = `${this.options.prefix}.${node.object.name}`;
195+
}
196+
142197
context[node.object.name] = obj;
143198
return true;
144199
}
@@ -196,7 +251,7 @@ class ExpressionSync {
196251
// Equality operators
197252
case '!==': return left !== val();
198253
case '===': return left === val();
199-
case '!=': return left != val(); /* eslint-disable-line eqeqeq */
254+
case '!=': return left != val(); /* eslint-disable-line eqeqeq */
200255
case '==': return left == val(); /* eslint-disable-line eqeqeq */
201256

202257
// Bitwise shift operators
@@ -300,7 +355,7 @@ class ExpressionSync {
300355
ConditionalExpression(node, context) {
301356
const { test, consequent, alternate } = node;
302357
const truthy = this.visit(test, context, node);
303-
return truthy ? this.visit(consequent, context, node) : this.visit(alternate, context, node);
358+
return this.visit(truthy ? consequent : alternate, context, node);
304359
}
305360

306361
Identifier(node, context, parent) {
@@ -314,8 +369,25 @@ class ExpressionSync {
314369
return;
315370
}
316371

372+
if (this.options.prefix && !node.name.startsWith(this.options.prefix)) {
373+
if (!this.insideFunction || !this.functionParams?.some(p => p.name === node.name)) {
374+
node.name = `${this.options.prefix}.${node.name}`;
375+
}
376+
}
377+
317378
if (context != null) {
318-
if (context[node.name] !== undefined) return context[node.name];
379+
if (typeof context?.lookup === 'function') {
380+
const value = context.lookup(node.name);
381+
if (value !== undefined) {
382+
return value;
383+
}
384+
}
385+
386+
const value = context[node.name] ?? getValue(context, node.name);
387+
if (value !== undefined) {
388+
return value;
389+
}
390+
319391
if (hasOwnProperty.call(context, node.name)) {
320392
return;
321393
}
@@ -325,6 +397,7 @@ class ExpressionSync {
325397
if (this.options.strict !== false) {
326398
throw new ReferenceError(`${node.name} is undefined`);
327399
}
400+
328401
this.state.fail = true;
329402
return ExpressionSync.FAIL;
330403
};
@@ -352,6 +425,11 @@ class ExpressionSync {
352425

353426
MemberExpression(node, context, parent) {
354427
const { computed, object, property, unset } = node;
428+
429+
if (object.name && this.options.prefix && !object.name.startsWith(this.options.prefix)) {
430+
object.name = `${this.options.prefix}.${object.name}`;
431+
}
432+
355433
const value = this.visit(object, context, node) ?? context[object.name];
356434
const data = computed ? context : value;
357435

@@ -370,7 +448,7 @@ class ExpressionSync {
370448
prop = this.visit(property, data, node);
371449
}
372450

373-
if (prop == null && property.name && data) {
451+
if (prop === undefined && property.name && data) {
374452
prop = data[property.name];
375453
}
376454

@@ -395,11 +473,14 @@ class ExpressionSync {
395473
if (type === 'SpreadElement') {
396474
Object.assign(object, this.visit(property, context, node));
397475
} else {
398-
const name = property.computed
399-
? this.visit(key, context, property)
400-
: (key.value || key.name);
476+
const name = property.computed ? this.visit(key, context, property) : key.value || key.name;
477+
const result = this.visit(value, context, property);
401478

402-
object[name] = this.visit(value, context, property);
479+
if (property.shorthand && this.options.prefix) {
480+
property.shorthand = false;
481+
}
482+
483+
object[name] = result;
403484
}
404485
}
405486

@@ -457,9 +538,9 @@ class ExpressionSync {
457538

458539
ThisExpression(node, context) {
459540
if (!context) throw new TypeError('Cannot read property "this" of undefined');
460-
if (Reflect.has(context, 'this')) {
461-
return context['this'];
462-
}
541+
542+
const thisKey = this.options.prefix ? `${this.options.prefix}.this` : 'this';
543+
return getValue(context, thisKey) ?? getValue(context, 'this');
463544
}
464545

465546
UnaryExpression(node, context) {
@@ -486,12 +567,22 @@ class ExpressionSync {
486567

487568
const update = v => {
488569
const updated = node.prefix ? this.prefix(node, v) : this.postfix(node, v);
570+
571+
const name = node.argument.name;
572+
if (name && this.options.prefix && !name.startsWith(this.options.prefix)) {
573+
node.argument.name = `${this.options.prefix}.${node.argument.name}`;
574+
}
575+
489576
context[node.argument.name] = updated;
490577
return updated;
491578
};
492579

493580
return value instanceof Promise ? value.then(obj => update(obj)) : update(value);
494581
}
582+
583+
ExpressionStatement(node, context) {
584+
return this.visit(node.expression, context, node);
585+
}
495586
}
496587

497588
module.exports = ExpressionSync;

0 commit comments

Comments
 (0)