Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

json-e

Package Overview
Dependencies
Maintainers
7
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

json-e - npm Package Compare versions

Comparing version 3.0.2 to 4.0.0

src/AST.js

2

package.json
{
"name": "json-e",
"version": "3.0.2",
"version": "4.0.0",
"description": "json parameterization module inspired from json-parameterization",

@@ -5,0 +5,0 @@ "main": "./src/index.js",

@@ -639,2 +639,20 @@ * [Full documentation](https://json-e.js.org)

Json-e supports short-circuit evaluation, so if in `||` left operand is true
returning value will be true no matter what right operand is:
```yaml
context: {}
template: {$eval: "true || b"}
result: true
```
And if in `&&` left operand is false returning value will be false no matter
what right operand is:
```yaml
context: {}
template: {$eval: "false && b"}
result: false
```
### Object Property Access

@@ -641,0 +659,0 @@

@@ -1,2 +0,4 @@

var interpreter = require('./interpreter');
const {Parser} = require('./parser');
const Tokenizer = require("../src/tokenizer");
const {Interpreter} = require('./interpreter');
var fromNow = require('./from-now');

@@ -12,2 +14,6 @@ var stringify = require('json-stable-stringify-without-jsonify');

let syntaxRuleError = (token) => {
return new SyntaxError(`Found ${token.value}, expected !=, &&, (, *, **, +, -, ., /, <, <=, ==, >, >=, [, in, ||`);
};
function checkUndefinedProperties(template, allowed) {

@@ -38,3 +44,3 @@ var unknownKeys = '';

if (remaining[offset+1] != '$') {
let v = interpreter.parseUntilTerminator(remaining.slice(offset), 2, '}', context);
let v = parseUntilTerminator(remaining.slice(offset + 2), '}', context);
if (isArray(v.result) || isObject(v.result)) {

@@ -74,3 +80,3 @@ let input = remaining.slice(offset + 2, offset + v.offset);

return interpreter.parse(template['$eval'], context);
return parse(template['$eval'], context);
};

@@ -122,7 +128,7 @@

}
if (isTruthy(interpreter.parse(template['$if'], context))) {
if (isTruthy(parse(template['$if'], context))) {
if(template.hasOwnProperty('$then')){
throw new TemplateError('$if Syntax error: $then: should be spelled then: (no $)')
}
return template.hasOwnProperty('then') ? render(template.then, context) : deleteMarker;

@@ -196,3 +202,3 @@ }

let object = isObject(value);
if (object) {

@@ -206,3 +212,3 @@ value = Object.keys(value).map(key => ({key, val: value[key]}));

throw new TemplateError(`$map on objects expects each(${x}) to evaluate to an object`);
}
}
return eachValue;

@@ -231,3 +237,3 @@ }).filter(v => v !== deleteMarker);

for (let condition of Object.keys(conditions).sort()) {
if (isTruthy(interpreter.parse(condition, context))) {
if (isTruthy(parse(condition, context))) {
result.push(render(conditions[condition], context));

@@ -315,3 +321,3 @@ }

contextClone[x] = value;
return interpreter.parse(byExpr, contextClone);
return parse(byExpr, contextClone);
};

@@ -409,2 +415,51 @@ } else {

let tokenizer = new Tokenizer({
ignore: '\\s+', // ignore all whitespace including \n
patterns: {
number: '[0-9]+(?:\\.[0-9]+)?',
identifier: '[a-zA-Z_][a-zA-Z_0-9]*',
string: '\'[^\']*\'|"[^"]*"',
// avoid matching these as prefixes of identifiers e.g., `insinutations`
true: 'true(?![a-zA-Z_0-9])',
false: 'false(?![a-zA-Z_0-9])',
in: 'in(?![a-zA-Z_0-9])',
null: 'null(?![a-zA-Z_0-9])',
},
tokens: [
'**', ...'+-*/[].(){}:,'.split(''),
'>=', '<=', '<', '>', '==', '!=', '!', '&&', '||',
'true', 'false', 'in', 'null', 'number',
'identifier', 'string',
]
});
let parse = (source, context) => {
let parser = new Parser(tokenizer, source);
let tree = parser.parse();
if (parser.current_token != null) {
throw syntaxRuleError(parser.current_token);
}
let interpreter = new Interpreter(context);
return interpreter.interpret(tree);
};
let parseUntilTerminator = (source, terminator, context) => {
let parser = new Parser(tokenizer, source);
let tree = parser.parse();
let next = parser.current_token;
if (!next) {
// string ended without the terminator
let errorLocation = source.length;
throw new SyntaxError(`Found end of string, expected ${terminator}`,
{start: errorLocation, end: errorLocation});
} else if (next.kind !== terminator) {
throw syntaxRuleError(next);
}
let interpreter = new Interpreter(context);
let result = interpreter.interpret(tree);
return {result, offset: next.start + 2};
};
module.exports = (template, context = {}) => {

@@ -411,0 +466,0 @@ let test = Object.keys(context).every(v => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v));

@@ -1,351 +0,280 @@

/*
* Author: Jonas Finnemann Jensen
* Github: https://github.com/jonasfj
*/
var PrattParser = require('./prattparser');
var {isString, isNumber, isInteger,
isArray, isObject, isFunction, isTruthy} = require('./type-utils');
var {InterpreterError} = require('./error');
const {isFunction, isObject, isString, isArray, isNumber, isInteger, isTruthy} = require("../src/type-utils");
const {InterpreterError} = require('./error');
let expectationError = (operator, expectation) => new InterpreterError(`${operator} expects ${expectation}`);
let isEqual = (a, b) => {
if (isArray(a) && isArray(b) && a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!isEqual(a[i], b[i])) { return false; }
class Interpreter {
constructor(context) {
this.context = context;
}
return true;
}
if (isFunction(a)) {
return a === b;
}
if (isObject(a) && isObject(b)) {
let keys = Object.keys(a).sort();
if (!isEqual(keys, Object.keys(b).sort())) { return false; }
for (let k of keys) {
if (!isEqual(a[k], b[k])) { return false; }
}
return true;
}
return a === b;
};
let parseList = (ctx, separator, terminator) => {
let list = [];
if (!ctx.attempt(terminator)) {
do {
list.push(ctx.parse());
} while (ctx.attempt(separator));
ctx.require(terminator);
}
return list;
};
let parseObject = (ctx) => {
let obj = {};
if (!ctx.attempt('}')) {
do {
let k = ctx.require('identifier', 'string');
if (k.kind === 'string') {
k.value = parseString(k.value);
}
ctx.require(':');
let v = ctx.parse();
obj[k.value] = v;
} while (ctx.attempt(','));
ctx.require('}');
}
return obj;
};
let parseInterval = (left, token, ctx) => {
let a = null, b = null, isInterval = false;
if (ctx.attempt(':')) {
a = 0;
isInterval = true;
} else {
a = ctx.parse();
if (ctx.attempt(':')) {
isInterval = true;
visit(node) {
let funcName = "visit_" + node.constructor.name;
return this[funcName](node);
}
}
if (isInterval && !ctx.attempt(']')) {
b = ctx.parse();
ctx.require(']');
}
if (!isInterval) {
ctx.require(']');
}
return accessProperty(left, a, b, isInterval);
};
let accessProperty = (left, a, b, isInterval) => {
if (isArray(left) || isString(left)) {
if (isInterval) {
b = b === null ? left.length : b;
if (!isInteger(a) || !isInteger(b)) {
throw new InterpreterError('cannot perform interval access with non-integers');
}
return left.slice(a, b);
visit_ASTNode(node) {
let str;
switch (node.token.kind) {
case("number"):
return +node.token.value;
case("null"):
return null;
case("string"):
str = node.token.value.slice(1, -1);
return str;
case("true"):
return true;
case("false"):
return false;
case("identifier"):
return node.token.value;
}
}
if (!isInteger(a)) {
throw new InterpreterError('should only use integers to access arrays or strings');
}
// for -ve index access
a = a < 0 ? (left.length + a) % left.length : a;
if (a >= left.length) {
throw new InterpreterError('index out of bounds');
visit_UnaryOp(node) {
let value = this.visit(node.expr);
switch (node.token.kind) {
case ("+"):
if (!isNumber(value)) {
throw expectationError('unary +', 'number');
}
return +value;
case ("-"):
if (!isNumber(value)) {
throw expectationError('unary -', 'number');
}
return -value;
case ("!"):
return !isTruthy(value)
}
}
return left[a];
}
// if we reach here it means we are accessing property value from object
if (!isObject(left)) {
throw new InterpreterError('infix: "[..]" expects object, array, or string');
}
visit_BinOp(node) {
let left = this.visit(node.left);
let right;
switch (node.token.kind) {
case ("||"):
return isTruthy(left) || isTruthy(this.visit(node.right));
case ("&&"):
return isTruthy(left) && isTruthy(this.visit(node.right));
default:
right = this.visit(node.right);
}
if (!isString(a)) {
throw new InterpreterError('object keys must be strings');
}
switch (node.token.kind) {
case ("+"):
testMathOperands("+", left, right);
return left + right;
case ("-"):
testMathOperands("-", left, right);
return left - right;
case ("/"):
testMathOperands("/", left, right);
return left / right;
case ("*"):
testMathOperands("*", left, right);
return left * right;
case (">"):
testComparisonOperands(">", left, right);
return left > right;
case ("<"):
testComparisonOperands("<", left, right);
return left < right;
case (">="):
testComparisonOperands(">=", left, right);
return left >= right;
case ("<="):
testComparisonOperands("<=", left, right);
return left <= right;
case ("!="):
testComparisonOperands("!=", left, right);
return !isEqual(left, right);
case ("=="):
testComparisonOperands("==", left, right);
return isEqual(left, right);
case ("**"):
testMathOperands("**", left, right);
return Math.pow(right, left);
case ("."): {
if (isObject(left)) {
if (left.hasOwnProperty(right)) {
return left[right];
}
throw new InterpreterError(`object has no property "${right}"`);
}
throw expectationError('infix: .', 'objects');
}
case ("in"): {
if (isObject(right)) {
if (!isString(left)) {
throw expectationError('Infix: in-object', 'string on left side');
}
right = Object.keys(right);
} else if (isString(right)) {
if (!isString(left)) {
throw expectationError('Infix: in-string', 'string on left side');
}
return right.indexOf(left) !== -1;
} else if (!isArray(right)) {
throw expectationError('Infix: in', 'Array, string, or object on right side');
}
return right.some(r => isEqual(left, r));
}
}
}
if (left.hasOwnProperty(a)) {
return left[a];
} else {
return null;
}
};
visit_List(node) {
let list = [];
let parseString = (str) => {
return str.slice(1, -1);
};
if (node.list[0] !== undefined) {
node.list.forEach(function (item) {
list.push(this.visit(item))
}, this);
}
let testComparisonOperands = (operator, left, right) => {
return list
}
if (operator === '==' || operator === '!=') {
return null;
}
visit_ValueAccess(node) {
let array = this.visit(node.arr);
let left = 0, right = null;
let test = ['>=', '<=', '<', '>'].some(v => v === operator)
&& (isNumber(left) && isNumber(right) || isString(left) && isString(right));
if (node.left) {
left = this.visit(node.left);
}
if (node.right) {
right = this.visit(node.right);
}
if (left < 0) {
left = array.length + left
}
if (isArray(array) || isString(array)) {
if (node.isInterval) {
right = right === null ? array.length : right;
if (right < 0) {
right = array.length + right;
if (right < 0)
right = 0
}
if (left > right) {
left = right
}
if (!isInteger(left) || !isInteger(right)) {
throw new InterpreterError('cannot perform interval access with non-integers');
}
return array.slice(left, right)
}
if (!isInteger(left)) {
throw new InterpreterError('should only use integers to access arrays or strings');
}
if (left >= array.length) {
throw new InterpreterError('index out of bounds');
}
return array[left]
}
if (!isObject(array)) {
throw expectationError(`infix: "[..]"`, 'object, array, or string');
}
if (!test) {
throw expectationError(`infix: ${operator}`, `numbers/strings ${operator} numbers/strings`);
}
return;
};
if (!isString(left)) {
throw new InterpreterError('object keys must be strings');
}
let testMathOperands = (operator, left, right) => {
if (operator === '+' && !(isNumber(left) && isNumber(right) || isString(left) && isString(right))) {
throw expectationError('infix: +', 'numbers/strings + numbers/strings');
}
if (['-', '*', '/', '**'].some(v => v === operator) && !(isNumber(left) && isNumber(right))) {
throw expectationError(`infix: ${operator}`, `number ${operator} number`);
}
return;
};
if (array.hasOwnProperty(left)) {
return array[left];
} else {
return null;
}
}
let prefixRules = {};
let infixRules = {};
visit_ContextValue(node) {
if (this.context.hasOwnProperty(node.token.value)) {
let contextValue = this.context[node.token.value];
return contextValue
}
throw new InterpreterError(`unknown context value ${node.token.value}`);
}
// defining prefix rules
prefixRules['number'] = (token, ctx) => {
let v = Number(token.value);
if (isNaN(v)) {
throw new Error(`${token.value} should be a number`);
}
return v;
};
visit_FunctionCall(node) {
let args = [];
prefixRules['!'] = (token, ctx) => {
let operand = ctx.parse('unary');
return !isTruthy(operand);
};
let funcName = this.visit(node.name);
if (isFunction(funcName)) {
node.args.forEach(function (item) {
args.push(this.visit(item))
}, this);
return funcName.apply(null, args);
} else {
throw new InterpreterError(`${funcName} is not callable`);
}
}
prefixRules['-'] = (token, ctx) => {
let v = ctx.parse('unary');
visit_Object(node) {
let obj = {};
if (!isNumber(v)) {
throw expectationError('unary -', 'number');
}
for (let key in node.obj) {
obj[key] = this.visit(node.obj[key])
}
return -v;
};
return obj
}
prefixRules['+'] = (token, ctx) => {
let v = ctx.parse('unary');
interpret(tree) {
return this.visit(tree);
}
}
if (!isNumber(v)) {
throw expectationError('unary +', 'number');
}
return +v;
};
prefixRules['identifier'] = (token, ctx) => {
if (ctx.context.hasOwnProperty(token.value)) {
return ctx.context[token.value];
}
throw new InterpreterError(`unknown context value ${token.value}`);
};
prefixRules['null'] = (token, ctx) => {
return null;
};
prefixRules['['] = (token, ctx) => parseList(ctx, ',', ']');
prefixRules['('] = (token, ctx) => {
let v = ctx.parse();
ctx.require(')');
return v;
};
prefixRules['{'] = (token, ctx) => parseObject(ctx);
prefixRules['string'] = (token, ctx) => parseString(token.value);
prefixRules['true'] = (token, ctx) => {
if (token.value === 'true') {
return true;
}
throw new Error('Only \'true/false\' is considered as bool');
};
prefixRules['false'] = (token, ctx) => {
if (token.value === 'false') {
return false;
}
throw new Error('Only \'true/false\' is considered as bool');
};
// infix rule definition starts here
infixRules['+'] = infixRules['-'] = infixRules['*'] = infixRules['/']
= (left, token, ctx) => {
let operator = token.kind;
let right = ctx.parse(operator);
testMathOperands(operator, left, right);
switch (operator) {
case '+': return left + right;
case '-': return left - right;
case '*': return left * right;
case '/': return left / right;
default: throw new Error(`unknown infix operator: '${operator}'`);
let isEqual = (a, b) => {
if (isArray(a) && isArray(b) && a.length === b.length) {
for (let i = 0; i < a.length; i++) {
if (!isEqual(a[i], b[i])) {
return false;
}
}
return true;
}
};
infixRules['**'] = (left, token, ctx) => {
let operator = token.kind;
let right = ctx.parse('**-right-associative');
testMathOperands(operator, left, right);
if (typeof left !== typeof right) {
throw new InterpreterError(`TypeError: ${typeof left} ${operator} ${typeof right}`);
}
return Math.pow(left, right);
if (isFunction(a)) {
return a === b;
}
if (isObject(a) && isObject(b)) {
let keys = Object.keys(a).sort();
if (!isEqual(keys, Object.keys(b).sort())) {
return false;
}
for (let k of keys) {
if (!isEqual(a[k], b[k])) {
return false;
}
}
return true;
}
return a === b;
};
infixRules['['] = (left, token, ctx) => parseInterval(left, token, ctx);
infixRules['.'] = (left, token, ctx) => {
if (isObject(left)) {
let key = ctx.require('identifier').value;
if (left.hasOwnProperty(key)) {
return left[key];
let testMathOperands = (operator, left, right) => {
if (operator === '+' && !(isNumber(left) && isNumber(right) || isString(left) && isString(right))) {
throw expectationError('infix: +', 'numbers/strings + numbers/strings');
}
throw new InterpreterError(`object has no property "${key}"`);
}
throw expectationError('infix: .', 'objects');
if (['-', '*', '/', '**'].some(v => v === operator) && !(isNumber(left) && isNumber(right))) {
throw expectationError(`infix: ${operator}`, `number ${operator} number`);
}
return
};
infixRules['('] = (left, token, ctx) => {
if (isFunction(left)) {
return left.apply(null, parseList(ctx, ',', ')'));
}
throw expectationError('infix: f(args)', 'f to be function');
};
infixRules['=='] = infixRules['!='] = infixRules['<='] =
infixRules['>='] = infixRules['<'] = infixRules['>']
= (left, token, ctx) => {
let operator = token.kind;
let right = ctx.parse(operator);
testComparisonOperands(operator, left, right);
switch (operator) {
case '>=': return left >= right;
case '<=': return left <= right;
case '>': return left > right;
case '<': return left < right;
case '==': return isEqual(left, right);
case '!=': return !isEqual(left, right);
default: throw new Error('no rule for comparison operator: ' + operator);
let testComparisonOperands = (operator, left, right) => {
if (operator === '==' || operator === '!=') {
return null;
}
};
infixRules['||'] = infixRules['&&'] = (left, token, ctx) => {
let operator = token.kind;
let right = ctx.parse(operator);
switch (operator) {
case '||': return isTruthy(left) || isTruthy(right);
case '&&': return isTruthy(left) && isTruthy(right);
default: throw new Error('no rule for boolean operator: ' + operator);
}
};
let test = ['>=', '<=', '<', '>'].some(v => v === operator)
&& (isNumber(left) && isNumber(right) || isString(left) && isString(right));
infixRules['in'] = (left, token, ctx) => {
let right = ctx.parse(token.kind);
if (isObject(right)) {
if (!isString(left)) {
throw expectationError('Infix: in-object', 'string on left side');
if (!test) {
throw expectationError(`infix: ${operator}`, `numbers/strings ${operator} numbers/strings`);
}
right = Object.keys(right);
} else if (isString(right)) {
if (!isString(left)) {
throw expectationError('Infix: in-string', 'string on left side');
}
// short-circuit to indexOf since this is a substring operation
return right.indexOf(left) !== -1;
} else if (!isArray(right)) {
throw expectationError('Infix: in', 'Array, string, or object on right side');
}
return right.some(r => isEqual(left, r));
return
};
module.exports = new PrattParser({
ignore: '\\s+', // ignore all whitespace including \n
patterns: {
number: '[0-9]+(?:\\.[0-9]+)?',
identifier: '[a-zA-Z_][a-zA-Z_0-9]*',
string: '\'[^\']*\'|"[^"]*"',
// avoid matching these as prefixes of identifiers e.g., `insinutations`
true: 'true(?![a-zA-Z_0-9])',
false: 'false(?![a-zA-Z_0-9])',
in: 'in(?![a-zA-Z_0-9])',
null: 'null(?![a-zA-Z_0-9])',
},
tokens: [
'**', ...'+-*/[].(){}:,'.split(''),
'>=', '<=', '<', '>', '==', '!=', '!', '&&', '||',
'true', 'false', 'in', 'null', 'number',
'identifier', 'string',
],
precedence: [
['||'],
['&&'],
['in'],
['==', '!='],
['>=', '<=', '<', '>'],
['+', '-'],
['*', '/'],
['**-right-associative'],
['**'],
['[', '.'],
['('],
['unary'],
],
prefixRules,
infixRules,
});
exports
.Interpreter = Interpreter;

@@ -76,3 +76,3 @@ var {SyntaxError} = require('./error');

if (source.slice(offset) !== '') {
throw new SyntaxError(`unexpected EOF for '${source}' at '${source.slice(offset)}'`,
throw new SyntaxError(`Unexpected input for '${source}' at '${source.slice(offset)}'`,
{start: offset, end: source.length});

@@ -79,0 +79,0 @@ }

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc