New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

array-expression

Package Overview
Dependencies
Maintainers
1
Versions
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

array-expression - npm Package Compare versions

Comparing version
0.0.1
to
0.0.2
+2
dist/evaluator.d.ts
import type { Data, Expression, Value } from "./interface";
export declare const evalVal: (value: Value, data?: Data) => string | number | boolean | Expression;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evalVal = void 0;
const decision_1 = require("./functions/decision");
const math_1 = require("./functions/math");
const string_1 = require("./functions/string");
const type_1 = require("./functions/type");
const utils_1 = require("./utils");
// prettier-ignore
const FUNCTIONS = {
"boolean": type_1.bool,
"string": type_1.str,
"+": math_1.add,
"-": math_1.subtract,
"*": math_1.multiply,
"/": math_1.divide,
"%": math_1.remainder,
"^": math_1.power,
"sqrt": math_1.sqrt,
"abs": math_1.abs,
"sin": math_1.sin,
"cos": math_1.cos,
"tan": math_1.tan,
"log": math_1.log,
"floor": math_1.floor,
"ceil": math_1.ceil,
"round": math_1.round,
"min": math_1.min,
"max": math_1.max,
"!": decision_1.negate,
"==": decision_1.equalTo,
"!=": decision_1.notEqualTo,
"<": decision_1.lessThan,
"<=": decision_1.lessThanOrEqual,
">": decision_1.greaterThan,
">=": decision_1.greaterThanOrEqual,
"all": decision_1.all,
"any": decision_1.any,
"if": decision_1.ifElse,
"concat": string_1.concat,
"downcase": string_1.downcase,
"upcase": string_1.upcase,
"number-format": string_1.numberFormat
};
const evalVal = (value, data) => {
if ((0, utils_1.isConstant)(value)) {
if (data && typeof value === "string") {
const wildcardRegex = /^\$(\w+)$/;
const match = wildcardRegex.exec(value);
if (match && match[1]) {
return data[match[1]];
}
}
return value;
}
if ((0, utils_1.isExpression)(value))
return evalExp(value, data);
throw new Error("Value is not constant or expression");
};
exports.evalVal = evalVal;
const evalExp = (expression, data) => {
const [name, ...args] = expression;
const func = FUNCTIONS[name];
if (!func)
throw new Error("Invalid expression");
return func(...args.map((arg) => (0, exports.evalVal)(arg, data)));
};
import type { Constant } from '../interface';
export declare const negate: (value: Constant) => boolean;
export declare const equalTo: (a: Constant, b: Constant) => boolean;
export declare const notEqualTo: (a: Constant, b: Constant) => boolean;
export declare const lessThan: (a: string | number, b: string | number) => boolean;
export declare const lessThanOrEqual: (a: string | number, b: string | number) => boolean;
export declare const greaterThan: (a: string | number, b: string | number) => boolean;
export declare const greaterThanOrEqual: (a: string | number, b: string | number) => boolean;
export declare const all: (...values: Constant[]) => boolean;
export declare const any: (...values: Constant[]) => boolean;
export declare const ifElse: (...args: Constant[]) => Constant;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ifElse = exports.any = exports.all = exports.greaterThanOrEqual = exports.greaterThan = exports.lessThanOrEqual = exports.lessThan = exports.notEqualTo = exports.equalTo = exports.negate = void 0;
const negate = (value) => !value;
exports.negate = negate;
const equalTo = (a, b) => a === b;
exports.equalTo = equalTo;
const notEqualTo = (a, b) => a !== b;
exports.notEqualTo = notEqualTo;
const lessThan = (a, b) => a < b;
exports.lessThan = lessThan;
const lessThanOrEqual = (a, b) => a <= b;
exports.lessThanOrEqual = lessThanOrEqual;
const greaterThan = (a, b) => a > b;
exports.greaterThan = greaterThan;
const greaterThanOrEqual = (a, b) => a >= b;
exports.greaterThanOrEqual = greaterThanOrEqual;
const all = (...values) => values.reduce((acc, curr) => acc && !!curr, true);
exports.all = all;
const any = (...values) => values.reduce((acc, curr) => acc || !!curr, false);
exports.any = any;
const ifElse = (...args) => {
if (args.length < 3 || args.length % 2 !== 1)
throw new Error("Invalid arguments for 'if' expression");
let argIndex = 0;
while (argIndex < args.length - 1) {
const condition = args[argIndex];
const output = args[argIndex + 1];
if (condition) {
return output;
}
argIndex += 2;
}
return args[args.length - 1];
};
exports.ifElse = ifElse;
export declare const add: (a: number, b: number) => number;
export declare const subtract: (a: number, b: number) => number;
export declare const multiply: (a: number, b: number) => number;
export declare const divide: (a: number, b: number) => number;
export declare const remainder: (a: number, b: number) => number;
export declare const power: (a: number, b: number) => number;
export declare const sqrt: (value: number) => number;
export declare const abs: (value: number) => number;
export declare const sin: (value: number) => number;
export declare const cos: (value: number) => number;
export declare const tan: (value: number) => number;
export declare const log: (value: number) => number;
export declare const floor: (value: number) => number;
export declare const ceil: (value: number) => number;
export declare const round: (value: number) => number;
export declare const min: (...values: number[]) => number;
export declare const max: (...values: number[]) => number;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.max = exports.min = exports.round = exports.ceil = exports.floor = exports.log = exports.tan = exports.cos = exports.sin = exports.abs = exports.sqrt = exports.power = exports.remainder = exports.divide = exports.multiply = exports.subtract = exports.add = void 0;
const add = (a, b) => a + b;
exports.add = add;
const subtract = (a, b) => a - b;
exports.subtract = subtract;
const multiply = (a, b) => a * b;
exports.multiply = multiply;
const divide = (a, b) => a / b;
exports.divide = divide;
const remainder = (a, b) => a % b;
exports.remainder = remainder;
const power = (a, b) => Math.pow(a, b);
exports.power = power;
const sqrt = (value) => Math.sqrt(value);
exports.sqrt = sqrt;
const abs = (value) => Math.abs(value);
exports.abs = abs;
const sin = (value) => Math.sin(value);
exports.sin = sin;
const cos = (value) => Math.cos(value);
exports.cos = cos;
const tan = (value) => Math.tan(value);
exports.tan = tan;
const log = (value) => Math.log(value);
exports.log = log;
const floor = (value) => Math.floor(value);
exports.floor = floor;
const ceil = (value) => Math.ceil(value);
exports.ceil = ceil;
const round = (value) => Math.round(value);
exports.round = round;
const min = (...values) => Math.min(...values);
exports.min = min;
const max = (...values) => Math.max(...values);
exports.max = max;
import type { Constant } from "../interface";
export declare const concat: (...args: Constant[]) => string;
export declare const downcase: (str: string) => string;
export declare const upcase: (str: string) => string;
export declare const numberFormat: (number: number, minimumFractionDigits: number, maximumFractionDigits?: number) => string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.numberFormat = exports.upcase = exports.downcase = exports.concat = void 0;
const concat = (...args) => args.join("");
exports.concat = concat;
const downcase = (str) => str.toLowerCase();
exports.downcase = downcase;
const upcase = (str) => str.toUpperCase();
exports.upcase = upcase;
const numberFormat = (number, minimumFractionDigits, maximumFractionDigits) => {
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits,
maximumFractionDigits,
});
return formatter.format(number);
};
exports.numberFormat = numberFormat;
import type { Constant } from "../interface";
export declare const bool: (value: Constant) => boolean;
export declare const str: (value: Constant) => string;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.str = exports.bool = void 0;
const bool = (value) => !!value;
exports.bool = bool;
const str = (value) => String(value);
exports.str = str;
export type { Constant, Data, Expression, Value } from "./interface";
export { evalVal as exp } from "./evaluator";
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.exp = void 0;
var evaluator_1 = require("./evaluator");
Object.defineProperty(exports, "exp", { enumerable: true, get: function () { return evaluator_1.evalVal; } });
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const _1 = require(".");
describe("type", () => {
test("boolean", () => {
expect((0, _1.exp)(["boolean", 1])).toBe(true);
expect((0, _1.exp)(["boolean", 0])).toBe(false);
expect((0, _1.exp)(["boolean", "a"])).toBe(true);
expect((0, _1.exp)(["boolean", ""])).toBe(false);
expect((0, _1.exp)(["boolean", true])).toBe(true);
expect((0, _1.exp)(["boolean", false])).toBe(false);
});
test("string", () => {
expect((0, _1.exp)(["string", 1])).toBe("1");
expect((0, _1.exp)(["string", 1.5])).toBe("1.5");
expect((0, _1.exp)(["string", true])).toBe("true");
});
});
describe("math", () => {
test("+", () => {
expect((0, _1.exp)(["+", 1, 2])).toBe(3);
});
test("-", () => {
expect((0, _1.exp)(["-", 3, 1])).toBe(2);
});
test("*", () => {
expect((0, _1.exp)(["*", 3, 5])).toBe(15);
});
test("/", () => {
expect((0, _1.exp)(["/", 15, 3])).toBe(5);
});
test("%", () => {
expect((0, _1.exp)(["%", 15, 3])).toBe(0);
expect((0, _1.exp)(["%", 15, 2])).toBe(1);
expect((0, _1.exp)(["%", 2, 3])).toBe(2);
});
test("^", () => {
expect((0, _1.exp)(["^", 2, 3])).toBe(8);
});
test("sqrt", () => {
expect((0, _1.exp)(["sqrt", 9])).toBe(3);
});
test("abs", () => {
expect((0, _1.exp)(["abs", -5])).toBe(5);
});
test("sin", () => {
expect((0, _1.exp)(["sin", Math.PI / 2])).toBe(1);
});
test("cos", () => {
expect((0, _1.exp)(["cos", 0])).toBe(1);
});
test("tan", () => {
expect((0, _1.exp)(["tan", 0])).toBe(0);
});
test("log", () => {
expect((0, _1.exp)(["log", 1])).toBe(0);
expect((0, _1.exp)(["log", 30])).toBe(Math.log(30));
});
test("floor", () => {
expect((0, _1.exp)(["floor", 1.5])).toBe(1);
expect((0, _1.exp)(["floor", -1.5])).toBe(-2);
});
test("ceil", () => {
expect((0, _1.exp)(["ceil", 1.5])).toBe(2);
expect((0, _1.exp)(["ceil", -1.5])).toBe(-1);
});
test("round", () => {
expect((0, _1.exp)(["round", 1.6])).toBe(2);
expect((0, _1.exp)(["round", 1.4])).toBe(1);
});
test("min", () => {
expect((0, _1.exp)(["min", 1, 2])).toBe(1);
expect((0, _1.exp)(["min", 4, 2, 3])).toBe(2);
});
test("max", () => {
expect((0, _1.exp)(["max", 1, 2])).toBe(2);
expect((0, _1.exp)(["max", 4, 2, 3])).toBe(4);
});
});
describe("decision", () => {
test("!", () => {
expect((0, _1.exp)(["!", true])).toBe(false);
expect((0, _1.exp)(["!", false])).toBe(true);
expect((0, _1.exp)(["!", 1])).toBe(false);
expect((0, _1.exp)(["!", 0])).toBe(true);
expect((0, _1.exp)(["!", "a"])).toBe(false);
expect((0, _1.exp)(["!", ""])).toBe(true);
});
test("==", () => {
expect((0, _1.exp)(["==", 1, 1])).toBe(true);
expect((0, _1.exp)(["==", 1, 2])).toBe(false);
expect((0, _1.exp)(["==", 1, "1"])).toBe(false);
expect((0, _1.exp)(["==", "a", "a"])).toBe(true);
expect((0, _1.exp)(["==", true, true])).toBe(true);
expect((0, _1.exp)(["==", false, false])).toBe(true);
expect((0, _1.exp)(["==", true, false])).toBe(false);
});
test("!=", () => {
expect((0, _1.exp)(["!=", 1, 1])).toBe(false);
expect((0, _1.exp)(["!=", 1, 2])).toBe(true);
expect((0, _1.exp)(["!=", 1, "1"])).toBe(true);
expect((0, _1.exp)(["!=", "a", "a"])).toBe(false);
expect((0, _1.exp)(["!=", true, true])).toBe(false);
expect((0, _1.exp)(["!=", false, false])).toBe(false);
expect((0, _1.exp)(["!=", true, false])).toBe(true);
});
test("<", () => {
expect((0, _1.exp)(["<", 1, 2])).toBe(true);
expect((0, _1.exp)(["<", 2, 1])).toBe(false);
expect((0, _1.exp)(["<", 1, 1])).toBe(false);
expect((0, _1.exp)(["<", "a", "b"])).toBe(true);
expect((0, _1.exp)(["<", "b", "a"])).toBe(false);
expect((0, _1.exp)(["<", "a", "a"])).toBe(false);
});
test("<=", () => {
expect((0, _1.exp)(["<=", 1, 2])).toBe(true);
expect((0, _1.exp)(["<=", 2, 1])).toBe(false);
expect((0, _1.exp)(["<=", 1, 1])).toBe(true);
expect((0, _1.exp)(["<=", "a", "b"])).toBe(true);
expect((0, _1.exp)(["<=", "b", "a"])).toBe(false);
expect((0, _1.exp)(["<=", "a", "a"])).toBe(true);
});
test(">", () => {
expect((0, _1.exp)([">", 1, 2])).toBe(false);
expect((0, _1.exp)([">", 2, 1])).toBe(true);
expect((0, _1.exp)([">", 1, 1])).toBe(false);
expect((0, _1.exp)([">", "a", "b"])).toBe(false);
expect((0, _1.exp)([">", "b", "a"])).toBe(true);
expect((0, _1.exp)([">", "a", "a"])).toBe(false);
});
test(">=", () => {
expect((0, _1.exp)([">=", 1, 2])).toBe(false);
expect((0, _1.exp)([">=", 2, 1])).toBe(true);
expect((0, _1.exp)([">=", 1, 1])).toBe(true);
expect((0, _1.exp)([">=", "a", "b"])).toBe(false);
expect((0, _1.exp)([">=", "b", "a"])).toBe(true);
expect((0, _1.exp)([">=", "a", "a"])).toBe(true);
});
test("all", () => {
expect((0, _1.exp)(["all", 1, 2, 3, 4, 5])).toBe(true);
expect((0, _1.exp)(["all", 1, 2, 3, 0, 4, 5])).toBe(false);
expect((0, _1.exp)(["all", "a", "b", 1, "c", "d"])).toBe(true);
expect((0, _1.exp)(["all", "a", "b", 1, "", "d"])).toBe(false);
expect((0, _1.exp)(["all", "a", "b", 1, true, "d"])).toBe(true);
expect((0, _1.exp)(["all", "a", "b", 1, false, "d"])).toBe(false);
});
test("any", () => {
expect((0, _1.exp)(["any", 0, 0, 0, 0, 1, 0])).toBe(true);
expect((0, _1.exp)(["any", 0, 0, 0, 0, 0, 0])).toBe(false);
expect((0, _1.exp)(["any", "", "", 1, "", ""])).toBe(true);
expect((0, _1.exp)(["any", "", "", "", "", ""])).toBe(false);
expect((0, _1.exp)(["any", "", "", 1, true, ""])).toBe(true);
expect((0, _1.exp)(["any", "", "", 0, false, ""])).toBe(false);
});
test("if", () => {
expect((0, _1.exp)(["if", true, "success", "failure"])).toBe("success");
expect((0, _1.exp)(["if", false, "success", "failure"])).toBe("failure");
expect((0, _1.exp)(["if", 1, "success", "failure"])).toBe("success");
expect((0, _1.exp)(["if", 0, "success", "failure"])).toBe("failure");
expect((0, _1.exp)(["if", "a", "success", "failure"])).toBe("success");
expect((0, _1.exp)(["if", "", "success", "failure"])).toBe("failure");
expect((0, _1.exp)([
"if",
["==", 5, 1],
"equal to one",
["==", 2, 2],
"equal to two",
"failure",
])).toBe("equal to two");
expect((0, _1.exp)([
"if",
["==", 5, 1],
"equal to one",
["==", 3, 2],
"equal to two",
"failure",
])).toBe("failure");
});
});
describe("decision", () => {
test("concat", () => {
expect((0, _1.exp)(["concat", "Edward", " ", "Anthony"])).toBe("Edward Anthony");
expect((0, _1.exp)(["concat", 1, "pm"])).toBe("1pm");
});
test("downcase", () => {
expect((0, _1.exp)(["downcase", "PM"])).toBe("pm");
expect((0, _1.exp)(["downcase", "pm"])).toBe("pm");
});
test("upcase", () => {
expect((0, _1.exp)(["upcase", "pm"])).toBe("PM");
expect((0, _1.exp)(["upcase", "PM"])).toBe("PM");
});
test("number-format", () => {
expect((0, _1.exp)(["number-format", 3.1415926535, 1, 3])).toBe("3.142");
expect((0, _1.exp)(["number-format", 3, 2, 5])).toBe("3.00");
});
});
describe("nested", () => {
test("convert liter/minute to US gallon/hour", () => {
const literPerMinute = 5;
expect((0, _1.exp)([
"concat",
["*", ["*", literPerMinute, 0.2641722], 60],
" ",
"gallon/hour",
])).toBe("79.25166 gallon/hour");
});
test("nested conditional", () => {
const getExpression = (animal) => [
"if",
["==", animal, "cat"],
"meow",
["==", animal, "dog"],
"woof",
"Wa-pa-pa-pa-pa-pa-pow!",
];
expect((0, _1.exp)(getExpression("cat"))).toBe("meow");
expect((0, _1.exp)(getExpression("dog"))).toBe("woof");
expect((0, _1.exp)(getExpression("fox"))).toBe("Wa-pa-pa-pa-pa-pa-pow!");
});
test("wildcard", () => {
const data = {
value: 5,
};
expect((0, _1.exp)([
"concat",
["number-format", ["*", ["*", "$value", 0.2641722], 60], 0, 1],
" ",
"gallon/hour",
], data)).toBe("79.3 gallon/hour");
const expression = [
"if",
["==", "$animal", "cat"],
"meow",
["==", "$animal", "dog"],
"woof",
"Wa-pa-pa-pa-pa-pa-pow!",
];
expect((0, _1.exp)(expression, { animal: "cat" })).toBe("meow");
expect((0, _1.exp)(expression, { animal: "dog" })).toBe("woof");
expect((0, _1.exp)(expression, { animal: "fox" })).toBe("Wa-pa-pa-pa-pa-pa-pow!");
});
});
export type Constant = string | number | boolean;
export type Expression = [string, Value, ...Value[]];
export type Value = Expression | Constant;
export type Data = {
[s: string]: Constant;
};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
import type { Constant, Expression, Value } from "./interface";
export declare const isExpression: (value: Value) => value is Expression;
export declare const isConstant: (value: Value) => value is Constant;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isConstant = exports.isExpression = void 0;
const isExpression = (value) => {
return (Array.isArray(value) && value.length >= 2 && typeof value[0] === "string");
};
exports.isExpression = isExpression;
const isConstant = (value) => {
return ["string", "number", "boolean"].includes(typeof value);
};
exports.isConstant = isConstant;
+6
-1
{
"name": "array-expression",
"version": "0.0.1",
"version": "0.0.2",
"description": "Formatter declaration that can be transported through network and executed securely.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"prebuild": "rimraf dist",
"dev": "npm run prebuild && tsc -w",
"test": "jest"

@@ -26,2 +30,3 @@ },

"jest": "^29.3.1",
"rimraf": "^5.0.5",
"ts-jest": "^29.0.5",

@@ -28,0 +33,0 @@ "ts-node": "^10.9.1",

-16
{
"arrowParens": "always",
"bracketSpacing": true,
"htmlWhitespaceSensitivity": "ignore",
"insertPragma": false,
"singleQuote": true,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"endOfLine": "auto",
"printWidth": 100
}
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
export default {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/tp/_0j13b5s4vbbzfm8bcj8flzr0000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
import {
all,
any,
equalTo,
greaterThan,
greaterThanOrEqual,
ifElse,
lessThan,
lessThanOrEqual,
negate,
notEqualTo,
} from "./functions/decision";
import {
abs,
add,
ceil,
cos,
divide,
floor,
log,
max,
min,
multiply,
power,
remainder,
round,
sin,
sqrt,
subtract,
tan,
} from "./functions/math";
import { concat, downcase, numberFormat, upcase } from "./functions/string";
import { bool, str } from "./functions/type";
import type { Data, Expression, Value } from "./interface";
import { isConstant, isExpression } from "./utils";
// prettier-ignore
const FUNCTIONS: Record<string, Function> = {
"boolean": bool,
"string": str,
"+": add,
"-": subtract,
"*": multiply,
"/": divide,
"%": remainder,
"^": power,
"sqrt": sqrt,
"abs": abs,
"sin": sin,
"cos": cos,
"tan": tan,
"log": log,
"floor": floor,
"ceil": ceil,
"round": round,
"min": min,
"max": max,
"!": negate,
"==": equalTo,
"!=": notEqualTo,
"<": lessThan,
"<=": lessThanOrEqual,
">": greaterThan,
">=": greaterThanOrEqual,
"all": all,
"any": any,
"if": ifElse,
"concat": concat,
"downcase": downcase,
"upcase": upcase,
"number-format": numberFormat
};
export const evalVal = (value: Value, data?: Data) => {
if (isConstant(value)) {
if (data && typeof value === "string") {
const wildcardRegex = /^\$(\w+)$/;
const match = wildcardRegex.exec(value);
if (match && match[1]) {
return data[match[1]];
}
}
return value;
}
if (isExpression(value)) return evalExp(value, data);
throw new Error("Value is not constant or expression");
};
const evalExp = (expression: Expression, data?: Data): Value => {
const [name, ...args] = expression;
const func = FUNCTIONS[name];
if (!func) throw new Error("Invalid expression");
return func(...args.map((arg) => evalVal(arg, data)));
};
import type { Constant } from '../interface';
export const negate = (value: Constant) => !value;
export const equalTo = (a: Constant, b: Constant) => a === b;
export const notEqualTo = (a: Constant, b: Constant) => a !== b;
export const lessThan = (a: string | number, b: string | number) => a < b;
export const lessThanOrEqual = (a: string | number, b: string | number) => a <= b;
export const greaterThan = (a: string | number, b: string | number) => a > b;
export const greaterThanOrEqual = (a: string | number, b: string | number) => a >= b;
export const all = (...values: Constant[]) =>
values.reduce<boolean>((acc, curr) => acc && !!curr, true);
export const any = (...values: Constant[]) =>
values.reduce<boolean>((acc, curr) => acc || !!curr, false);
export const ifElse = (...args: Constant[]) => {
if (args.length < 3 || args.length % 2 !== 1)
throw new Error("Invalid arguments for 'if' expression");
let argIndex = 0;
while (argIndex < args.length - 1) {
const condition = args[argIndex];
const output = args[argIndex + 1];
if (condition) {
return output;
}
argIndex += 2;
}
return args[args.length - 1];
};
export const add = (a: number, b: number) => a + b;
export const subtract = (a: number, b: number) => a - b;
export const multiply = (a: number, b: number) => a * b;
export const divide = (a: number, b: number) => a / b;
export const remainder = (a: number, b: number) => a % b;
export const power = (a: number, b: number) => Math.pow(a, b);
export const sqrt = (value: number) => Math.sqrt(value);
export const abs = (value: number) => Math.abs(value);
export const sin = (value: number) => Math.sin(value);
export const cos = (value: number) => Math.cos(value);
export const tan = (value: number) => Math.tan(value);
export const log = (value: number) => Math.log(value);
export const floor = (value: number) => Math.floor(value);
export const ceil = (value: number) => Math.ceil(value);
export const round = (value: number) => Math.round(value);
export const min = (...values: number[]) => Math.min(...values);
export const max = (...values: number[]) => Math.max(...values);
import type { Constant } from "../interface";
export const concat = (...args: Constant[]) => args.join("");
export const downcase = (str: string) => str.toLowerCase();
export const upcase = (str: string) => str.toUpperCase();
export const numberFormat = (
number: number,
minimumFractionDigits: number,
maximumFractionDigits?: number
) => {
const formatter = new Intl.NumberFormat("en-US", {
minimumFractionDigits,
maximumFractionDigits,
});
return formatter.format(number);
};
import type { Constant } from "../interface";
export const bool = (value: Constant) => !!value;
export const str = (value: Constant) => String(value);
import { Data, exp, Expression } from ".";
describe("type", () => {
test("boolean", () => {
expect(exp(["boolean", 1])).toBe(true);
expect(exp(["boolean", 0])).toBe(false);
expect(exp(["boolean", "a"])).toBe(true);
expect(exp(["boolean", ""])).toBe(false);
expect(exp(["boolean", true])).toBe(true);
expect(exp(["boolean", false])).toBe(false);
});
test("string", () => {
expect(exp(["string", 1])).toBe("1");
expect(exp(["string", 1.5])).toBe("1.5");
expect(exp(["string", true])).toBe("true");
});
});
describe("math", () => {
test("+", () => {
expect(exp(["+", 1, 2])).toBe(3);
});
test("-", () => {
expect(exp(["-", 3, 1])).toBe(2);
});
test("*", () => {
expect(exp(["*", 3, 5])).toBe(15);
});
test("/", () => {
expect(exp(["/", 15, 3])).toBe(5);
});
test("%", () => {
expect(exp(["%", 15, 3])).toBe(0);
expect(exp(["%", 15, 2])).toBe(1);
expect(exp(["%", 2, 3])).toBe(2);
});
test("^", () => {
expect(exp(["^", 2, 3])).toBe(8);
});
test("sqrt", () => {
expect(exp(["sqrt", 9])).toBe(3);
});
test("abs", () => {
expect(exp(["abs", -5])).toBe(5);
});
test("sin", () => {
expect(exp(["sin", Math.PI / 2])).toBe(1);
});
test("cos", () => {
expect(exp(["cos", 0])).toBe(1);
});
test("tan", () => {
expect(exp(["tan", 0])).toBe(0);
});
test("log", () => {
expect(exp(["log", 1])).toBe(0);
expect(exp(["log", 30])).toBe(Math.log(30));
});
test("floor", () => {
expect(exp(["floor", 1.5])).toBe(1);
expect(exp(["floor", -1.5])).toBe(-2);
});
test("ceil", () => {
expect(exp(["ceil", 1.5])).toBe(2);
expect(exp(["ceil", -1.5])).toBe(-1);
});
test("round", () => {
expect(exp(["round", 1.6])).toBe(2);
expect(exp(["round", 1.4])).toBe(1);
});
test("min", () => {
expect(exp(["min", 1, 2])).toBe(1);
expect(exp(["min", 4, 2, 3])).toBe(2);
});
test("max", () => {
expect(exp(["max", 1, 2])).toBe(2);
expect(exp(["max", 4, 2, 3])).toBe(4);
});
});
describe("decision", () => {
test("!", () => {
expect(exp(["!", true])).toBe(false);
expect(exp(["!", false])).toBe(true);
expect(exp(["!", 1])).toBe(false);
expect(exp(["!", 0])).toBe(true);
expect(exp(["!", "a"])).toBe(false);
expect(exp(["!", ""])).toBe(true);
});
test("==", () => {
expect(exp(["==", 1, 1])).toBe(true);
expect(exp(["==", 1, 2])).toBe(false);
expect(exp(["==", 1, "1"])).toBe(false);
expect(exp(["==", "a", "a"])).toBe(true);
expect(exp(["==", true, true])).toBe(true);
expect(exp(["==", false, false])).toBe(true);
expect(exp(["==", true, false])).toBe(false);
});
test("!=", () => {
expect(exp(["!=", 1, 1])).toBe(false);
expect(exp(["!=", 1, 2])).toBe(true);
expect(exp(["!=", 1, "1"])).toBe(true);
expect(exp(["!=", "a", "a"])).toBe(false);
expect(exp(["!=", true, true])).toBe(false);
expect(exp(["!=", false, false])).toBe(false);
expect(exp(["!=", true, false])).toBe(true);
});
test("<", () => {
expect(exp(["<", 1, 2])).toBe(true);
expect(exp(["<", 2, 1])).toBe(false);
expect(exp(["<", 1, 1])).toBe(false);
expect(exp(["<", "a", "b"])).toBe(true);
expect(exp(["<", "b", "a"])).toBe(false);
expect(exp(["<", "a", "a"])).toBe(false);
});
test("<=", () => {
expect(exp(["<=", 1, 2])).toBe(true);
expect(exp(["<=", 2, 1])).toBe(false);
expect(exp(["<=", 1, 1])).toBe(true);
expect(exp(["<=", "a", "b"])).toBe(true);
expect(exp(["<=", "b", "a"])).toBe(false);
expect(exp(["<=", "a", "a"])).toBe(true);
});
test(">", () => {
expect(exp([">", 1, 2])).toBe(false);
expect(exp([">", 2, 1])).toBe(true);
expect(exp([">", 1, 1])).toBe(false);
expect(exp([">", "a", "b"])).toBe(false);
expect(exp([">", "b", "a"])).toBe(true);
expect(exp([">", "a", "a"])).toBe(false);
});
test(">=", () => {
expect(exp([">=", 1, 2])).toBe(false);
expect(exp([">=", 2, 1])).toBe(true);
expect(exp([">=", 1, 1])).toBe(true);
expect(exp([">=", "a", "b"])).toBe(false);
expect(exp([">=", "b", "a"])).toBe(true);
expect(exp([">=", "a", "a"])).toBe(true);
});
test("all", () => {
expect(exp(["all", 1, 2, 3, 4, 5])).toBe(true);
expect(exp(["all", 1, 2, 3, 0, 4, 5])).toBe(false);
expect(exp(["all", "a", "b", 1, "c", "d"])).toBe(true);
expect(exp(["all", "a", "b", 1, "", "d"])).toBe(false);
expect(exp(["all", "a", "b", 1, true, "d"])).toBe(true);
expect(exp(["all", "a", "b", 1, false, "d"])).toBe(false);
});
test("any", () => {
expect(exp(["any", 0, 0, 0, 0, 1, 0])).toBe(true);
expect(exp(["any", 0, 0, 0, 0, 0, 0])).toBe(false);
expect(exp(["any", "", "", 1, "", ""])).toBe(true);
expect(exp(["any", "", "", "", "", ""])).toBe(false);
expect(exp(["any", "", "", 1, true, ""])).toBe(true);
expect(exp(["any", "", "", 0, false, ""])).toBe(false);
});
test("if", () => {
expect(exp(["if", true, "success", "failure"])).toBe("success");
expect(exp(["if", false, "success", "failure"])).toBe("failure");
expect(exp(["if", 1, "success", "failure"])).toBe("success");
expect(exp(["if", 0, "success", "failure"])).toBe("failure");
expect(exp(["if", "a", "success", "failure"])).toBe("success");
expect(exp(["if", "", "success", "failure"])).toBe("failure");
expect(
exp([
"if",
["==", 5, 1],
"equal to one",
["==", 2, 2],
"equal to two",
"failure",
])
).toBe("equal to two");
expect(
exp([
"if",
["==", 5, 1],
"equal to one",
["==", 3, 2],
"equal to two",
"failure",
])
).toBe("failure");
});
});
describe("decision", () => {
test("concat", () => {
expect(exp(["concat", "Edward", " ", "Anthony"])).toBe("Edward Anthony");
expect(exp(["concat", 1, "pm"])).toBe("1pm");
});
test("downcase", () => {
expect(exp(["downcase", "PM"])).toBe("pm");
expect(exp(["downcase", "pm"])).toBe("pm");
});
test("upcase", () => {
expect(exp(["upcase", "pm"])).toBe("PM");
expect(exp(["upcase", "PM"])).toBe("PM");
});
test("number-format", () => {
expect(exp(["number-format", 3.1415926535, 1, 3])).toBe("3.142");
expect(exp(["number-format", 3, 2, 5])).toBe("3.00");
});
});
describe("nested", () => {
test("convert liter/minute to US gallon/hour", () => {
const literPerMinute = 5;
expect(
exp([
"concat",
["*", ["*", literPerMinute, 0.2641722], 60],
" ",
"gallon/hour",
])
).toBe("79.25166 gallon/hour");
});
test("nested conditional", () => {
const getExpression = (animal: string): Expression => [
"if",
["==", animal, "cat"],
"meow",
["==", animal, "dog"],
"woof",
"Wa-pa-pa-pa-pa-pa-pow!",
];
expect(exp(getExpression("cat"))).toBe("meow");
expect(exp(getExpression("dog"))).toBe("woof");
expect(exp(getExpression("fox"))).toBe("Wa-pa-pa-pa-pa-pa-pow!");
});
test("wildcard", () => {
const data: Data = {
value: 5,
};
expect(
exp(
[
"concat",
["number-format", ["*", ["*", "$value", 0.2641722], 60], 0, 1],
" ",
"gallon/hour",
],
data
)
).toBe("79.3 gallon/hour");
const expression: Expression = [
"if",
["==", "$animal", "cat"],
"meow",
["==", "$animal", "dog"],
"woof",
"Wa-pa-pa-pa-pa-pa-pow!",
];
expect(exp(expression, { animal: "cat" })).toBe("meow");
expect(exp(expression, { animal: "dog" })).toBe("woof");
expect(exp(expression, { animal: "fox" })).toBe("Wa-pa-pa-pa-pa-pa-pow!");
});
});
export type { Constant, Data, Expression, Value } from "./interface";
export { evalVal as exp } from "./evaluator";
export type Constant = string | number | boolean;
export type Expression = [string, Value, ...Value[]];
export type Value = Expression | Constant;
export type Data = {
[s: string]: Constant;
};
import type { Constant, Expression, Value } from "./interface";
export const isExpression = (value: Value): value is Expression => {
return (
Array.isArray(value) && value.length >= 2 && typeof value[0] === "string"
);
};
export const isConstant = (value: Value): value is Constant => {
return ["string", "number", "boolean"].includes(typeof value);
};
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}