diff --git a/lib/vastly.global.js b/lib/vastly.global.js new file mode 100644 index 00000000..25d6a94c --- /dev/null +++ b/lib/vastly.global.js @@ -0,0 +1,371 @@ +var Vastly = (function (exports) { + 'use strict'; + + /** + * Get a node’s children + * @param {object | object[]} node or nodes + * @returns {object[]} + */ + function children(node) { + if (Array.isArray(node)) { + return node.flatMap(node => children(node)); + } + + return childProperties.flatMap(property => node[property] ?? []); + } + + /** + * Which properties of a node are child nodes? + * Can be imported and manipulated by calling code to extend the walker + * @type {string[]} + */ + const childProperties = [ + "arguments", "callee", // CallExpression + "left", "right", // BinaryExpression, LogicalExpression + "argument", // UnaryExpression + "elements", // ArrayExpression + "test", "consequent", "alternate", // ConditionalExpression + "object", "property", // MemberExpression + "body" + ]; + + function closest (node, type) { + let n = node; + + do { + if (n.type === type) { + return n; + } + } while (n = n.parent); + + return null; + } + + function matches(node, filter) { + if (!filter) { + return true; + } + + if (Array.isArray(filter)) { + // Multiple filters: OR + return filter.some(f => matches(node, f)); + } + + if (typeof filter === "function") { + return filter(node); + } + + // Coerce to string, if we're here we have no other way to compare + return node.type == filter; + } + + /** + * Recursively execute a callback on this node and all its children. + * If the callback returns a non-undefined value, it will overwrite the node. + * @param {object} node + * @param {function(object, string, object?)} callback + * @param {object} [o] + * @param {string | string[] | function} [o.only] Only walk nodes of this type + * @param {string | string[] | function} [o.except] Ignore walking nodes of these types + */ + function map (node, callback, o) { + return _map(node, callback, o); + } + + function _map (node, callback, o = {}, property, parent) { + let ignored = o.ignore && matches(node, o.ignore); + + if (!ignored && matches(node, o.only)) { + let ret = callback(node, property, parent); + + for (let child of children(node)) { + _map(child, callback, o, property, node); + } + + if (ret !== undefined && parent) { + // Callback returned a value, overwrite the node + // We apply such transformations after walking, to avoid infinite recursion + parent[property] = ret; + } + + return ret; + } + } + + /** + * How to evaluate each unary operator. + * Can be imported and modified by calling code. + */ + const unaryOperators = { + "!": a => !a, + "+": a => +a, + "-": a => -a, + }; + + /** + * How to evaluate each binary operator. + * Can be imported and modified by calling code. + */ + const binaryOperators = { + // Math + "+": (a, b) => a + b, + "-": (a, b) => a - b, + "*": (a, b) => a * b, + "/": (a, b) => a / b, + "%": (a, b) => a % b, + "**": (a, b) => a ** b, + + // Bitwise + "&": (a, b) => a & b, + "|": (a, b) => a | b, + "^": (a, b) => a ^ b, + ">>": (a, b) => a >> b, + ">>>": (a, b) => a >>> b, + "<<": (a, b) => a << b, + + // Logical + "&&": (a, b) => a && b, + "||": (a, b) => a || b, + "??": (a, b) => a ?? b, + + // Comparison + "==": (a, b) => a == b, + "!=": (a, b) => a != b, + "===": (a, b) => a === b, + "!==": (a, b) => a !== b, + "<": (a, b) => a < b, + "<=": (a, b) => a <= b, + ">": (a, b) => a > b, + ">=": (a, b) => a >= b, + + // Other + "in": (a, b) => a in b, + "instanceof": (a, b) => a instanceof b, + + }; + + /** + * How to evaluate each AST type. + * Can be imported and modified by calling code. + */ + const evaluators = { + "UnaryExpression": (node, ...contexts) => { + let operator = unaryOperators[node.operator]; + if (!operator) { + throw new TypeError(`Unknown unary operator ${node.operator}`); + } + + return operator(evaluate(node.argument, ...contexts)); + }, + + "BinaryExpression": (node, ...contexts) => { + let operator = binaryOperators[node.operator]; + if (!operator) { + throw new TypeError(`Unknown binary operator ${node.operator}`); + } + + return operator(evaluate(node.left, ...contexts), evaluate(node.right, ...contexts)); + }, + + "ConditionalExpression": (node, ...contexts) => { + return evaluate(evaluate(node.test, ...contexts) ? node.consequent : node.alternate, ...contexts); + }, + + "MemberExpression": (node, ...contexts) => { + let object = evaluate(node.object, ...contexts); + let property = node.computed ? evaluate(node.property, ...contexts) : node.property.name; + + if (!object) { + throw new TypeError(`Cannot read properties of ${object} (reading '${property}')`); + } + + return object[property]; + }, + + "CallExpression": (node, ...contexts) => { + let callee = evaluate(node.callee, ...contexts); + let args = node.arguments.map(arg => evaluate(arg, ...contexts)); + return callee(...args); + }, + + "ArrayExpression": node => node.elements.map(node => evaluate(node, ...contexts)), + "Compound": (node, ...contexts) => evaluate(node.body.at(-1), ...contexts), + "Identifier": (node, ...contexts) => resolve(node.name, ...contexts), + "Literal": node => node.value, + "ThisExpression": (node, ...contexts) => contexts[0], + }; + + /** + * Evaluate an AST node into a value. + * @param {object} node AST node to evaluate + * @param {...object} contexts - Objects to look up identifiers in (in order). + * E.g. the first could be local data, the second could be global data. + */ + function evaluate (node, ...contexts) { + if (node.type in evaluators) { + return evaluators[node.type](node, ...contexts); + } + + throw new TypeError(`Cannot evaluate node of type ${node.type}`); + } + + function resolve (property, ...contexts) { + let context = contexts.find(context => property in context); + + return context?.[property]; + } + + /** + * Recursively execute a callback on this node and all its children. + * If the callback returns a non-undefined value, walking ends and the value is returned + * @param {object} node + * @param {function(object, string, object?)} callback + * @param {object} [o] + * @param {string | string[] | function} [o.only] Only walk nodes of this type + * @param {string | string[] | function} [o.except] Ignore walking nodes of these types + */ + function walk (node, callback, o) { + return _walk(node, callback, o); + } + + function _walk(node, callback, o = {}, property, parent) { + let ignored = o.except && matches(node, o.except); + + if (!ignored && matches(node, o.only)) { + let ret = callback(node, property, parent); + + if (ret !== undefined) { + // Callback returned a value, stop walking and return it + return ret; + } + + for (let child of children(node)) { + _walk(child, callback, o, property, node); + } + } + } + + /** + * Find an AST node and return it, or `null` if not found. + * @param {object | object[]} node + * @param {function(object): boolean} callback + */ + function find (node, callback) { + return walk(node, node => { + if (callback(node)) { + return node; + } + }) ?? null; + } + + /** + * Functions to serialize each specific node type. + * Can be imported and overwritten by calling code. + * @type {Object.} + * + */ + const serializers = { + "BinaryExpression": node => `${serialize(node.left)} ${node.operator} ${serialize(node.right)}`, + "UnaryExpression": node => `${node.operator}${serialize(node.argument)}`, + "CallExpression": node => { + let callee = serialize(node.callee); + let args = node.arguments.map(n => serialize(n)); + return `${callee}(${args.join(", ")})`; + }, + "ConditionalExpression": node => `${serialize(node.test)} ? ${serialize(node.consequent)} : ${serialize(node.alternate)}`, + "MemberExpression": node => { + let property = node.computed? `[${serialize(node.property)}]` : `.${node.property.name}`; + let object = serialize(node.object); + return `${object}${property}`; + }, + "ArrayExpression": node => `[${node.elements.map(n => serialize(n)).join(", ")}]`, + "Literal": node => node.raw, + "Identifier": node => node.name, + "ThisExpression": node => "this", + "Compound": node => node.body.map(n => serialize(n)).join(", ") + }; + + + const transformations = {}; + + /** + * Recursively serialize an AST node into a JS expression + * @param {*} node + * @returns + */ + function serialize (node) { + if (!node || typeof node === "string") { + return node; // already serialized + } + + let ret = transformations[node.type]?.(node) ?? node; + + if (typeof ret == "object" && ret?.type) { + node = ret; + } + else if (ret !== undefined) { + return ret; + } + + if (!node.type || !serializers[node.type]) { + throw new TypeError("Cannot understand this expression at all 😔"); + } + + return serializers[node.type](node); + } + + /** + * Recursively traverse the AST and return all top-level identifiers + * @param {object} node the AST node to traverse + * @returns a list of all top-level identifiers + */ + function variables(node) { + switch(node.type) { + case "Literal": + return [] + case "UnaryExpression": + return variables(node.argument); + case "BinaryExpression": + case "LogicalExpression": + const {left, right} = node; + return [left, right].flatMap(variables); + case "ConditionalExpression": + const {test, consequent, alternate} = node; + return [test, consequent, alternate].flatMap(variables); + case "Compound": + return node.body.flatMap(variables); + case "ArrayExpression": + return node.elements.flatMap(variables); + case "CallExpression": + return [node.callee, ...node.arguments].flatMap(variables); + case "MemberExpression": + const {object, property, computed} = node; + // only explore the property if it's a complex expression that might + // have more identifiers + // computed is true if the MemberExpression is of the form a[b] (need to explore further) + // computed is false if the MemberExpression is of the form a.b (don't need to explore further) + const propertyChildren = computed ? variables(property) : []; + return variables(object).concat(propertyChildren); + // Rest of the cases contain a single variable + case "ThisExpression": + case "Identifier": + default: + return [node]; + } + } + + exports.children = children; + exports.closest = closest; + exports.evaluate = evaluate; + exports.find = find; + exports.map = map; + exports.serialize = serialize; + exports.variables = variables; + exports.walk = walk; + + Object.defineProperty(exports, '__esModule', { value: true }); + + return exports; + +})({}); +//# sourceMappingURL=vastly.global.js.map diff --git a/package-lock.json b/package-lock.json index 07b3bd34..1fa25107 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mavo", - "version": "0.2.4", + "version": "0.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mavo", - "version": "0.2.4", + "version": "0.3.0", "license": "MIT", "devDependencies": { "@babel/core": "^7", @@ -21,12 +21,10 @@ "gulp-inject-version": "^1", "gulp-notify": "^4.0.0", "gulp-rename": "^2", - "gulp-replace": "^1", "gulp-sass": "^5", "gulp-sourcemaps": "^2", "merge2": "^1", - "sass": "^1", - "sorcery": "^0.10.0" + "sass": "^1" } }, "node_modules/@babel/code-frame": { @@ -1703,28 +1701,6 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, - "node_modules/@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "node_modules/@types/node": { - "version": "14.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", - "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", - "dev": true - }, - "node_modules/@types/vinyl": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz", - "integrity": "sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==", - "dev": true, - "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, "node_modules/acorn": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", @@ -2454,18 +2430,6 @@ "node": ">=0.10.0" } }, - "node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true, - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -2536,15 +2500,6 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true, - "engines": { - "node": "*" - } - }, "node_modules/buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -3351,12 +3306,6 @@ "es6-symbol": "^3.1.1" } }, - "node_modules/es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, "node_modules/es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -4617,22 +4566,6 @@ "node": ">=4" } }, - "node_modules/gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", - "dev": true, - "dependencies": { - "@types/node": "^14.14.41", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/gulp-sass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz", @@ -5453,22 +5386,6 @@ "node": ">=0.10.0" } }, - "node_modules/istextorbinary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", - "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", - "dev": true, - "dependencies": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6004,18 +5921,6 @@ "node": ">=0.10.0" } }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7081,17 +6986,6 @@ "node": ">= 0.10" } }, - "node_modules/replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7200,30 +7094,6 @@ "ret": "~0.1.10" } }, - "node_modules/sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=", - "dev": true, - "dependencies": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - } - }, - "node_modules/sander/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/sass": { "version": "1.35.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", @@ -7592,21 +7462,6 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, - "node_modules/sorcery": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", - "integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=", - "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0", - "sourcemap-codec": "^1.3.0" - }, - "bin": { - "sorcery": "bin/index.js" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7644,12 +7499,6 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, "node_modules/sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -7959,18 +7808,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -8604,15 +8441,6 @@ "yargs-parser": "^5.0.1" } }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/yargs/node_modules/yargs-parser": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", @@ -9803,28 +9631,6 @@ "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", "dev": true }, - "@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "@types/node": { - "version": "14.17.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz", - "integrity": "sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw==", - "dev": true - }, - "@types/vinyl": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.4.tgz", - "integrity": "sha512-2o6a2ixaVI2EbwBPg1QYLGQoHK56p/8X/sGfKbFC8N6sY9lfjsMf/GprtkQkSya0D4uRiutRZ2BWj7k3JvLsAQ==", - "dev": true, - "requires": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, "acorn": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz", @@ -10432,12 +10238,6 @@ "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, - "binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true - }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -10497,12 +10297,6 @@ } } }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - }, "buffer-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", @@ -11189,12 +10983,6 @@ "es6-symbol": "^3.1.1" } }, - "es6-promise": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", - "dev": true - }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -12201,19 +11989,6 @@ "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", "dev": true }, - "gulp-replace": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.3.tgz", - "integrity": "sha512-HcPHpWY4XdF8zxYkDODHnG2+7a3nD/Y8Mfu3aBgMiCFDW3X2GiOKXllsAmILcxe3KZT2BXoN18WrpEFm48KfLQ==", - "dev": true, - "requires": { - "@types/node": "^14.14.41", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - } - }, "gulp-sass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz", @@ -12847,16 +12622,6 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, - "istextorbinary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", - "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", - "dev": true, - "requires": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13313,15 +13078,6 @@ } } }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -14164,17 +13920,6 @@ "remove-trailing-separator": "^1.1.0" } }, - "replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -14258,29 +14003,6 @@ "ret": "~0.1.10" } }, - "sander": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", - "integrity": "sha1-dB4kXiMfB8r7b98PEzrfohalAq0=", - "dev": true, - "requires": { - "es6-promise": "^3.1.2", - "graceful-fs": "^4.1.3", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "sass": { "version": "1.35.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.35.1.tgz", @@ -14571,18 +14293,6 @@ } } }, - "sorcery": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.10.0.tgz", - "integrity": "sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=", - "dev": true, - "requires": { - "buffer-crc32": "^0.2.5", - "minimist": "^1.2.0", - "sander": "^0.5.0", - "sourcemap-codec": "^1.3.0" - } - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -14614,12 +14324,6 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, "sparkles": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", @@ -14876,12 +14580,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", - "dev": true - }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -15415,12 +15113,6 @@ } } } - }, - "yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true } } } diff --git a/src/backend.js b/src/backend.js index afdd1a1a..bac3c341 100644 --- a/src/backend.js +++ b/src/backend.js @@ -193,7 +193,7 @@ var _ = Mavo.Backend = class Backend extends EventTarget { } addEventListener("message", evt => { - if (evt.source === this.authPopup) { + if (evt.source === this.authPopup && evt.data.backend) { if (evt.data.backend == this.id) { this.accessToken = localStorage[`mavo:${id}token`] = evt.data.token; } diff --git a/src/mavo.js b/src/mavo.js index fb9af60e..235c537f 100644 --- a/src/mavo.js +++ b/src/mavo.js @@ -135,7 +135,7 @@ let _ = self.Mavo = $.Class(class Mavo { $.bind(this.element, "mv-login.mavo", evt => { if (evt.backend == (this.source || this.storage)) { // If last time we rendered we got nothing, maybe now we'll have better luck? - if (this.inProgress !== "loading" && !this.root.data && !this.unsavedChanges) { + if (!this.root.data && !this.unsavedChanges) { this.load(); } } @@ -546,9 +546,6 @@ let _ = self.Mavo = $.Class(class Mavo { return; } - let autoSaveState = this.autoSave; - this.autoSave = false; - if (data === undefined) { this.inProgress = "loading"; @@ -588,13 +585,17 @@ let _ = self.Mavo = $.Class(class Mavo { this.inProgress = false; } + let autoSaveState = this.autoSave; + this.autoSave = false; + this.render(data); + this.autoSave = autoSaveState; + await Mavo.defer(); this.dataLoaded.resolve(); this.element.dispatchEvent(new CustomEvent("mv-load", {detail: backend, bubbles: true})); - this.autoSave = autoSaveState; } async store () { diff --git a/src/mavoscript.js b/src/mavoscript.js index 9cae59dc..4e8af587 100644 --- a/src/mavoscript.js +++ b/src/mavoscript.js @@ -173,24 +173,6 @@ var _ = Mavo.Script = { } }, - // Is this variable? - // E.g. foo or foo.bar is not static whereas "foo" or bar() is - isStatic: node => { - if (node.type === "Identifier") { - return false; - } - - for (let property of _.childProperties) { - if (node[property] && property !== "callee") { - if (!_.isStatic(node[property])) { - return false; - } - } - } - - return true; - }, - /** * Operations for elements and scalars. * Operations between arrays happen element-wise. @@ -538,323 +520,11 @@ var _ = Mavo.Script = { return [a, b]; }, - childProperties: [ - "arguments", "callee", // CallExpression - "left", "right", // BinaryExpression, LogicalExpression - "argument", // UnaryExpression - "elements", // ArrayExpression - "test", "consequent", "alternate", // ConditionalExpression - "object", "property", // MemberExpression - "body" - ], + walk: Vastly.walk, - /** - * Recursively execute a callback on this node and all its children - * Caveat: For CallExpression arguments, it will call callback with an array - * callback needs to take care of iterating over the array - */ - walk: function(node, callback, o = {}, property, parent) { - if (!o.type || node.type === o.type) { - var ret = callback(node, property, parent); - } + closest: Vastly.closest, - if (!o.ignore || o.ignore.indexOf(node.type) === -1) { - if (Array.isArray(node)) { - for (let n of node) { - _.walk(n, callback, o, property, node); - } - } - else { - _.childProperties.forEach(property => { - if (node[property]) { - _.walk(node[property], callback, o, property, node); - } - }); - } - } - - if (ret !== undefined && parent) { - // Apply transformations after walking, otherwise it may recurse infinitely - parent[property] = ret; - } - - return ret; - }, - - /** - * These serializers transform the AST into JS - */ - serializers: { - "BinaryExpression": node => `${_.serialize(node.left, node)} ${node.operator} ${_.serialize(node.right, node)}`, - "UnaryExpression": node => `${node.operator}${_.serialize(node.argument, node)}`, - "CallExpression": node => { - var callee = node.callee; - let root = node.callee; - let parent = node; - let prop = "callee"; - - // Find left-most member - while (root.type === "MemberExpression") { - parent = root; - root = root.object; - prop = "object"; - } - - if (node.callee.type === "MemberExpression") { - if (node.callee.property.type === "Identifier" && node.callee.property.name === "call") { - callee = node.callee.object; - } - } - - if (root.type === "Identifier") { - // Clashes with native prototype methods? If so, look first in Function trap - var name = root.name; - - if (name === "scope") { - return _.serializeScopeCall(node.arguments); - } - else if (name in Mavo.Script.$fn) { - parent[prop] = { - type: "MemberExpression", - computed: false, - object: {type: "Identifier", name: "$fn"}, - property: root - }; - } - } - - var nameSerialized = _.serialize(node.callee, node); - var argsSerialized = node.arguments.map(n => _.serialize(n, node)); - return `${nameSerialized}(${argsSerialized.join(", ")})`; - }, - "ConditionalExpression": node => `${_.serialize(node.test, node)}? ${_.serialize(node.consequent, node)} : ${_.serialize(node.alternate, node)}`, - "MemberExpression": (node, parent) => { - let n = node, pn, callee; - - do { - if (n.type === "CallExpression" && n.callee === pn) { - break; - } - pn = n; - } while (n = n.parent); - - if (n) { // Use plain serialization for foo.bar.baz() - var property = node.computed? `[${_.serialize(node.property, node)}]` : `.${node.property.name}`; - return `${_.serialize(node.object, node)}${property}`; - } - - n = node; - let properties = [], object, objectParent; - - while (n.type === "MemberExpression") { - let serialized = n.computed? _.serialize(n.property, n) : `"${n.property.name}"`; - properties.push(serialized); - objectParent = n; - object = n = n.object; - } - - return `$fn.get(${_.serialize(object, objectParent)}, ${properties.reverse().join(", ")})`; - }, - "ArrayExpression": node => `[${node.elements.map(n => _.serialize(n, node)).join(", ")}]`, - "Literal": node => { - let quote = node.raw[0]; - - if (quote === "'" || quote === '"') { - let content = node.raw.slice(1, -1); - content = content.replace(/\r/g, "\\r").replace(/\n/g, "\\n"); - // jsep does not properly escape quotes, see https://github.com/EricSmekens/jsep/issues/192 - // let regex = quote === '"'? /(? node.name, - "ThisExpression": node => "this", - "Compound": node => node.body.map(n => _.serialize(n, node)).join(", ") - }, - - /** - * These are run before the serializers and transform the expression to support MavoScript - */ - transformations: { - "BinaryExpression": node => { - let name = _.getOperatorName(node.operator); - let def = _.operators[name]; - - // Operator-specific transformations - def.transformation?.(node); - - var nodeLeft = node; - var ret = { - type: "CallExpression", - arguments: [], - callee: {type: "Identifier", name} - }; - - if (def.comparison) { - // Flatten comparison operator calls. If all comparison - // operators are the same, flatten into one call (to maintain - // simplicity of output): - // 3 < 4 < 5 becomes lt(3, 4, 5). - // Otherwise, assemble an argument list like so: - // 3 < 4 = 5 becomes compare(3, "lt", 4, "eq", 5). - - // Create list of {comparison, operand} objects - let comparisonOperands = []; - do { - let operatorName = _.getOperatorName(nodeLeft.operator); // e.g. "lt" - comparisonOperands.unshift({ - comparison: operatorName, - operand: nodeLeft.right - }); - nodeLeft = nodeLeft.left; - } while (def.flatten !== false && _.isComparisonOperator(nodeLeft.operator)); - - // Determine if all comparison operators are the same - let comparisonsHeterogeneous = false; - for (let i = 0; i < comparisonOperands.length - 1; i++) { - if (comparisonOperands[i].comparison != comparisonOperands[i+1].comparison) { - comparisonsHeterogeneous = true; - break; - } - } - - // Assemble final callee and argument list - ret.arguments.push(nodeLeft); // first operand - if (comparisonsHeterogeneous) { - ret.callee.name = "compare"; - comparisonOperands.forEach(co => { - ret.arguments.push({ - type: "Literal", - value: co.comparison, - raw: `"${co.comparison}"`, - }); - ret.arguments.push(co.operand); - }); - } - else { - comparisonOperands.forEach(co => { - ret.arguments.push(co.operand); - }); - } - } - else { - // Flatten same operator calls - do { - ret.arguments.unshift(nodeLeft.right); - nodeLeft = nodeLeft.left; - } while (def.flatten !== false && nodeLeft.right && _.getOperatorName(nodeLeft.operator) === name); - - ret.arguments.unshift(nodeLeft); - } - - // Operator-specific transformations - def.postFlattenTransformation?.(ret); - - return ret; - }, - "UnaryExpression": node => { - var name = _.getOperatorName(node.operator, true); - - if (name) { - return { - type: "CallExpression", - arguments: [node.argument], - callee: {type: "Identifier", name} - }; - } - }, - "CallExpression": node => { - if (node.callee.type == "Identifier") { - if (node.callee.name == "if") { - node.callee.name = "iff"; - - // Traverse data actions inside if() and rewrite them to their *if() counterpart - var condition = node.arguments[0]; - - for (let i=1; i { - var name = n.callee.name; - - if (Mavo.Actions.Functions.hasOwnProperty(name) // is a data action - && !/if$/.test(name) // and not already the *if() version of itself - ) { - n.callee.name += "if"; - - // Add condition as first argument of *if() function - n.arguments.unshift(condition); - } - }, {type: "CallExpression"}); - } - } - else if (node.callee.name == "delete") { - node.callee.name = "clear"; - } - else { - var def = Mavo.Functions[node.callee.name]; - - if (def && def.needsContext) { - // Rewrite to funcName.call($this, ...args) - node.callee = { - type: "MemberExpression", - computed: false, - object: node.callee, - property: {type: "Identifier", name: "call"} - }; - node.arguments.unshift({type: "Identifier", name: "$this"}); - } - } - } - }, - "ThisExpression": node => { - return {type: "Identifier", name: "$this"}; - } - }, - - closest (node, type) { - let n = node; - - do { - if (n.type === type) { - return n; - } - } while (n = n.parent); - - return null; - }, - - serialize: (node, parent) => { - if (typeof node === "string") { - return node; // already serialized - } - - if (parent) { - node.parent = parent; - } - - var ret = _.transformations[node.type]?.(node, parent); - - if (typeof ret == "object" && ret?.type) { - node = ret; - } - else if (ret !== undefined) { - return ret; - } - - if (!node.type || !_.serializers[node.type]) { - throw new TypeError("Cannot understand this expression at all 😔"); - } - - return _.serializers[node.type](node, parent); - }, + serialize: Vastly.serialize, rewrite: function(code, o) { let ast = _.parse(code); @@ -936,9 +606,6 @@ Mavo.Actions.running = Mavo.Actions._running;`; } }; -_.serializers.LogicalExpression = _.serializers.BinaryExpression; -_.transformations.LogicalExpression = _.transformations.BinaryExpression; - for (let name in _.operators) { let details = _.operators[name];