twing
Version:
First-class Twig engine for Node.js
399 lines (398 loc) • 16.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAttributeSynchronously = exports.getAttribute = void 0;
const map_like_1 = require("./map-like");
const examine_object_1 = require("./examine-object");
const is_plain_object_1 = require("./is-plain-object");
const get_1 = require("./get");
const php_1 = require("./php");
const isObject = require('isobject');
/**
* Returns the attribute value for a given array/object.
*
* @param environment
* @param {*} object The object or array from where to get the item
* @param {*} attribute The item to get from the array or object
* @param {Map<any, any>} methodArguments A map of arguments to pass if the item is an object method
* @param {string} type The type of attribute (@see Twig_Template constants)
* @param {boolean} shouldTestExistence Whether this is only a defined check
* @param {boolean} shouldIgnoreStrictCheck Whether to ignore the strict attribute check or not
* @param sandboxed
* @param strict
*
* @return {Promise<any>} The attribute value, or a boolean when isDefinedTest is true, or null when the attribute is not set and ignoreStrictCheck is true
*
* @throw {TwingErrorRuntime} if the attribute does not exist and Twing is running in strict mode and isDefinedTest is false
*/
const getAttribute = (environment, object, attribute, methodArguments, type, shouldTestExistence, shouldIgnoreStrictCheck, sandboxed, strict) => {
const { sandboxPolicy } = environment;
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !strict : shouldIgnoreStrictCheck;
const _do = () => {
let message;
// ANY_CALL or ARRAY_CALL
if (type !== "method") {
let arrayItem;
if ((0, php_1.isBoolean)(attribute)) {
arrayItem = attribute ? 1 : 0;
}
else if ((0, php_1.isFloat)(attribute)) {
arrayItem = parseInt(attribute);
}
else {
arrayItem = attribute;
}
if (object) {
if (((0, map_like_1.isAMapLike)(object) && object.has(arrayItem))
|| (Array.isArray(object) && (typeof arrayItem === "number") && (object.length > arrayItem))
|| ((0, is_plain_object_1.isPlainObject)(object) && Reflect.has(object, arrayItem))) {
if (shouldTestExistence) {
return true;
}
if (type !== "array" && sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return (0, get_1.get)(object, arrayItem);
}
}
if ((type === "array")
|| ((0, map_like_1.isAMapLike)(object))
|| (Array.isArray(object))
|| (object === null)
|| (typeof object !== 'object')) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
// object is null
if (type === "array") {
message = `Impossible to access a key ("${attribute}") on a null variable.`;
}
else {
message = `Impossible to access an attribute ("${attribute}") on a null variable.`;
}
}
else if ((0, map_like_1.isAMapLike)(object)) {
if (object.size < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
}
else {
message = `Index "${arrayItem}" is out of bounds for array [${[...object.values()]}].`;
}
}
else if (Array.isArray(object)) {
if (object.length < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
}
else {
message = `Index "${arrayItem}" is out of bounds for array [${[...object]}].`;
}
}
else if (type === "array") {
// object is another kind of object
message = `Impossible to access a key ("${attribute}") on a ${typeof object} variable ("${object.toString()}").`;
}
else {
// object is a primitive
message = `Impossible to access an attribute ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
}
// ANY_CALL or METHOD_CALL
if ((object === null) || (!isObject(object)) || ((0, map_like_1.isAMapLike)(object))) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
message = `Impossible to invoke a method ("${attribute}") on a null variable.`;
}
else if ((0, map_like_1.isAMapLike)(object) || Array.isArray(object)) {
message = `Impossible to invoke a method ("${attribute}") on an array.`;
}
else {
message = `Impossible to invoke a method ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
// object property
if (type !== "method") {
if (Reflect.has(object, attribute) && (typeof object[attribute] !== 'function')) {
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return (0, get_1.get)(object, attribute);
}
}
// object method
// precedence: getXxx() > isXxx() > hasXxx()
let methods = [];
for (let property of (0, examine_object_1.examineObject)(object)) {
let candidate = object[property];
if (typeof candidate === 'function') {
methods.push(property);
}
}
methods.sort();
let lcMethods = methods.map((method) => {
return method.toLowerCase();
});
let candidates = new Map();
for (let i = 0; i < methods.length; i++) {
let method = methods[i];
let lcName = lcMethods[i];
candidates.set(method, method);
candidates.set(lcName, method);
let name = '';
if (lcName[0] === 'g' && lcName.indexOf('get') === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
}
else if (lcName[0] === 'i' && lcName.indexOf('is') === 0) {
name = method.substr(2);
lcName = lcName.substr(2);
}
else if (lcName[0] === 'h' && lcName.indexOf('has') === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
if (lcMethods.includes('is' + lcName)) {
continue;
}
}
else {
continue;
}
// skip get() and is() methods (in which case, name is empty)
if (name.length > 0) {
if (!candidates.has(name)) {
candidates.set(name, method);
}
if (!candidates.has(lcName)) {
candidates.set(lcName, method);
}
}
}
let itemAsString = attribute;
let method;
let lcItem;
if (candidates.has(attribute)) {
method = candidates.get(attribute);
}
else if (candidates.has(lcItem = itemAsString.toLowerCase())) {
method = candidates.get(lcItem);
}
else {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
throw new Error(`Neither the property "${attribute}" nor one of the methods ${attribute}()" or "get${attribute}()"/"is${attribute}()"/"has${attribute}()" exist and have public access in class "${object.constructor.name}".`);
}
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkMethodAllowed(object, method);
}
return (0, get_1.get)(object, method).apply(object, [...methodArguments.values()]);
};
try {
return Promise.resolve(_do());
}
catch (e) {
return Promise.reject(e);
}
};
exports.getAttribute = getAttribute;
const getAttributeSynchronously = (environment, object, attribute, methodArguments, type, shouldTestExistence, shouldIgnoreStrictCheck, sandboxed, strict) => {
const { sandboxPolicy } = environment;
shouldIgnoreStrictCheck = (shouldIgnoreStrictCheck === null) ? !strict : shouldIgnoreStrictCheck;
let message;
// ANY_CALL or ARRAY_CALL
if (type !== "method") {
let arrayItem;
if ((0, php_1.isBoolean)(attribute)) {
arrayItem = attribute ? 1 : 0;
}
else if ((0, php_1.isFloat)(attribute)) {
arrayItem = parseInt(attribute);
}
else {
arrayItem = attribute;
}
if (object) {
if (((0, map_like_1.isAMapLike)(object) && object.has(arrayItem))
|| (Array.isArray(object) && (typeof arrayItem === "number") && (object.length > arrayItem))
|| ((0, is_plain_object_1.isPlainObject)(object) && Reflect.has(object, arrayItem))) {
if (shouldTestExistence) {
return true;
}
if (type !== "array" && sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return (0, get_1.get)(object, arrayItem);
}
}
if ((type === "array")
|| ((0, map_like_1.isAMapLike)(object))
|| (Array.isArray(object))
|| (object === null)
|| (typeof object !== 'object')) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
// object is null
if (type === "array") {
message = `Impossible to access a key ("${attribute}") on a null variable.`;
}
else {
message = `Impossible to access an attribute ("${attribute}") on a null variable.`;
}
}
else if ((0, map_like_1.isAMapLike)(object)) {
if (object.size < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
}
else {
message = `Index "${arrayItem}" is out of bounds for array [${[...object.values()]}].`;
}
}
else if (Array.isArray(object)) {
if (object.length < 1) {
message = `Index "${arrayItem}" is out of bounds as the array is empty.`;
}
else {
message = `Index "${arrayItem}" is out of bounds for array [${[...object]}].`;
}
}
else if (type === "array") {
// object is another kind of object
message = `Impossible to access a key ("${attribute}") on a ${typeof object} variable ("${object.toString()}").`;
}
else {
// object is a primitive
message = `Impossible to access an attribute ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
}
// ANY_CALL or METHOD_CALL
if ((object === null) || (!isObject(object)) || ((0, map_like_1.isAMapLike)(object))) {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
if (object === null) {
message = `Impossible to invoke a method ("${attribute}") on a null variable.`;
}
else if ((0, map_like_1.isAMapLike)(object) || Array.isArray(object)) {
message = `Impossible to invoke a method ("${attribute}") on an array.`;
}
else {
message = `Impossible to invoke a method ("${attribute}") on a ${typeof object} variable ("${object}").`;
}
throw new Error(message);
}
// object property
if (type !== "method") {
if (Reflect.has(object, attribute) && (typeof object[attribute] !== 'function')) {
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkPropertyAllowed(object, attribute);
}
return (0, get_1.get)(object, attribute);
}
}
// object method
// precedence: getXxx() > isXxx() > hasXxx()
let methods = [];
for (let property of (0, examine_object_1.examineObject)(object)) {
let candidate = object[property];
if (typeof candidate === 'function') {
methods.push(property);
}
}
methods.sort();
let lcMethods = methods.map((method) => {
return method.toLowerCase();
});
let candidates = new Map();
for (let i = 0; i < methods.length; i++) {
let method = methods[i];
let lcName = lcMethods[i];
candidates.set(method, method);
candidates.set(lcName, method);
let name = '';
if (lcName[0] === 'g' && lcName.indexOf('get') === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
}
else if (lcName[0] === 'i' && lcName.indexOf('is') === 0) {
name = method.substr(2);
lcName = lcName.substr(2);
}
else if (lcName[0] === 'h' && lcName.indexOf('has') === 0) {
name = method.substr(3);
lcName = lcName.substr(3);
if (lcMethods.includes('is' + lcName)) {
continue;
}
}
else {
continue;
}
// skip get() and is() methods (in which case, name is empty)
if (name.length > 0) {
if (!candidates.has(name)) {
candidates.set(name, method);
}
if (!candidates.has(lcName)) {
candidates.set(lcName, method);
}
}
}
let itemAsString = attribute;
let method;
let lcItem;
if (candidates.has(attribute)) {
method = candidates.get(attribute);
}
else if (candidates.has(lcItem = itemAsString.toLowerCase())) {
method = candidates.get(lcItem);
}
else {
if (shouldTestExistence) {
return false;
}
if (shouldIgnoreStrictCheck) {
return;
}
throw new Error(`Neither the property "${attribute}" nor one of the methods ${attribute}()" or "get${attribute}()"/"is${attribute}()"/"has${attribute}()" exist and have public access in class "${object.constructor.name}".`);
}
if (shouldTestExistence) {
return true;
}
if (sandboxed) {
sandboxPolicy.checkMethodAllowed(object, method);
}
return (0, get_1.get)(object, method).apply(object, [...methodArguments.values()]);
};
exports.getAttributeSynchronously = getAttributeSynchronously;