twing
Version:
First-class Twig engine for the JavaScript ecosystem
1,511 lines (1,414 loc) • 440 kB
JavaScript
'use strict';
var Levenshtein = require('levenshtein');
var Path = require('path');
var rtrim = require('locutus/php/strings/rtrim');
var luxon = require('luxon');
var isPlainObject$1 = require('is-plain-object');
var parser = require('regex-parser');
var phpRange = require('locutus/php/array/range');
var array_chunk = require('locutus/php/array/array_chunk');
var array_merge = require('locutus/php/array/array_merge');
var snakeCase = require('snake-case');
var isObject = require('isobject');
var twigLexer = require('twig-lexer');
var strings = require('locutus/php/strings');
var phpSprintf = require('locutus/php/strings/sprintf');
var phpRawurlencode = require('locutus/php/url/rawurlencode');
var phpOrd = require('locutus/php/strings/ord');
var sourceMap = require('source-map');
var math = require('locutus/php/math');
var pad = require('pad');
var phpStrtr = require('locutus/php/strings/strtr');
var phpNumberFormat = require('locutus/php/strings/number_format');
var phpHttpBuildQuery = require('locutus/php/url/http_build_query');
var IconVLite = require('iconv-lite');
var phpUcwords = require('locutus/php/strings/ucwords');
var words = require('capitalize');
var phpStripTags = require('locutus/php/strings/strip_tags');
var phpTrim = require('locutus/php/strings/trim');
var phpLeftTrim = require('locutus/php/strings/ltrim');
var phpNl2br = require('locutus/php/strings/nl2br');
var explode = require('locutus/php/strings/explode');
var esrever = require('esrever');
var phpRound = require('locutus/php/math/round');
var phpCeil = require('locutus/php/math/ceil');
var phpFloor = require('locutus/php/math/floor');
var runes = require('runes');
var mt_rand = require('locutus/php/math/mt_rand');
var array_rand = require('locutus/php/array/array_rand');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var Path__namespace = /*#__PURE__*/_interopNamespaceDefault(Path);
const createBaseError = (name, message, location, source, previous) => {
const baseError = Error(message);
baseError.name = name;
const error = Object.create(baseError, {
location: {
get: () => location
},
source: {
get: () => source
},
previous: {
value: previous
},
rootMessage: {
value: message
},
appendMessage: {
value: (value) => {
message += value;
updateRepresentation();
}
}
});
const updateRepresentation = () => {
let representation = message;
let dot = false;
if (representation.slice(-1) === '.') {
representation = representation.slice(0, -1);
dot = true;
}
let questionMark = false;
if (representation.slice(-1) === '?') {
representation = representation.slice(0, -1);
questionMark = true;
}
representation += ` in "${source.name}"`;
const { line, column } = location;
representation += ` at line ${line}, column ${column}`;
if (dot) {
representation += '.';
}
if (questionMark) {
representation += '?';
}
baseError.message = representation;
};
updateRepresentation();
Error.captureStackTrace(error, createBaseError);
return error;
};
const parsingErrorName = 'TwingParsingError';
const createParsingError = (message, location, source, previous) => {
const baseError = createBaseError(parsingErrorName, message, location, source, previous);
Error.captureStackTrace(baseError, createParsingError);
return Object.create(baseError, {
addSuggestions: {
value: (name, items) => {
const alternatives = [];
for (const item of items) {
const levenshtein = new Levenshtein(name, item);
if (levenshtein.distance <= (name.length / 3) || item.indexOf(name) > -1) {
alternatives.push(item);
}
}
if (alternatives.length < 1) {
return;
}
alternatives.sort();
baseError.appendMessage(` Did you mean "${alternatives.join(', ')}"?`);
}
}
});
};
const runtimeErrorName = 'TwingRuntimeError';
const createRuntimeError = (message, location, source, previous) => {
const error = createBaseError(runtimeErrorName, message, location, source, previous);
Error.captureStackTrace(error, createRuntimeError);
return error;
};
const isATwingError = (candidate) => {
return [
parsingErrorName,
runtimeErrorName
].includes(candidate.name);
};
const createTemplateLoadingError = (names) => {
let message;
if (names.length === 1) {
const name = names[0];
message = `Unable to find template "${name ? name : ''}".`;
}
else {
message = `Unable to find one of the following templates: "${names.join('", "')}".`;
}
const error = Error(message);
Error.captureStackTrace(error, createTemplateLoadingError);
return error;
};
const createSource = (name, code) => {
return {
get code() {
return code;
},
get name() {
return name;
}
};
};
const { join: join$1, dirname, normalize } = Path__namespace.posix;
const createFilesystemLoader = (filesystem) => {
const namespacedPaths = new Map();
const stat = (path) => {
return new Promise((resolve) => {
filesystem.stat(path, (error, stats) => {
if (error) {
resolve(null);
}
else {
resolve(stats);
}
});
});
};
const resolvePathFromSource = (name, from) => {
return join$1(dirname(from), name);
};
/**
* If the name starts with a slash, resolve absolutely;
* else, if the name starts with a dot, resolve relatively to the passed _from_;
* else, resolve from the namespace.
*/
const resolve = (name, from) => {
const findTemplateInPath = async (path) => {
const stats = await stat(path);
if (stats && stats.isFile()) {
return Promise.resolve(path);
}
else {
return Promise.resolve(null);
}
};
if (name.startsWith('/')) {
// If the name starts with a slash, resolve absolutely;
return findTemplateInPath(name);
}
else if (name.startsWith('.')) {
// else, if the name starts with a dot, resolve relatively to the passed _from_;
name = normalize(from ? resolvePathFromSource(name, from) : name);
return findTemplateInPath(name);
}
else {
// else, resolve from the namespace.
const [namespace, shortname] = parseName(name);
// todo: we keep the fallback to ['.'] for backward compatibility purpose; with Twing 8, we can be more restrictive.
const paths = namespacedPaths.get(namespace) || ['.'];
const findTemplateInPathAtIndex = async (index) => {
if (index < paths.length) {
const path = paths[index];
const templatePath = await findTemplateInPath(join$1(path, shortname));
if (templatePath) {
return Promise.resolve(templatePath);
}
else {
// let's continue searching
return findTemplateInPathAtIndex(index + 1);
}
}
else {
return Promise.resolve(null);
}
};
return findTemplateInPathAtIndex(0);
}
};
const parseName = (name) => {
const position = name.indexOf('/');
const potentialNamespace = name.substring(0, position);
if (namespacedPaths.get(potentialNamespace) !== undefined) {
const shortname = name.substring(position + 1);
return [potentialNamespace, shortname];
}
return [null, name];
};
const addPath = (path, namespace = null) => {
let namespacePaths = namespacedPaths.get(namespace);
if (!namespacePaths) {
namespacePaths = [];
namespacedPaths.set(namespace, namespacePaths);
}
namespacePaths.push(rtrim(path, '\/\\\\'));
};
const prependPath = (path, namespace = null) => {
path = rtrim(path, '\/\\\\');
const namespacePaths = namespacedPaths.get(namespace);
if (!namespacePaths) {
namespacedPaths.set(namespace, [path]);
}
else {
namespacePaths.unshift(path);
}
};
return {
addPath,
exists: (name, from) => {
return resolve(name, from)
.then((path) => {
return path !== null;
});
},
resolve,
getSource: (name, from) => {
return resolve(name, from)
.then((path) => {
if (path === null) {
return null;
}
else {
return new Promise((resolve, reject) => {
filesystem.readFile(path, (error, data) => {
if (error) {
reject(error);
}
else {
resolve(createSource(path, data.toString()));
}
});
});
}
});
},
isFresh: (name, time, from) => {
return resolve(name, from)
.then((path) => {
if (path === null) {
return true;
}
else {
return stat(path)
.then((stats) => {
return stats.mtime.getTime() <= time;
});
}
});
},
prependPath
};
};
const createSynchronousFilesystemLoader = (filesystem) => {
const namespacedPaths = new Map();
const stat = (path) => {
try {
return filesystem.statSync(path);
}
catch (error) {
return null;
}
};
const resolvePathFromSource = (name, from) => {
return join$1(dirname(from), name);
};
/**
* If the name starts with a slash, resolve absolutely;
* else, if the name starts with a dot, resolve relatively to the passed _from_;
* else, resolve from the namespace.
*/
const resolve = (name, from) => {
const findTemplateInPath = (path) => {
const stats = stat(path);
if (stats && stats.isFile()) {
return path;
}
else {
return null;
}
};
if (name.startsWith('/')) {
// If the name starts with a slash, resolve absolutely;
return findTemplateInPath(name);
}
else if (name.startsWith('.')) {
// else, if the name starts with a dot, resolve relatively to the passed _from_;
name = normalize(from ? resolvePathFromSource(name, from) : name);
return findTemplateInPath(name);
}
else {
// else, resolve from the namespace.
const [namespace, shortname] = parseName(name);
// todo: we keep the fallback to ['.'] for backward compatibility purpose; with Twing 8, we can be more restrictive.
const paths = namespacedPaths.get(namespace) || ['.'];
const findTemplateInPathAtIndex = (index) => {
if (index < paths.length) {
const path = paths[index];
const templatePath = findTemplateInPath(join$1(path, shortname));
if (templatePath) {
return templatePath;
}
else {
// let's continue searching
return findTemplateInPathAtIndex(index + 1);
}
}
else {
return null;
}
};
return findTemplateInPathAtIndex(0);
}
};
const parseName = (name) => {
const position = name.indexOf('/');
const potentialNamespace = name.substring(0, position);
if (namespacedPaths.get(potentialNamespace) !== undefined) {
const shortname = name.substring(position + 1);
return [potentialNamespace, shortname];
}
return [null, name];
};
const addPath = (path, namespace = null) => {
let namespacePaths = namespacedPaths.get(namespace);
if (!namespacePaths) {
namespacePaths = [];
namespacedPaths.set(namespace, namespacePaths);
}
namespacePaths.push(rtrim(path, '\/\\\\'));
};
const prependPath = (path, namespace = null) => {
path = rtrim(path, '\/\\\\');
const namespacePaths = namespacedPaths.get(namespace);
if (!namespacePaths) {
namespacedPaths.set(namespace, [path]);
}
else {
namespacePaths.unshift(path);
}
};
return {
addPath,
exists: (name, from) => {
const path = resolve(name, from);
return path !== null;
},
resolve,
getSource: (name, from) => {
const path = resolve(name, from);
if (path === null) {
return null;
}
else {
const data = filesystem.readFileSync(path);
return createSource(path, data.toString());
}
},
isFresh: (name, time, from) => {
const path = resolve(name, from);
if (path === null) {
return true;
}
else {
const stats = stat(path);
return stats.mtime.getTime() <= time;
}
},
prependPath
};
};
const createArrayLoader = (templates) => {
const loader = {
setTemplate: (name, template) => {
templates[name] = template;
},
getSource: (name, from) => {
return loader.exists(name, from)
.then((exists) => {
if (!exists) {
return null;
}
return createSource(name, templates[name]);
});
},
exists(name) {
return Promise.resolve(templates[name] !== undefined);
},
resolve: (name, from) => {
return loader.exists(name, from)
.then((exists) => {
if (!exists) {
return null;
}
return name;
});
},
isFresh: () => {
return Promise.resolve(true);
}
};
return loader;
};
const createSynchronousArrayLoader = (templates) => {
const loader = {
setTemplate: (name, template) => {
templates[name] = template;
},
getSource: (name, from) => {
if (loader.exists(name, from)) {
return createSource(name, templates[name]);
}
return null;
},
exists(name) {
return templates[name] !== undefined;
},
resolve: (name, from) => {
if (loader.exists(name, from)) {
return name;
}
return null;
},
isFresh: () => {
return true;
}
};
return loader;
};
const createChainLoader = (loaders) => {
let existsCache = new Map();
const addLoader = (loader) => {
loaders.push(loader);
existsCache = new Map();
};
const loader = {
get loaders() {
return loaders;
},
addLoader,
exists: (name, from) => {
const cachedResult = existsCache.get(name);
if (cachedResult) {
return Promise.resolve(cachedResult);
}
const existsAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
return loader.exists(name, from)
.then((exists) => {
existsCache.set(name, exists);
if (!exists) {
return existsAtIndex(index + 1);
}
else {
return true;
}
});
}
else {
return Promise.resolve(false);
}
};
return existsAtIndex(0).then((exists) => {
existsCache.set(name, exists);
return exists;
});
},
resolve: (name, from) => {
const resolveAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
return loader.exists(name, from)
.then((exists) => {
if (!exists) {
return resolveAtIndex(index + 1);
}
else {
return loader.resolve(name, from);
}
})
.then((key) => {
if (key === null) {
return resolveAtIndex(index + 1);
}
return key;
});
}
else {
return Promise.resolve(null);
}
};
return resolveAtIndex(0)
.then((key) => {
if (key) {
return key;
}
else {
return null;
}
});
},
getSource: (name, from) => {
const getSourceContextAtIndex = (index) => {
if (index < loaders.length) {
let loader = loaders[index];
return loader.getSource(name, from)
.then((source) => {
if (source === null) {
return getSourceContextAtIndex(index + 1);
}
return source;
});
}
else {
return Promise.resolve(null);
}
};
return getSourceContextAtIndex(0)
.then((source) => {
if (source) {
return source;
}
else {
return null;
}
});
},
isFresh: (name, time, from) => {
const isFreshAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
return loader.isFresh(name, time, from)
.then((isFresh) => {
if (isFresh === null) {
return isFreshAtIndex(index + 1);
}
return isFresh;
});
}
else {
return Promise.resolve(null);
}
};
return isFreshAtIndex(0);
}
};
return loader;
};
const createSynchronousChainLoader = (loaders) => {
let existsCache = new Map();
const addLoader = (loader) => {
loaders.push(loader);
existsCache = new Map();
};
const loader = {
get loaders() {
return loaders;
},
addLoader,
exists: (name, from) => {
const cachedResult = existsCache.get(name);
if (cachedResult) {
return cachedResult;
}
const existsAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
const exists = loader.exists(name, from);
existsCache.set(name, exists);
if (!exists) {
return existsAtIndex(index + 1);
}
else {
return true;
}
}
else {
return false;
}
};
const exists = existsAtIndex(0);
existsCache.set(name, exists);
return exists;
},
resolve: (name, from) => {
const resolveAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
const exists = loader.exists(name, from);
const key = exists ? loader.resolve(name, from) : resolveAtIndex(index + 1);
if (key === null) {
return resolveAtIndex(index + 1);
}
return key;
}
else {
return null;
}
};
const key = resolveAtIndex(0);
if (key) {
return key;
}
else {
return null;
}
},
getSource: (name, from) => {
const getSourceContextAtIndex = (index) => {
if (index < loaders.length) {
let loader = loaders[index];
const source = loader.getSource(name, from);
if (source === null) {
return getSourceContextAtIndex(index + 1);
}
return source;
}
else {
return null;
}
};
const source = getSourceContextAtIndex(0);
if (source) {
return source;
}
else {
return null;
}
},
isFresh: (name, time, from) => {
const isFreshAtIndex = (index) => {
if (index < loaders.length) {
const loader = loaders[index];
const isFresh = loader.isFresh(name, time, from);
if (isFresh === null) {
return isFreshAtIndex(index + 1);
}
return isFresh;
}
else {
return null;
}
};
return isFreshAtIndex(0);
}
};
return loader;
};
const isAMarkup = (candidate) => {
return candidate !== null
&& candidate !== undefined
&& candidate.charset !== undefined
&& candidate.content !== undefined
&& candidate.count !== undefined // todo: we should not test getter values but actual property existence
&& candidate.toJSON !== undefined
&& candidate.toString !== undefined;
};
const createMarkup = (content, charset = 'UTF-8') => {
return {
get content() {
return content;
},
get charset() {
return charset;
},
get count() {
return content.length;
},
toString() {
return content.toString();
},
toJSON() {
return content.toString();
}
};
};
const getChildren = (node) => {
return Object.entries(node.children);
};
const getChildrenCount = (node) => {
return Object.keys(node.children).length;
};
const createBaseNode = (type, attributes = {}, children = {}, line = 0, column = 0, tag = null) => {
return {
attributes,
children,
column,
line,
tag,
type
};
};
/**
* Create a node acting as a container for the passed list of indexed nodes.
*
* @param children The children of the created node
* @param line The line of the created node
* @param column The column of the created node
* @param tag The tag of the created node
*/
const createNode = (children = {}, line = 0, column = 0, tag = null) => {
return createBaseNode(null, {}, children, line, column, tag);
};
const createApplyNode = (filters, body, line, column) => {
return createBaseNode("apply", {}, {
body,
filters
}, line, column, 'apply');
};
const createAutoEscapeNode = (strategy, body, line, column, tag) => {
return createBaseNode("auto_escape", {
strategy
}, {
body
}, line, column, tag);
};
const createBlockNode = (name, body, line, column, tag = null) => {
return createBaseNode("block", { name }, { body }, line, column, tag);
};
const createBlockReferenceNode = (name, line, column, tag) => {
return createBaseNode("block_reference", {
name
}, {}, line, column, tag);
};
const createCheckSecurityNode = (usedFilters, usedTags, usedFunctions, line, column) => {
return createBaseNode("check_security", {
usedFilters,
usedTags,
usedFunctions
}, {}, line, column);
};
const createCheckToStringNode = (value, line, column) => {
return createBaseNode("check_to_string", {}, {
value
}, line, column);
};
const createCommentNode = (data, line, column) => {
return createBaseNode("comment", {
data
}, {}, line, column);
};
const createDeprecatedNode = (message, line, column, tag) => {
return createBaseNode("deprecated", {}, {
message
}, line, column, tag);
};
const createDoNode = (body, line, column, tag) => {
return createBaseNode("do", {}, {
body
}, line, column, tag);
};
const createFlushNode = (line, column, tag) => {
return createBaseNode("flush", {}, {}, line, column, tag);
};
const createForLoopNode = (line, column, tag) => {
return createBaseNode("for_loop", {
hasAnIf: false,
hasAnElse: false
}, {}, line, column, tag);
};
const createIfNode = (testNode, elseNode, line, column, tag = null) => {
const children = {
tests: testNode
};
if (elseNode) {
children.else = elseNode;
}
return createBaseNode('if', {}, children, line, column, tag);
};
const createForNode = (keyTarget, valueTarget, sequence, ifExpression, body, elseNode, line, column, tag) => {
const loop = createForLoopNode(line, column, tag);
const bodyChildren = {};
let i = 0;
bodyChildren[i++] = body;
bodyChildren[i++] = loop;
let actualBody = createNode(bodyChildren, line, column);
if (ifExpression) {
const ifChildren = {};
let i = 0;
ifChildren[i++] = ifExpression;
ifChildren[i++] = actualBody;
actualBody = createIfNode(createNode(ifChildren, line, column), null, line, column);
loop.attributes.hasAnIf = true;
}
const children = {
keyTarget: keyTarget,
valueTarget: valueTarget,
sequence: sequence,
body: actualBody
};
if (elseNode) {
children.else = elseNode;
loop.attributes.hasAnElse = true;
}
return createBaseNode("for", {
hasAnIf: ifExpression !== null
}, children, line, column, tag);
};
const createImportNode = (templateName, alias, global, line, column, tag) => {
return createBaseNode("import", {
global
}, {
templateName,
alias
}, line, column, tag);
};
const createBaseIncludeNode = (type, attributes, children, line, column, tag) => {
return createBaseNode(type, attributes, children, line, column, tag);
};
const createLineNode = (data, line, column, tag) => {
return createBaseNode("line", {
data
}, {}, line, column, tag);
};
const VARARGS_NAME = 'varargs';
const createMacroNode = (name, body, macroArguments, line, column, tag) => {
return createBaseNode("macro", {
name
}, {
body,
arguments: macroArguments
}, line, column, tag);
};
const createPrintNode = (expression, line, column) => {
return createBaseNode("print", {}, {
expression: expression
}, line, column, null);
};
const createSandboxNode = (body, line, column, tag) => {
return createBaseNode("sandbox", {}, {
body
}, line, column, tag);
};
const createBaseExpressionNode = createBaseNode;
const createConstantNode = (value, line, column) => {
return createBaseExpressionNode("constant", {
value
}, {}, line, column);
};
const createSetNode = (captures, names, values, line, column, tag) => {
const setNode = createBaseNode("set", {
captures
}, {
names,
values
}, line, column, tag);
/*
* Optimizes the node when capture is used for a large block of text.
*
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo");
*/
if (setNode.attributes.captures) {
const values = setNode.children.values;
if (values.type === "text") {
setNode.children.values = createNode({
0: createConstantNode(values.attributes.data, values.line, values.column)
}, values.line, values.column);
setNode.attributes.captures = false;
}
}
return setNode;
};
const createSpacelessNode = (body, line, column, tag) => {
return createBaseNode("spaceless", {}, {
body
}, line, column, tag);
};
const createTemplateNode = (body, parent, blocks, macros, traits, embeddedTemplates, source, line, column) => {
const children = {
body,
blocks,
macros,
traits,
securityCheck: createNode()
};
if (parent !== null) {
children.parent = parent;
}
const baseNode = createBaseNode("template", {
index: 0,
source
}, children, line, column);
return Object.assign(Object.assign({}, baseNode), { get embeddedTemplates() {
return embeddedTemplates;
} });
};
const createBaseTextNode = (type, data, line, column, tag = null) => {
return createBaseNode(type, {
data
}, {}, line, column, tag);
};
const createTextNode = (data, line, column) => createBaseTextNode("text", data, line, column);
const createTraitNode = (template, targets, line, column) => {
return Object.assign({}, createBaseNode("trait", {}, {
template,
targets
}, line, column));
};
const createVerbatimNode = (data, line, column, tag) => createBaseTextNode("verbatim", data, line, column, tag);
const createWithNode = (body, variables, only, line, column, tag) => {
const children = {
body
};
if (variables) {
children.variables = variables;
}
return createBaseNode("with", {
only
}, children, line, column, tag);
};
const getRecordSize = (record) => {
return Object.keys(record).length;
};
const pushToRecord = (record, value) => {
const size = getRecordSize(record);
record[size] = value;
};
const createBaseArrayNode = (type, elements, line, column) => {
const children = {};
for (const { key, value } of elements) {
pushToRecord(children, key);
pushToRecord(children, value);
}
return createBaseExpressionNode(type, {}, children, line, column);
};
const createArrayNode = (elements, line, column) => {
let index = 0;
const baseNode = createBaseArrayNode("array", elements.map(({ key, value }) => {
return {
key: key || createConstantNode(index++, line, column),
value
};
}), line, column);
return Object.assign({}, baseNode);
};
const createArrowFunctionNode = (body, names, line, column) => {
return createBaseExpressionNode("arrow_function", {}, {
body,
names
}, line, column);
};
// todo: probably a useless node
const createAssignmentNode = (name, line, column) => {
return createBaseNode("assignment", {
name
}, {}, line, column);
};
const createAttributeAccessorNode = (target, attribute, methodArguments, type, line, column) => {
return createBaseExpressionNode("attribute_accessor", {
isOptimizable: true,
type,
shouldTestExistence: false
}, {
target,
attribute,
arguments: methodArguments
}, line, column);
};
const cloneGetAttributeNode = (attributeAccessorNode) => {
const { children, attributes, line, column } = attributeAccessorNode;
const { arguments: methodArguments, attribute, target } = children;
const { type } = attributes;
return createAttributeAccessorNode(target, attribute, methodArguments, type, line, column);
};
const createBaseBinaryNode = (type, operands, line, column) => {
const baseNode = createBaseExpressionNode(type, {}, {
left: operands[0],
right: operands[1]
}, line, column);
return Object.assign({}, baseNode);
};
const createBinaryNodeFactory = (type) => {
const factory = (operands, line, column) => {
const baseNode = createBaseBinaryNode(type, operands, line, column);
return Object.assign({}, baseNode);
};
return factory;
};
const createBlockFunctionNode = (name, template, line, column, tag) => {
const children = {
name
};
if (template) {
children.template = template;
}
return createBaseExpressionNode("block_function", {
shouldTestExistence: false
}, children, line, column, tag);
};
const cloneBlockReferenceExpressionNode = (blockFunctionNode) => {
return createBlockFunctionNode(blockFunctionNode.children.name, blockFunctionNode.children.template || null, blockFunctionNode.line, blockFunctionNode.column);
};
const createBaseCallNode = (type, operatorName, operand, callArguments, line, column) => {
let children = {
arguments: callArguments
};
if (operand !== null) {
children.operand = operand;
}
return createBaseExpressionNode(type, {
operatorName
}, children, line, column);
};
const createBaseConditionalNode = (type, expr1, expr2, expr3, line, column) => {
return createBaseExpressionNode(type, {}, {
expr1, expr2, expr3
}, line, column);
};
const createConditionalNode = (expr1, expr2, expr3, line, column) => createBaseConditionalNode("conditional", expr1, expr2, expr3, line, column);
const createEscapeNode = (body, strategy) => {
return createBaseExpressionNode("escape", {
strategy
}, {
body
}, body.line, body.column);
};
const createHashNode = (elements, line, column) => {
return createBaseArrayNode("hash", elements, line, column);
};
const createMethodCallNode = (operand, methodName, methodArguments, line, column) => {
return createBaseExpressionNode("method_call", {
methodName,
shouldTestExistence: false
}, {
operand,
arguments: methodArguments
}, line, column);
};
const cloneMethodCallNode = (methodCallNode) => {
return createMethodCallNode(methodCallNode.children.operand, methodCallNode.attributes.methodName, methodCallNode.children.arguments, methodCallNode.line, methodCallNode.column);
};
const createNameNode = (name, line, column) => {
const attributes = {
name,
isAlwaysDefined: false,
shouldIgnoreStrictCheck: false,
shouldTestExistence: false
};
return createBaseNode("name", attributes, {}, line, column);
};
const cloneNameNode = (nameNode) => {
return createNameNode(nameNode.attributes.name, nameNode.line, nameNode.column);
};
const createUnaryNodeFactory = (type) => {
const factory = (operand, line, column) => {
const baseNode = createBaseUnaryNode(type, operand, line, column);
return Object.assign({}, baseNode);
};
return factory;
};
const createBaseUnaryNode = (type, operand, line, column) => {
const baseNode = createBaseExpressionNode(type, {}, {
operand
}, line, column);
return Object.assign({}, baseNode);
};
const createNotNode = createUnaryNodeFactory("not");
const createAndNode = createBinaryNodeFactory("and");
const createTestNode = (operand, testName, testArguments, line, column) => {
return createBaseCallNode("test", testName, operand, testArguments, line, column);
};
const createNullishCoalescingNode = (operands, line, column) => {
const [left, right] = operands;
if (left.type === "name") {
left.attributes.isAlwaysDefined = true;
}
const testNode = createAndNode([
createTestNode(left, "defined", createArrayNode([], line, column), line, column),
createNotNode(createTestNode(left, 'null', createArrayNode([], line, column), line, column), line, column)
], line, column);
return createBaseConditionalNode("nullish_coalescing", testNode, left, right, line, column);
};
const createParentFunctionNode = (name, line, column) => {
return createBaseExpressionNode("parent_function", {
name,
//output: false
}, {}, line, column);
};
const createSpreadNode = (iterable, line, column) => {
return createBaseExpressionNode("spread", {}, {
iterable
}, line, column);
};
const createAddNode = createBinaryNodeFactory("add");
const createBitwiseAndNode = createBinaryNodeFactory("bitwise_and");
const createBitwiseOrNode = createBinaryNodeFactory("bitwise_or");
const createBitwiseXorNode = createBinaryNodeFactory("bitwise_xor");
const createConcatenateNode = createBinaryNodeFactory("concatenate");
const createDivideAndFloorNode = createBinaryNodeFactory("divide_and_floor");
const createDivideNode = createBinaryNodeFactory("divide");
const createEndsWithNode = createBinaryNodeFactory("ends_with");
const createHasEveryNode = createBinaryNodeFactory("has_every");
const createHasSomeNode = createBinaryNodeFactory("has_some");
const createIsEqualNode = createBinaryNodeFactory("is_equal_to");
const createIsGreaterThanNode = createBinaryNodeFactory("is_greater_than");
const createIsGreaterThanOrEqualToNode = createBinaryNodeFactory("is_greater_than_or_equal_to");
const createIsInNode = createBinaryNodeFactory("is_in");
const createIsLessThanNode = createBinaryNodeFactory("is_less_than");
const createIsLessThanOrEqualToNode = createBinaryNodeFactory("is_less_than_or_equal_to");
const createIsNotEqualToNode = createBinaryNodeFactory("is_not_equal_to");
const createIsNotInNode = createBinaryNodeFactory("is_not_in");
const createMatchesNode = createBinaryNodeFactory("matches");
const createModuloNode = createBinaryNodeFactory("modulo");
const createMultiplyNode = createBinaryNodeFactory("multiply");
const createOrNode = createBinaryNodeFactory("or");
const createPowerNode = createBinaryNodeFactory("power");
const createRangeNode = createBinaryNodeFactory("range");
const createStartsWithNode = createBinaryNodeFactory("starts_with");
const createSubtractNode = createBinaryNodeFactory("subtract");
const createFilterNode = (operand, filterName, filterArguments, line, column) => {
return createBaseCallNode("filter", filterName, operand, filterArguments, line, column);
};
const createFunctionNode = (functionName, functionArguments, line, column) => {
return createBaseCallNode("function", functionName, null, functionArguments, line, column);
};
const createNegativeNode = createUnaryNodeFactory("negative");
const createPositiveNode = createUnaryNodeFactory("positive");
const createEmbedNode = (attributes, children, line, column, tag) => {
return createBaseIncludeNode("embed", attributes, children, line, column, tag);
};
const createIncludeNode = (attributes, children, line, column, tag) => {
return createBaseIncludeNode("include", attributes, children, line, column, tag);
};
/**
* Converts input to Map.
*
* @param {*} thing
* @returns {Map<any, any>}
*/
const iteratorToMap = (thing) => {
if (thing.entries) {
return new Map(thing.entries());
}
else {
const result = new Map();
if (typeof thing[Symbol.iterator] === 'function') {
let i = 0;
for (const value of thing) {
result.set(i++, value);
}
}
else {
for (const key in thing) {
result.set(key, thing[key]);
}
}
return result;
}
};
const iterableToMap = iteratorToMap;
function isAMapLike(candidate) {
return candidate !== null &&
candidate !== undefined &&
candidate.delete !== undefined &&
candidate.get !== undefined &&
candidate.has !== undefined &&
candidate.set !== undefined &&
candidate.entries !== undefined;
}
const every = async (iterable, comparator) => {
if (Array.isArray(iterable)) {
iterable = iteratorToMap(iterable);
}
for (const [key, value] of iterable) {
if (await comparator(value, key) === false) {
return false;
}
}
return true;
};
const everySynchronously = (iterable, comparator) => {
if (Array.isArray(iterable)) {
iterable = iteratorToMap(iterable);
}
for (const [key, value] of iterable) {
if (comparator(value, key) === false) {
return false;
}
}
return true;
};
const some = async (iterable, comparator) => {
if (Array.isArray(iterable)) {
iterable = iteratorToMap(iterable);
}
for (const [key, value] of iterable) {
if (await comparator(value, key) === true) {
return true;
}
}
return false;
};
const someSynchronously = (iterable, comparator) => {
if (Array.isArray(iterable)) {
iterable = iteratorToMap(iterable);
}
for (const [key, value] of iterable) {
if (comparator(value, key) === true) {
return true;
}
}
return false;
};
/**
* Compare by conforming to PHP loose comparisons rules
*
* @see http://php.net/manual/en/types.comparisons.php
* @see https://stackoverflow.com/questions/47969711/php-algorithm-loose-equality-comparison
*/
function compare(firstOperand, secondOperand) {
// Array<any>
if (Array.isArray(firstOperand)) {
firstOperand = iteratorToMap(firstOperand);
}
if (Array.isArray(secondOperand)) {
secondOperand = iteratorToMap(secondOperand);
}
// null
if (firstOperand === null) {
return compareToNull(secondOperand);
}
if (secondOperand === null) {
return compareToNull(firstOperand);
}
// boolean
if (typeof firstOperand === 'boolean') {
return compareToBoolean(firstOperand, secondOperand);
}
if (typeof secondOperand === 'boolean') {
return compareToBoolean(secondOperand, firstOperand);
}
// number
if (typeof firstOperand === 'number') {
return compareToNumber(firstOperand, secondOperand);
}
if (typeof secondOperand === 'number') {
return compareToNumber(secondOperand, firstOperand);
}
// TwingMarkup
if (isAMarkup(firstOperand)) {
firstOperand = firstOperand.toString();
}
if (isAMarkup(secondOperand)) {
secondOperand = secondOperand.toString();
}
// Buffer
if (Buffer.isBuffer(firstOperand)) {
firstOperand = firstOperand.toString();
}
if (Buffer.isBuffer(secondOperand)) {
secondOperand = secondOperand.toString();
}
// Map
if (isAMapLike(firstOperand)) {
return compareToMap(firstOperand, secondOperand);
}
// string
if (typeof firstOperand === 'string') {
return compareToString(firstOperand, secondOperand);
}
// date
if (firstOperand instanceof luxon.DateTime) {
return compareToDateTime(firstOperand, secondOperand);
}
// fallback to strict comparison
return firstOperand === secondOperand;
}
/**
* Compare a Map to something else by conforming to PHP loose comparisons rules
* ┌─────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬─────────┬───────┬───────┐
* │ │ TRUE │ FALSE │ 1 │ 0 │ -1 │ "1" │ "0" │ "-1" │ NULL │ [] │ ["php"] | "php" │ "" │
* ├─────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼─────────┼───────┼───────┤
* │ [] │ FALSE │ TRUE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ TRUE │ TRUE │ FALSE │ FALSE │ FALSE |
* │ ["php"] │ TRUE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ TRUE │ FALSE │ FALSE |
* └─────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴─────────┴───────┴───────┘
*/
function compareToMap(firstOperand, secondOperand) {
if (firstOperand.size === 0) {
return isAMapLike(secondOperand) && (secondOperand.size === 0);
}
else {
if (!isAMapLike(secondOperand)) {
return false;
}
else if (firstOperand.size !== secondOperand.size) {
return false;
}
let result = false;
for (let [i, valueItem] of firstOperand) {
let compareItem = secondOperand.get(i);
result = compare(valueItem, compareItem);
if (!result) {
break;
}
}
return result;
}
}
/**
* Compare a boolean to something else by conforming to PHP loose comparisons rules
* ┌─────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬─────────┬───────┬───────┐
* │ │ TRUE │ FALSE │ 1 │ 0 │ -1 │ "1" │ "0" │ "-1" │ NULL │ array() │ "php" │ "" │
* ├─────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼─────── ─┼───────┼───────┤
* │ TRUE │ TRUE │ FALSE │ TRUE │ FALSE │ TRUE │ TRUE │ FALSE │ TRUE │ FALSE │ FALSE │ TRUE │ FALSE │
* │ FALSE │ FALSE │ TRUE │ FALSE │ TRUE │ FALSE │ FALSE │ TRUE │ FALSE │ TRUE │ TRUE │ FALSE │ TRUE │
* └─────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴─────────┴───────┴───────┘
*/
function compareToBoolean(firstOperand, secondOperand) {
if (secondOperand instanceof luxon.DateTime) {
return firstOperand === true;
}
if (typeof secondOperand === 'boolean') {
return firstOperand === secondOperand;
}
if (typeof secondOperand === 'number') {
return firstOperand === (secondOperand !== 0);
}
if (typeof secondOperand === 'string') {
if (secondOperand.length > 1) {
return firstOperand;
}
else {
let float = parseFloat(secondOperand);
if (!isNaN(float)) {
return firstOperand === (float !== 0);
}
else {
return firstOperand === (secondOperand.length > 0);
}
}
}
if (isAMapLike(secondOperand)) {
return firstOperand === secondOperand.size > 0;
}
return firstOperand === true;
}
/**
* Compare a DateTime to something else by conforming to PHP loose comparisons rules
* ┌─────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬─────────┬───────┬───────┬───────┬───────┐
* │ │ TRUE │ FALSE │ 1 │ 0 │ -1 │ "1" │ "0" │ "-1" │ NULL │ [] │ ["php"] | "php" │ "" │ NOW | LATER |
* ├─────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼─────────┼───────┼───────┼───────┼───────┤
* │ NOW │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ TRUE │ FALSE │
* │ LATER │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ FALSE │ TRUE │
* └─────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴─────────┴───────┴───────┴───────┴───────┘
*/
function compareToDateTime(firstOperand, secondOperand) {
if (secondOperand instanceof luxon.DateTime) {
return firstOperand.valueOf() === secondOperand.valueOf();
}
return false;
}
/**
* Compare null to something else by conforming to PHP loose comparisons rules
* ┌─────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬─────────┬───────┬───────┐
* │ │ TRUE │ FALSE │ 1 │ 0 │ -1 │ "1" │ "0" │ "-1" │ NULL │ [] │ ["php"] | "php" │ "" │
* ├─────────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼───────┼─────────┼───────┼───────┤
* │ NULL │ FALSE │ TRUE │ FALSE │ TRUE │ FALSE │ FALSE │ FALSE │ FALSE │ TRUE │ TRUE │ FALSE │ FALSE │ TRUE |
* └─────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴─────────┴───────┴───────┘
*/
function compareToNull(value) {
if (typeof value === 'boolean') {
return (value === false);
}
if (typeof valu