mirror of
https://gitlab.com/nightlycommit/twing.git
synced 2025-01-18 08:46:50 +02:00
Merge branch 'issue-614' into 'milestone/7.0.0'
Resolve issue #614 See merge request nightlycommit/twing!607
This commit is contained in:
commit
ec4a05cbbc
@ -48,7 +48,7 @@
|
||||
"is-plain-object": "^2.0.4",
|
||||
"isobject": "^3.0.1",
|
||||
"levenshtein": "^1.0.5",
|
||||
"locutus": "^2.0.11",
|
||||
"locutus": "^2.0.31",
|
||||
"luxon": "^1.19.3",
|
||||
"pad": "^2.0.3",
|
||||
"regex-parser": "^2.2.8",
|
||||
|
11
src/lib.ts
11
src/lib.ts
@ -8,12 +8,11 @@ export type {TwingError} from "./lib/error";
|
||||
export type {TwingBaseError, TwingErrorLocation} from "./lib/error/base";
|
||||
export type {TwingParsingError} from "./lib/error/parsing";
|
||||
export type {TwingRuntimeError} from "./lib/error/runtime";
|
||||
export type {TwingTemplateLoadingError} from "./lib/error/loader";
|
||||
|
||||
export {isATwingError} from "./lib/error";
|
||||
export {createParsingError} from "./lib/error/parsing";
|
||||
export {createRuntimeError, isARuntimeError} from "./lib/error/runtime";
|
||||
export {createTemplateLoadingError, isATemplateLoadingError} from "./lib/error/loader";
|
||||
export {createRuntimeError} from "./lib/error/runtime";
|
||||
export {createTemplateLoadingError} from "./lib/error/loader";
|
||||
|
||||
// loader
|
||||
export type {
|
||||
@ -283,13 +282,7 @@ export type {
|
||||
} from "./lib/operator";
|
||||
export type {TwingOutputBuffer} from "./lib/output-buffer";
|
||||
export type {TwingParser, TwingParserOptions} from "./lib/parser";
|
||||
export type {TwingSandboxSecurityError} from "./lib/sandbox/security-error";
|
||||
export type {TwingSandboxSecurityPolicy} from "./lib/sandbox/security-policy";
|
||||
export type {TwingSandboxSecurityNotAllowedFilterError} from "./lib/sandbox/security-not-allowed-filter-error";
|
||||
export type {TwingSandboxSecurityNotAllowedFunctionError} from "./lib/sandbox/security-not-allowed-function-error";
|
||||
export type {TwingSandboxSecurityNotAllowedMethodError} from "./lib/sandbox/security-not-allowed-method-error";
|
||||
export type {TwingSandboxSecurityNotAllowedPropertyError} from "./lib/sandbox/security-not-allowed-property-error";
|
||||
export type {TwingSandboxSecurityNotAllowedTagError} from "./lib/sandbox/security-not-allowed-tag-error";
|
||||
export type {TwingSource} from "./lib/source";
|
||||
export type {TwingSourceMapRuntime} from "./lib/source-map-runtime";
|
||||
export type {
|
||||
|
@ -22,7 +22,6 @@ import {createSourceMapRuntime} from "./source-map-runtime";
|
||||
import {createSandboxSecurityPolicy, TwingSandboxSecurityPolicy} from "./sandbox/security-policy";
|
||||
import {TwingTemplate} from "./template";
|
||||
import {Settings as DateTimeSettings} from "luxon";
|
||||
import {TwingParsingError} from "./error/parsing";
|
||||
import {createLexer, TwingLexer} from "./lexer";
|
||||
import {TwingCache} from "./cache";
|
||||
import {createCoreExtension} from "./extension/core";
|
||||
@ -290,17 +289,7 @@ export const createEnvironment = (
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return parser.parse(stream);
|
||||
} catch (error: any) {
|
||||
const source = stream.source;
|
||||
|
||||
if (!(error as TwingParsingError).source) {
|
||||
(error as TwingParsingError).source = source.name;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
return parser.parse(stream);
|
||||
},
|
||||
render: (name, context, options) => {
|
||||
return environment.loadTemplate(name)
|
||||
|
@ -1,21 +1,15 @@
|
||||
import {TwingParsingError, parsingErrorName} from "./error/parsing";
|
||||
import {TwingRuntimeError, runtimeErrorName} from "./error/runtime";
|
||||
import {TwingTemplateLoadingError, templateLoadingError} from "./error/loader";
|
||||
import {TwingSandboxSecurityError, sandboxSecurityErrorName} from "./sandbox/security-error";
|
||||
|
||||
export type TwingError =
|
||||
| TwingTemplateLoadingError
|
||||
| TwingRuntimeError
|
||||
| TwingParsingError
|
||||
| TwingSandboxSecurityError
|
||||
;
|
||||
|
||||
export const isATwingError = (candidate: Error): candidate is TwingError => {
|
||||
return [
|
||||
templateLoadingError,
|
||||
parsingErrorName,
|
||||
runtimeErrorName,
|
||||
sandboxSecurityErrorName
|
||||
runtimeErrorName
|
||||
].includes((candidate as TwingError).name);
|
||||
};
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import type {TwingSource} from "../source";
|
||||
|
||||
export type TwingErrorLocation = {
|
||||
line: number;
|
||||
column: number;
|
||||
@ -7,14 +9,14 @@ export interface TwingBaseError<Name extends string> extends Error {
|
||||
readonly name: Name;
|
||||
readonly previous: any | undefined;
|
||||
readonly rootMessage: string;
|
||||
location: TwingErrorLocation | undefined;
|
||||
source: string | undefined;
|
||||
location: TwingErrorLocation;
|
||||
source: TwingSource;
|
||||
|
||||
appendMessage(message: string): void;
|
||||
}
|
||||
|
||||
export const createBaseError = <Name extends string>(
|
||||
name: Name, message: string, location?: TwingErrorLocation, source?: string, previous?: any
|
||||
name: Name, message: string, location: TwingErrorLocation, source: TwingSource, previous?: any
|
||||
): TwingBaseError<Name> => {
|
||||
const baseError = Error(message);
|
||||
|
||||
@ -22,20 +24,10 @@ export const createBaseError = <Name extends string>(
|
||||
|
||||
const error = Object.create(baseError, {
|
||||
location: {
|
||||
get: () => location,
|
||||
set: (value: TwingErrorLocation) => {
|
||||
location = value;
|
||||
|
||||
updateRepresentation();
|
||||
}
|
||||
get: () => location
|
||||
},
|
||||
source: {
|
||||
get: () => source,
|
||||
set: (value: string) => {
|
||||
source = value;
|
||||
|
||||
updateRepresentation();
|
||||
}
|
||||
get: () => source
|
||||
},
|
||||
previous: {
|
||||
value: previous
|
||||
@ -69,15 +61,11 @@ export const createBaseError = <Name extends string>(
|
||||
questionMark = true;
|
||||
}
|
||||
|
||||
if (source) {
|
||||
representation += ` in "${source}"`;
|
||||
}
|
||||
representation += ` in "${source.name}"`;
|
||||
|
||||
if (location !== undefined) {
|
||||
const {line, column} = location;
|
||||
|
||||
representation += ` at line ${line}, column ${column}`;
|
||||
}
|
||||
const {line, column} = location;
|
||||
|
||||
representation += ` at line ${line}, column ${column}`;
|
||||
|
||||
if (dot) {
|
||||
representation += '.';
|
||||
|
@ -1,15 +1,4 @@
|
||||
import {createBaseError, TwingErrorLocation, TwingBaseError} from "./base";
|
||||
|
||||
export const templateLoadingError = 'TwingTemplateLoadingError';
|
||||
|
||||
/**
|
||||
* Exception thrown when an error occurs during template loading.
|
||||
*/
|
||||
export interface TwingTemplateLoadingError extends TwingBaseError<typeof templateLoadingError> {
|
||||
|
||||
}
|
||||
|
||||
export const createTemplateLoadingError = (names: Array<string | null>, location?: TwingErrorLocation, source?: string, previous?: any): TwingTemplateLoadingError => {
|
||||
export const createTemplateLoadingError = (names: Array<string | null>): Error => {
|
||||
let message: string;
|
||||
|
||||
if (names.length === 1) {
|
||||
@ -20,13 +9,9 @@ export const createTemplateLoadingError = (names: Array<string | null>, location
|
||||
message = `Unable to find one of the following templates: "${names.join('", "')}".`;
|
||||
}
|
||||
|
||||
const error = createBaseError(templateLoadingError, message, location, source, previous);
|
||||
const error = Error(message);
|
||||
|
||||
Error.captureStackTrace(error, createTemplateLoadingError);
|
||||
|
||||
return error;
|
||||
};
|
||||
|
||||
export const isATemplateLoadingError = (candidate: any): candidate is TwingTemplateLoadingError => {
|
||||
return (candidate as TwingTemplateLoadingError).name === templateLoadingError;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {createBaseError, TwingErrorLocation, TwingBaseError} from "./base";
|
||||
import type {TwingSource} from "../source";
|
||||
|
||||
const Levenshtein = require('levenshtein');
|
||||
|
||||
@ -15,7 +16,7 @@ export interface TwingParsingError extends TwingBaseError<typeof parsingErrorNam
|
||||
}
|
||||
|
||||
export const createParsingError = (
|
||||
message: string, location?: TwingErrorLocation, source?: string, previous?: Error
|
||||
message: string, location: TwingErrorLocation, source: TwingSource, previous?: Error
|
||||
): TwingParsingError => {
|
||||
const baseError = createBaseError(parsingErrorName, message, location, source, previous);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {createBaseError, TwingErrorLocation, TwingBaseError} from "./base";
|
||||
import type {TwingSource} from "../source";
|
||||
|
||||
export const runtimeErrorName = 'TwingRuntimeError';
|
||||
|
||||
@ -6,11 +7,7 @@ export interface TwingRuntimeError extends TwingBaseError<typeof runtimeErrorNam
|
||||
|
||||
}
|
||||
|
||||
export const isARuntimeError = (candidate: Error): candidate is TwingRuntimeError => {
|
||||
return (candidate as TwingRuntimeError).name === runtimeErrorName;
|
||||
};
|
||||
|
||||
export const createRuntimeError = (message: string, location?: TwingErrorLocation, source?: string, previous?: Error): TwingRuntimeError => {
|
||||
export const createRuntimeError = (message: string, location: TwingErrorLocation, source: TwingSource, previous?: Error): TwingRuntimeError => {
|
||||
const error = createBaseError(runtimeErrorName, message, location, source, previous);
|
||||
|
||||
Error.captureStackTrace(error, createRuntimeError);
|
||||
|
@ -1,26 +1,12 @@
|
||||
import type {TwingEscapingStrategyHandler} from "../escaping-strategy";
|
||||
const phpBin2hex = require("locutus/php/strings/bin2hex");
|
||||
const phpLtrim = require('locutus/php/strings/ltrim');
|
||||
const strlen = require('utf8-binary-cutter').getBinarySize;
|
||||
const phpSprintf = require('locutus/php/strings/sprintf');
|
||||
|
||||
export const createCssEscapingStrategyHandler = (): TwingEscapingStrategyHandler => {
|
||||
return (value) => {
|
||||
value = value.replace(/[^a-zA-Z0-9]/ug, (matches: string) => {
|
||||
let char = matches;
|
||||
|
||||
// \xHH
|
||||
if (strlen(char) === 1) {
|
||||
let hex = phpLtrim(phpBin2hex(char).toUpperCase(), '0');
|
||||
|
||||
if (strlen(hex) === 0) {
|
||||
hex = '0';
|
||||
}
|
||||
|
||||
return '\\' + hex + ' ';
|
||||
}
|
||||
|
||||
// \uHHHH
|
||||
return '\\' + phpLtrim(phpBin2hex(char).toUpperCase(), '0') + ' ';
|
||||
value = value.replace(/[^a-zA-Z0-9]/ug, (character: string) => {
|
||||
const codePoint = character.codePointAt(0)!;
|
||||
|
||||
return phpSprintf('\\u%04X', codePoint);
|
||||
});
|
||||
|
||||
return value;
|
||||
|
@ -1,8 +1,6 @@
|
||||
import type {TwingEscapingStrategyHandler} from "../escaping-strategy";
|
||||
|
||||
const phpBin2hex = require("locutus/php/strings/bin2hex");
|
||||
const phpSprintf = require('locutus/php/strings/sprintf');
|
||||
const strlen = require('utf8-binary-cutter').getBinarySize;
|
||||
|
||||
export const createJsEscapingStrategyHandler = (): TwingEscapingStrategyHandler => {
|
||||
return (value) => {
|
||||
@ -30,16 +28,22 @@ export const createJsEscapingStrategyHandler = (): TwingEscapingStrategyHandler
|
||||
return shortMap.get(char);
|
||||
}
|
||||
|
||||
// \uHHHH
|
||||
char = phpBin2hex(char).toUpperCase();
|
||||
let codePoint = char.codePointAt(0)!;
|
||||
|
||||
if (strlen(char) <= 4) {
|
||||
return phpSprintf('\\u%04s', char);
|
||||
if (codePoint <= 0x10000) {
|
||||
return phpSprintf('\\u%04X', codePoint);
|
||||
}
|
||||
|
||||
// Split characters outside the BMP into surrogate pairs
|
||||
// https://tools.ietf.org/html/rfc2781.html#section-2.1
|
||||
codePoint = codePoint - 0x10000;
|
||||
|
||||
const high = 0xD800 | (codePoint >> 10);
|
||||
const low = 0xDC00 | (codePoint & 0x3FF);
|
||||
|
||||
return phpSprintf('\\u%04s\\u%04s', char.substr(0, 4), char.substr(4, 4));
|
||||
return phpSprintf('\\u%04X\\u%04X', high, low);
|
||||
});
|
||||
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {isPlainObject} from "../../../helpers/is-plain-object";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
@ -16,7 +15,7 @@ export const column: TwingCallable = (_executionContext, thing: any, columnKey:
|
||||
let map: Map<any, any>;
|
||||
|
||||
if (!isTraversable(thing) || isPlainObject(thing)) {
|
||||
return Promise.reject(createRuntimeError(`The column filter only works with arrays or "Traversable", got "${typeof thing}" as first argument.`));
|
||||
return Promise.reject(new Error(`The column filter only works with arrays or "Traversable", got "${typeof thing}" as first argument.`));
|
||||
} else {
|
||||
map = iteratorToMap(thing);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ export const escape: TwingCallable<[
|
||||
|
||||
const {template, environment} = executionContext;
|
||||
|
||||
// todo: probably we need to use traceable method
|
||||
return escapeValue(template, environment, value, strategy, environment.charset)
|
||||
.then((value) => {
|
||||
if (typeof value === "string") {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {mergeIterables} from "../../../helpers/merge-iterables";
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
@ -24,13 +23,13 @@ export const merge: TwingCallable = (_executionContext, iterable1: any, source:
|
||||
const isIterable1NullOrUndefined = (iterable1 === null) || (iterable1 === undefined);
|
||||
|
||||
if (isIterable1NullOrUndefined || (!isTraversable(iterable1) && (typeof iterable1 !== 'object'))) {
|
||||
return Promise.reject(createRuntimeError(`The merge filter only works on arrays or "Traversable", got "${!isIterable1NullOrUndefined ? typeof iterable1 : iterable1}".`));
|
||||
return Promise.reject(new Error(`The merge filter only works on arrays or "Traversable", got "${!isIterable1NullOrUndefined ? typeof iterable1 : iterable1}".`));
|
||||
}
|
||||
|
||||
const isSourceNullOrUndefined = (source === null) || (source === undefined);
|
||||
|
||||
if (isSourceNullOrUndefined || (!isTraversable(source) && (typeof source !== 'object'))) {
|
||||
return Promise.reject(createRuntimeError(`The merge filter only accepts arrays or "Traversable" as source, got "${!isSourceNullOrUndefined ? typeof source : source}".`));
|
||||
return Promise.reject(new Error(`The merge filter only accepts arrays or "Traversable" as source, got "${!isSourceNullOrUndefined ? typeof source : source}".`));
|
||||
}
|
||||
|
||||
return Promise.resolve(mergeIterables(iteratorToMap(iterable1), iteratorToMap(source)));
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {iteratorToHash} from "../../../helpers/iterator-to-hash";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpStrtr = require('locutus/php/strings/strtr');
|
||||
@ -18,7 +17,7 @@ export const replace: TwingCallable = (_executionContext,value: string | null, f
|
||||
if (isTraversable(from)) {
|
||||
from = iteratorToHash(from);
|
||||
} else if (typeof from !== 'object') {
|
||||
throw createRuntimeError(`The "replace" filter expects an hash or "Iterable" as replace values, got "${typeof from}".`);
|
||||
throw new Error(`The "replace" filter expects an hash or "Iterable" as replace values, got "${typeof from}".`);
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpRound = require('locutus/php/math/round');
|
||||
@ -21,7 +20,7 @@ export const round: TwingCallable = (_executionContext, value: any, precision: n
|
||||
}
|
||||
|
||||
if (method !== 'ceil' && method !== 'floor') {
|
||||
throw createRuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.');
|
||||
throw new Error('The round filter only supports the "common", "ceil", and "floor" methods.');
|
||||
}
|
||||
|
||||
const intermediateValue = value * Math.pow(10, precision);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {asort} from "../../../helpers/asort";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
@ -18,7 +17,7 @@ export const sort: TwingCallable<[
|
||||
arrow: ((a: any, b: any) => Promise<-1 | 0 | 1>) | null
|
||||
], Map<any, any>> = async (_executionContext, iterable, arrow)=> {
|
||||
if (!isTraversable(iterable)) {
|
||||
return Promise.reject(createRuntimeError(`The sort filter only works with iterables, got "${typeof iterable}".`));
|
||||
return Promise.reject(new Error(`The sort filter only works with iterables, got "${typeof iterable}".`));
|
||||
}
|
||||
|
||||
const map = iteratorToMap(iterable);
|
||||
|
@ -1,4 +1,3 @@
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
const phpTrim = require('locutus/php/strings/trim');
|
||||
@ -26,7 +25,7 @@ export const trim: TwingCallable = (_executionContext, string: string, character
|
||||
case 'right':
|
||||
return phpRightTrim(string, characterMask);
|
||||
default:
|
||||
throw createRuntimeError('Trimming side must be "left", "right" or "both".');
|
||||
throw new Error('Trimming side must be "left", "right" or "both".');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import {DateTime, Duration} from "luxon";
|
||||
import {modifyDate} from "../../../helpers/modify-date";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
/**
|
||||
@ -72,7 +71,7 @@ export const createDate = (
|
||||
}
|
||||
|
||||
if (!result || !result.isValid) {
|
||||
throw createRuntimeError(`Failed to parse date "${input}".`);
|
||||
throw new Error(`Failed to parse date "${input}".`);
|
||||
}
|
||||
|
||||
// now let's apply timezone
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {iteratorToMap} from "../../../helpers/iterator-to-map";
|
||||
import {mergeIterables} from "../../../helpers/merge-iterables";
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {isPlainObject} from "../../../helpers/is-plain-object";
|
||||
import {createContext} from "../../../context";
|
||||
import {createMarkup, TwingMarkup} from "../../../markup";
|
||||
@ -44,12 +43,10 @@ export const include: TwingCallable<[
|
||||
sourceMapRuntime,
|
||||
strict
|
||||
} = executionContext;
|
||||
const from = template.name;
|
||||
|
||||
if (!isPlainObject(variables) && !isTraversable(variables)) {
|
||||
const isVariablesNullOrUndefined = variables === null || variables === undefined;
|
||||
|
||||
return Promise.reject(createRuntimeError(`Variables passed to the "include" function or tag must be iterable, got "${!isVariablesNullOrUndefined ? typeof variables : variables}".`, undefined, from));
|
||||
return Promise.reject(new Error(`Variables passed to the "include" function or tag must be iterable, got "${!isVariablesNullOrUndefined ? typeof variables : variables}".`));
|
||||
}
|
||||
|
||||
variables = iteratorToMap(variables);
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {iconv} from "../../../helpers/iconv";
|
||||
import {isTraversable} from "../../../helpers/is-traversable";
|
||||
import {iteratorToArray} from "../../../helpers/iterator-to-array";
|
||||
import {createRuntimeError} from "../../../error/runtime";
|
||||
import {TwingCallable} from "../../../callable-wrapper";
|
||||
|
||||
const runes = require('runes');
|
||||
@ -79,7 +78,7 @@ export const random: TwingCallable = (executionContext, values: any | null, max:
|
||||
}
|
||||
|
||||
if (values.length < 1) {
|
||||
throw createRuntimeError('The random function cannot pick from an empty array.');
|
||||
return Promise.reject(new Error('The random function cannot pick from an empty array.'));
|
||||
}
|
||||
|
||||
return values[array_rand(values, 1)];
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {isAMarkup, TwingMarkup} from "../markup";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
import {TwingEnvironment} from "../environment";
|
||||
import {TwingEscapingStrategy} from "../escaping-strategy";
|
||||
import {TwingTemplate} from "../template";
|
||||
@ -28,7 +27,7 @@ export const escapeValue = (
|
||||
const strategyHandler = environment.escapingStrategyHandlers[strategy];
|
||||
|
||||
if (strategyHandler === undefined) {
|
||||
return Promise.reject(createRuntimeError(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`));
|
||||
return Promise.reject(new Error(`Invalid escaping strategy "${strategy}" (valid ones: ${Object.keys(environment.escapingStrategyHandlers).sort().join(', ')}).`));
|
||||
}
|
||||
|
||||
result = strategyHandler(value.toString(), charset || environment.charset, template.name);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import {isAMapLike} from "./map-like";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
import {examineObject} from "./examine-object";
|
||||
import {isPlainObject} from "./is-plain-object";
|
||||
import {get} from "./get";
|
||||
@ -124,7 +123,7 @@ export const getAttribute = (
|
||||
message = `Impossible to access an attribute ("${attribute}") on a ${typeof object} variable ("${object}").`;
|
||||
}
|
||||
|
||||
throw createRuntimeError(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +147,7 @@ export const getAttribute = (
|
||||
message = `Impossible to invoke a method ("${attribute}") on a ${typeof object} variable ("${object}").`;
|
||||
}
|
||||
|
||||
throw createRuntimeError(message);
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// object property
|
||||
@ -246,7 +245,7 @@ export const getAttribute = (
|
||||
return;
|
||||
}
|
||||
|
||||
throw createRuntimeError(`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}".`);
|
||||
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) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type {TwingContext} from "../context";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
|
||||
export const getContextValue = (
|
||||
charset: string,
|
||||
@ -40,7 +39,7 @@ export const getContextValue = (
|
||||
result = context.get(name);
|
||||
|
||||
if (result === undefined) {
|
||||
return Promise.reject(createRuntimeError(`Variable "${name}" does not exist.`));
|
||||
return Promise.reject(new Error(`Variable "${name}" does not exist.`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,16 @@
|
||||
import {isATwingError} from "../error";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
import type {TwingSource} from "../source";
|
||||
import {isATwingError} from "../error";
|
||||
|
||||
export function getTraceableMethod<M extends (...args: Array<any>) => Promise<any>>(method: M, line: number, column: number, templateName: string): M {
|
||||
export function getTraceableMethod<M extends (...args: Array<any>) => Promise<any>>(method: M, location: {
|
||||
line: number;
|
||||
column: number;
|
||||
}, templateSource: TwingSource): M {
|
||||
return ((...args: Array<any>) => {
|
||||
return method(...args)
|
||||
.catch((error) => {
|
||||
if (isATwingError(error)) {
|
||||
if (error.location === undefined) {
|
||||
error.location = {line, column};
|
||||
error.source = templateName;
|
||||
}
|
||||
} else {
|
||||
throw createRuntimeError(`An exception has been thrown during the rendering of a template ("${error.message}").`, {
|
||||
line,
|
||||
column
|
||||
}, templateName, error);
|
||||
if (!isATwingError(error)) {
|
||||
throw createRuntimeError(error.message, location, templateSource, error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
@ -75,7 +75,7 @@ export class TwingLexer extends Lexer {
|
||||
} catch (error: any) {
|
||||
const {message, line, column} = (error as SyntaxError);
|
||||
|
||||
throw createParsingError(message, {line, column}, source.name, error);
|
||||
throw createParsingError(message, {line, column}, source, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ export const executeNode: TwingNodeExecutor = (node, executionContext) => {
|
||||
executor = executeWithNode;
|
||||
}
|
||||
else {
|
||||
return Promise.reject(createRuntimeError(`Unrecognized node of type "${node.type}"`, node));
|
||||
return Promise.reject(createRuntimeError(`Unrecognized node of type "${node.type}"`, node, executionContext.template.source));
|
||||
}
|
||||
|
||||
return executor(node, executionContext);
|
||||
|
@ -9,7 +9,7 @@ export const executeBlockReferenceNode: TwingNodeExecutor<TwingBlockReferenceNod
|
||||
} = executionContext;
|
||||
const {name} = node.attributes;
|
||||
|
||||
const displayBlock = getTraceableMethod(template.displayBlock, node.line, node.column, template.name);
|
||||
const displayBlock = getTraceableMethod(template.displayBlock, node, template.source);
|
||||
|
||||
return displayBlock(
|
||||
{
|
||||
|
@ -1,41 +1,34 @@
|
||||
import {TwingNodeExecutor} from "../node-executor";
|
||||
import {TwingCheckSecurityNode} from "../node/check-security";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedFilterError,
|
||||
TwingSandboxSecurityNotAllowedFilterError
|
||||
} from "../sandbox/security-not-allowed-filter-error";
|
||||
import {TwingSandboxSecurityNotAllowedFunctionError} from "../sandbox/security-not-allowed-function-error";
|
||||
import {
|
||||
isASandboxSecurityNotAllowedTagError,
|
||||
TwingSandboxSecurityNotAllowedTagError
|
||||
} from "../sandbox/security-not-allowed-tag-error";
|
||||
import type {TwingNode} from "../node";
|
||||
import {createRuntimeError} from "../error/runtime";
|
||||
|
||||
export const executeCheckSecurityNode: TwingNodeExecutor<TwingCheckSecurityNode> = (node, executionContext) => {
|
||||
const {template, environment, sandboxed} = executionContext;
|
||||
const {usedTags, usedFunctions, usedFilters} = node.attributes;
|
||||
|
||||
try {
|
||||
sandboxed && environment.sandboxPolicy.checkSecurity(
|
||||
if (sandboxed) {
|
||||
const issue = environment.sandboxPolicy.checkSecurity(
|
||||
[...usedTags.keys()],
|
||||
[...usedFilters.keys()],
|
||||
[...usedFunctions.keys()]
|
||||
);
|
||||
} catch (error: any) {
|
||||
const supplementError = (error: TwingSandboxSecurityNotAllowedFilterError | TwingSandboxSecurityNotAllowedFunctionError | TwingSandboxSecurityNotAllowedTagError) => {
|
||||
error.source = template.name;
|
||||
|
||||
if (isASandboxSecurityNotAllowedTagError(error)) {
|
||||
error.location = usedTags.get(error.tagName);
|
||||
} else if (isASandboxSecurityNotAllowedFilterError(error)) {
|
||||
error.location = usedFilters.get(error.filterName)
|
||||
if (issue !== null) {
|
||||
const {type, token} = issue;
|
||||
|
||||
let node: TwingNode;
|
||||
|
||||
if (type === "tag") {
|
||||
node = usedTags.get(token)!;
|
||||
} else if (type === "filter") {
|
||||
node = usedFilters.get(token)!
|
||||
} else {
|
||||
error.location = usedFunctions.get(error.functionName);
|
||||
node = usedFunctions.get(token)!;
|
||||
}
|
||||
|
||||
throw createRuntimeError(issue.message, node, template.source);
|
||||
}
|
||||
|
||||
supplementError(error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
|
@ -20,7 +20,7 @@ export const executeCheckToStringNode: TwingNodeExecutor<TwingCheckToStringNode>
|
||||
}
|
||||
|
||||
return Promise.resolve(value);
|
||||
}, valueNode.line, valueNode.column, template.name)
|
||||
}, valueNode, template.source)
|
||||
|
||||
return assertToStringAllowed(value);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export const executeAttributeAccessorNode: TwingNodeExecutor<TwingAttributeAcces
|
||||
execute(attribute, executionContext),
|
||||
execute(methodArguments, executionContext)
|
||||
]).then(([target, attribute, methodArguments]) => {
|
||||
const traceableGetAttribute = getTraceableMethod(getAttribute, node.line, node.column, template.name);
|
||||
const traceableGetAttribute = getTraceableMethod(getAttribute, node, template.source);
|
||||
|
||||
return traceableGetAttribute(
|
||||
environment,
|
||||
|
@ -10,7 +10,7 @@ import {createRuntimeError} from "../../error/runtime";
|
||||
|
||||
export const executeBinaryNode: TwingNodeExecutor<TwingBaseBinaryNode<any>> = async (node, executionContext) => {
|
||||
const {left, right} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
const {nodeExecutor: execute, template} = executionContext;
|
||||
|
||||
switch (node.type) {
|
||||
case "add": {
|
||||
@ -161,5 +161,5 @@ export const executeBinaryNode: TwingNodeExecutor<TwingBaseBinaryNode<any>> = as
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(createRuntimeError(`Unrecognized binary node of type "${node.type}"`, node));
|
||||
return Promise.reject(createRuntimeError(`Unrecognized binary node of type "${node.type}"`, node, template.source));
|
||||
};
|
||||
|
@ -22,9 +22,8 @@ export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = a
|
||||
|
||||
const loadTemplate = getTraceableMethod(
|
||||
template.loadTemplate,
|
||||
templateNode.line,
|
||||
templateNode.column,
|
||||
template.name
|
||||
templateNode,
|
||||
template.source
|
||||
);
|
||||
|
||||
resolveTemplate = loadTemplate(executionContext, templateName);
|
||||
@ -35,14 +34,14 @@ export const executeBlockFunction: TwingNodeExecutor<TwingBlockFunctionNode> = a
|
||||
return resolveTemplate
|
||||
.then<Promise<boolean | string>>((templateOfTheBlock) => {
|
||||
if (node.attributes.shouldTestExistence) {
|
||||
const hasBlock = getTraceableMethod(templateOfTheBlock.hasBlock, node.line, node.column, template.name);
|
||||
const hasBlock = getTraceableMethod(templateOfTheBlock.hasBlock, node, template.source);
|
||||
|
||||
return hasBlock({
|
||||
...executionContext,
|
||||
context: context.clone()
|
||||
}, blockName, blocks);
|
||||
} else {
|
||||
const displayBlock = getTraceableMethod(templateOfTheBlock.displayBlock, node.line, node.column, template.name);
|
||||
const displayBlock = getTraceableMethod(templateOfTheBlock.displayBlock, node, template.source);
|
||||
|
||||
let useBlocks = templateNode === undefined;
|
||||
|
||||
|
@ -11,6 +11,7 @@ import {getKeyValuePairs} from "../../helpers/get-key-value-pairs";
|
||||
import {getTest} from "../../helpers/get-test";
|
||||
import {getFunction} from "../../helpers/get-function";
|
||||
import {getFilter} from "../../helpers/get-filter";
|
||||
import type {TwingTemplate} from "../../template";
|
||||
|
||||
const array_merge = require('locutus/php/array/array_merge');
|
||||
const snakeCase = require('snake-case');
|
||||
@ -21,6 +22,7 @@ const normalizeName = (name: string) => {
|
||||
|
||||
const getArguments = (
|
||||
node: TwingBaseCallNode<any>,
|
||||
template: TwingTemplate,
|
||||
argumentsNode: TwingArrayNode,
|
||||
acceptedArguments: Array<TwingCallableArgument>,
|
||||
isVariadic: boolean
|
||||
@ -44,7 +46,7 @@ const getArguments = (
|
||||
name = normalizeName(name);
|
||||
}
|
||||
else if (named) {
|
||||
throw createRuntimeError(`Positional arguments cannot be used after named arguments for ${callType} "${callName}".`, node);
|
||||
throw createRuntimeError(`Positional arguments cannot be used after named arguments for ${callType} "${callName}".`, node, template.source);
|
||||
}
|
||||
|
||||
parameters.set(name, {
|
||||
@ -69,7 +71,7 @@ const getArguments = (
|
||||
|
||||
if (parameter) {
|
||||
if (parameters.has(position)) {
|
||||
throw createRuntimeError(`Argument "${name}" is defined twice for ${callType} "${callName}".`, node);
|
||||
throw createRuntimeError(`Argument "${name}" is defined twice for ${callType} "${callName}".`, node, template.source);
|
||||
}
|
||||
|
||||
arguments_ = array_merge(arguments_, optionalArguments);
|
||||
@ -91,7 +93,7 @@ const getArguments = (
|
||||
arguments_.push(createConstantNode(callableParameter.defaultValue, node.line, node.column));
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`Value for argument "${name}" is required for ${callType} "${callName}".`, node);
|
||||
throw createRuntimeError(`Value for argument "${name}" is required for ${callType} "${callName}".`, node, template.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -119,7 +121,7 @@ const getArguments = (
|
||||
if (parameters.size > 0) {
|
||||
const unknownParameter = [...parameters.values()][0];
|
||||
|
||||
throw createRuntimeError(`Unknown argument${parameters.size > 1 ? 's' : ''} "${[...parameters.keys()].join('", "')}" for ${callType} "${callName}(${names.join(', ')})".`, unknownParameter.key);
|
||||
throw createRuntimeError(`Unknown argument${parameters.size > 1 ? 's' : ''} "${[...parameters.keys()].join('", "')}" for ${callType} "${callName}(${names.join(', ')})".`, unknownParameter.key, template.source);
|
||||
}
|
||||
|
||||
return arguments_;
|
||||
@ -149,13 +151,14 @@ export const executeCallNode: TwingNodeExecutor<TwingBaseCallNode<any>> = async
|
||||
}
|
||||
|
||||
if (callableWrapper === null) {
|
||||
throw createRuntimeError(`Unknown ${type} "${operatorName}".`, node);
|
||||
throw createRuntimeError(`Unknown ${type} "${operatorName}".`, node, template.source);
|
||||
}
|
||||
|
||||
const {operand, arguments: callArguments} = node.children;
|
||||
|
||||
const argumentNodes = getArguments(
|
||||
node,
|
||||
template,
|
||||
callArguments,
|
||||
callableWrapper.acceptedArguments,
|
||||
callableWrapper.isVariadic
|
||||
@ -175,7 +178,7 @@ export const executeCallNode: TwingNodeExecutor<TwingBaseCallNode<any>> = async
|
||||
|
||||
actualArguments.push(...providedArguments);
|
||||
|
||||
const traceableCallable = getTraceableMethod(callableWrapper.callable, node.line, node.column, template.name);
|
||||
const traceableCallable = getTraceableMethod(callableWrapper.callable, node, template.source);
|
||||
|
||||
return traceableCallable(executionContext, ...actualArguments).then((value) => {
|
||||
return value;
|
||||
|
@ -10,7 +10,7 @@ export const executeEscapeNode: TwingNodeExecutor<TwingEscapeNode> = (node, exec
|
||||
|
||||
return execute(body, executionContext)
|
||||
.then((value) => {
|
||||
const traceableEscape = getTraceableMethod(escapeValue, node.line, node.column, template.name);
|
||||
const traceableEscape = getTraceableMethod(escapeValue, node, template.source);
|
||||
|
||||
return traceableEscape(template, environment, value, strategy, null);
|
||||
});
|
||||
|
@ -47,7 +47,7 @@ export const executeMethodCall: TwingNodeExecutor<TwingMethodCallNode> = async (
|
||||
if (handler) {
|
||||
return handler(executionContext, ...macroArguments);
|
||||
} else {
|
||||
throw createRuntimeError(`Macro "${methodName}" is not defined in template "${macroTemplate.name}".`, node, template.name);
|
||||
throw createRuntimeError(`Macro "${methodName}" is not defined in template "${macroTemplate.name}".`, node, template.source);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -15,9 +15,8 @@ export const executeNameNode: TwingNodeExecutor<TwingNameNode> = (node, {
|
||||
|
||||
const traceableGetContextValue = getTraceableMethod(
|
||||
getContextValue,
|
||||
node.line,
|
||||
node.column,
|
||||
template.name
|
||||
node,
|
||||
template.source
|
||||
);
|
||||
|
||||
return traceableGetContextValue(
|
||||
|
@ -5,7 +5,7 @@ import {getTraceableMethod} from "../../helpers/traceable-method";
|
||||
export const executeParentFunction: TwingNodeExecutor<TwingParentFunctionNode> = (node, executionContext) => {
|
||||
const {template, outputBuffer} = executionContext;
|
||||
const {name} = node.attributes;
|
||||
const displayParentBlock = getTraceableMethod(template.displayParentBlock, node.line, node.column, template.name);
|
||||
const displayParentBlock = getTraceableMethod(template.displayParentBlock, node, template.source);
|
||||
|
||||
outputBuffer.start();
|
||||
|
||||
|
@ -4,7 +4,7 @@ import {createRuntimeError} from "../../error/runtime";
|
||||
|
||||
export const executeUnaryNode: TwingNodeExecutor<TwingBaseUnaryNode<any>> = (node, executionContext) => {
|
||||
const {operand} = node.children;
|
||||
const {nodeExecutor: execute} = executionContext;
|
||||
const {nodeExecutor: execute, template} = executionContext;
|
||||
|
||||
switch (node.type) {
|
||||
case "negative": {
|
||||
@ -18,5 +18,5 @@ export const executeUnaryNode: TwingNodeExecutor<TwingBaseUnaryNode<any>> = (nod
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(createRuntimeError(`Unrecognized unary node of type "${node.type}"`, node));
|
||||
return Promise.reject(createRuntimeError(`Unrecognized unary node of type "${node.type}"`, node, template.source));
|
||||
};
|
||||
|
@ -17,7 +17,7 @@ export const executeImportNode: TwingNodeExecutor<TwingImportNode> = async (node
|
||||
} else {
|
||||
const templateName = await execute(templateNameNode, executionContext);
|
||||
|
||||
const loadTemplate = getTraceableMethod(template.loadTemplate, node.line, node.column, template.name);
|
||||
const loadTemplate = getTraceableMethod(template.loadTemplate, node, template.source);
|
||||
|
||||
aliasValue = await loadTemplate(executionContext, templateName);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ export const executeBaseIncludeNode = async (
|
||||
|
||||
const templatesToInclude = await getTemplate(executionContext);
|
||||
|
||||
const traceableInclude = getTraceableMethod(include, node.line, node.column, template.name);
|
||||
const traceableInclude = getTraceableMethod(include, node, template.source);
|
||||
|
||||
const output = await traceableInclude(
|
||||
executionContext,
|
||||
|
@ -14,7 +14,7 @@ export const executeEmbedNode: TwingNodeExecutor<TwingEmbedNode> = (node, execut
|
||||
const embeddedTemplate = embeddedTemplates.get(index)!;
|
||||
|
||||
return Promise.resolve(embeddedTemplate);
|
||||
}, node.line, node.column, template.name);
|
||||
}, node, template.source);
|
||||
|
||||
return loadEmbeddedTemplate();
|
||||
})
|
||||
|
@ -16,7 +16,7 @@ export const executeWithNode: TwingNodeExecutor<TwingWithNode> = async (node, ex
|
||||
const variables = await execute(variablesNode, executionContext);
|
||||
|
||||
if (typeof variables !== "object") {
|
||||
throw createRuntimeError(`Variables passed to the "with" tag must be a hash.`, node, template.name);
|
||||
throw createRuntimeError(`Variables passed to the "with" tag must be a hash.`, node, template.source);
|
||||
}
|
||||
|
||||
if (only) {
|
||||
|
@ -1,21 +1,22 @@
|
||||
import {TwingNodeVisitor} from "./node-visitor";
|
||||
import {TwingBaseNode, getChildren} from "./node";
|
||||
import type {TwingSource} from "./source";
|
||||
|
||||
/**
|
||||
* TwingNodeTraverser is a node traverser.
|
||||
*
|
||||
* It visits all nodes and their children and calls the registered visitors for each.
|
||||
*/
|
||||
export type TwingNodeTraverser = (node: TwingBaseNode) => TwingBaseNode | null;
|
||||
export type TwingNodeTraverser = (node: TwingBaseNode, source: TwingSource) => TwingBaseNode | null;
|
||||
|
||||
export const createNodeTraverser = (
|
||||
visitors: Array<TwingNodeVisitor>
|
||||
): TwingNodeTraverser => {
|
||||
const traverseWithVisitor = (visitor: TwingNodeVisitor, node: TwingBaseNode) => {
|
||||
node = visitor.enterNode(node);
|
||||
const traverseWithVisitor = (visitor: TwingNodeVisitor, node: TwingBaseNode, source: TwingSource) => {
|
||||
node = visitor.enterNode(node, source);
|
||||
|
||||
for (const [key, child] of getChildren(node)) {
|
||||
const newChild = traverseWithVisitor(visitor, child);
|
||||
const newChild = traverseWithVisitor(visitor, child, source);
|
||||
|
||||
if (newChild) {
|
||||
if (newChild !== child) {
|
||||
@ -26,14 +27,14 @@ export const createNodeTraverser = (
|
||||
}
|
||||
}
|
||||
|
||||
return visitor.leaveNode(node);
|
||||
return visitor.leaveNode(node, source);
|
||||
};
|
||||
|
||||
return (node) => {
|
||||
return (node, template) => {
|
||||
let result: TwingBaseNode | null = node;
|
||||
|
||||
|
||||
for (const visitor of visitors) {
|
||||
result = traverseWithVisitor(visitor, node);
|
||||
result = traverseWithVisitor(visitor, node, template);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import type {TwingBaseNode} from "./node";
|
||||
import type {TwingSource} from "./source";
|
||||
|
||||
/**
|
||||
* The interface that all node visitors must implement.
|
||||
@ -9,22 +10,22 @@ export interface TwingNodeVisitor {
|
||||
*
|
||||
* @return The modified node
|
||||
*/
|
||||
enterNode(node: TwingBaseNode): TwingBaseNode;
|
||||
enterNode(node: TwingBaseNode, source: TwingSource): TwingBaseNode;
|
||||
|
||||
/**
|
||||
* Called after the passed node has been visited.
|
||||
*
|
||||
* @return The modified node or null if the node must be removed from its parent
|
||||
*/
|
||||
leaveNode(node: TwingBaseNode): TwingBaseNode | null;
|
||||
leaveNode(node: TwingBaseNode, source: TwingSource): TwingBaseNode | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient factory for TwingNodeVisitor
|
||||
*/
|
||||
export const createNodeVisitor = (
|
||||
enterNode: (node: TwingBaseNode) => TwingBaseNode,
|
||||
leaveNode: (node: TwingBaseNode) => TwingBaseNode | null
|
||||
enterNode: (node: TwingBaseNode, source: TwingSource) => TwingBaseNode,
|
||||
leaveNode: (node: TwingBaseNode, source: TwingSource) => TwingBaseNode | null
|
||||
): TwingNodeVisitor => {
|
||||
return {
|
||||
enterNode,
|
||||
|
@ -15,6 +15,7 @@ import {createConditionalNode} from "../node/expression/conditional";
|
||||
import {TwingFilterNode} from "../node/expression/call/filter";
|
||||
import type {TwingNode} from "../node";
|
||||
import {getKeyValuePairs} from "../helpers/get-key-value-pairs";
|
||||
import type {TwingSource} from "../source";
|
||||
|
||||
export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
const enteredNodes: Array<TwingBaseExpressionNode> = [];
|
||||
@ -48,7 +49,7 @@ export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
return newNode;
|
||||
};
|
||||
|
||||
const enterDefinedTestNode = (node: TwingTestNode): TwingTestNode => {
|
||||
const enterDefinedTestNode = (node: TwingTestNode, source: TwingSource): TwingTestNode => {
|
||||
const operand = node.children.operand! as TwingNode;
|
||||
|
||||
if (
|
||||
@ -61,7 +62,7 @@ export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
operand.type !== "method_call" &&
|
||||
!(operand.type === "function" && operand.attributes.operatorName === 'constant')
|
||||
) {
|
||||
throw createParsingError('The "defined" test only works with simple variables.', node);
|
||||
throw createParsingError('The "defined" test only works with simple variables.', node, source);
|
||||
}
|
||||
|
||||
let newOperand: TwingBaseExpressionNode;
|
||||
@ -139,7 +140,7 @@ export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
};
|
||||
|
||||
return {
|
||||
enterNode: (node: TwingNode) => {
|
||||
enterNode: (node: TwingNode, source) => {
|
||||
if (!enteredNodes.includes(node)) {
|
||||
enteredNodes.push(node);
|
||||
|
||||
@ -151,7 +152,7 @@ export const createCoreNodeVisitor = (): TwingNodeVisitor => {
|
||||
|
||||
if (node.type === "test") {
|
||||
if (node.attributes.operatorName === "defined") {
|
||||
return enterDefinedTestNode(node);
|
||||
return enterDefinedTestNode(node, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type {TwingTokenStream} from "./token-stream";
|
||||
import {TwingTagHandler, TwingTokenParser} from "./tag-handler";
|
||||
import {TwingNodeVisitor} from "./node-visitor";
|
||||
import {createParsingError, TwingParsingError} from "./error/parsing";
|
||||
import {createParsingError} from "./error/parsing";
|
||||
import {TwingBaseNode, getChildren, TwingNode, createNode} from "./node";
|
||||
import {createTextNode} from "./node/text";
|
||||
import {createPrintNode} from "./node/print";
|
||||
@ -309,7 +309,7 @@ export const createParser = (
|
||||
throw createParsingError(
|
||||
`A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?`,
|
||||
node,
|
||||
stream.source.name
|
||||
stream.source
|
||||
);
|
||||
}
|
||||
|
||||
@ -330,7 +330,7 @@ export const createParser = (
|
||||
// expected as the definition is not part of the default template code flow.
|
||||
if (nested && (type === "block_reference")) {
|
||||
if (level >= 3) {
|
||||
throw createParsingError(`A block definition cannot be nested under non-capturing nodes.`, node, stream.source.name);
|
||||
throw createParsingError(`A block definition cannot be nested under non-capturing nodes.`, node, stream.source);
|
||||
}
|
||||
else {
|
||||
console.warn(`Nesting a block definition under a non-capturing node in "${stream.source.name}" at line ${node.line} is deprecated since Twig 2.5.0 and will become a syntax error in Twig 3.0.`);
|
||||
@ -388,7 +388,7 @@ export const createParser = (
|
||||
}
|
||||
}
|
||||
else if (strict) {
|
||||
const error = createParsingError(`Unknown filter "${name}".`, {line, column}, stream.source.name);
|
||||
const error = createParsingError(`Unknown filter "${name}".`, {line, column}, stream.source);
|
||||
|
||||
error.addSuggestions(name, filterNames);
|
||||
|
||||
@ -421,7 +421,7 @@ export const createParser = (
|
||||
}
|
||||
}
|
||||
else if (strict) {
|
||||
const error = createParsingError(`Unknown function "${name}".`, {line, column}, stream.source.name);
|
||||
const error = createParsingError(`Unknown function "${name}".`, {line, column}, stream.source);
|
||||
|
||||
error.addSuggestions(name, functionNames);
|
||||
|
||||
@ -440,14 +440,14 @@ export const createParser = (
|
||||
throw createParsingError('Calling "parent" outside a block is forbidden.', {
|
||||
line,
|
||||
column
|
||||
}, stream.source.name);
|
||||
}, stream.source);
|
||||
}
|
||||
|
||||
if (!parent && !hasTraits()) {
|
||||
throw createParsingError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', {
|
||||
line,
|
||||
column
|
||||
}, stream.source.name);
|
||||
}, stream.source);
|
||||
}
|
||||
|
||||
return createParentFunctionNode(peekBlockStack(), line, column);
|
||||
@ -459,7 +459,7 @@ export const createParser = (
|
||||
throw createParsingError('The "block" function takes one argument (the block name).', {
|
||||
line,
|
||||
column
|
||||
}, stream.source.name);
|
||||
}, stream.source);
|
||||
}
|
||||
|
||||
return createBlockFunctionNode(keyValuePairs[0].value, keyValuePairs.length > 1 ? keyValuePairs[1].value : null, line, column);
|
||||
@ -471,7 +471,7 @@ export const createParser = (
|
||||
throw createParsingError('The "attribute" function takes at least two arguments (the variable and the attributes).', {
|
||||
line,
|
||||
column
|
||||
}, stream.source.name);
|
||||
}, stream.source);
|
||||
}
|
||||
|
||||
return createAttributeAccessorNode(
|
||||
@ -639,7 +639,7 @@ export const createParser = (
|
||||
return name;
|
||||
}
|
||||
|
||||
const error = createParsingError(`Unknown test "${name}".`, {line, column}, stream.source.name);
|
||||
const error = createParsingError(`Unknown test "${name}".`, {line, column}, stream.source);
|
||||
|
||||
error.addSuggestions(name, testNames);
|
||||
|
||||
@ -702,20 +702,10 @@ export const createParser = (
|
||||
}];
|
||||
embeddedTemplates = [];
|
||||
|
||||
let body: TwingBaseNode | null;
|
||||
let body: TwingBaseNode | null = subparse(stream, tag, test);
|
||||
|
||||
try {
|
||||
body = subparse(stream, tag, test);
|
||||
|
||||
if (parent !== null && (body = filterChildBodyNode(stream, body)) === null) {
|
||||
body = createNode();
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (!(error as TwingParsingError).source) {
|
||||
(error as TwingParsingError).source = stream.source.name;
|
||||
}
|
||||
|
||||
throw error;
|
||||
if (parent !== null && (body = filterChildBodyNode(stream, body)) === null) {
|
||||
body = createNode();
|
||||
}
|
||||
|
||||
let node = createTemplateNode(
|
||||
@ -732,7 +722,7 @@ export const createParser = (
|
||||
// passed visitors
|
||||
let traverse = createNodeTraverser(visitors);
|
||||
|
||||
node = traverse(node) as TwingTemplateNode;
|
||||
node = traverse(node, stream.source) as TwingTemplateNode;
|
||||
|
||||
// core visitors
|
||||
traverse = createNodeTraverser([
|
||||
@ -741,7 +731,7 @@ export const createParser = (
|
||||
createSandboxNodeVisitor()
|
||||
]);
|
||||
|
||||
node = traverse(node) as TwingTemplateNode;
|
||||
node = traverse(node, stream.source) as TwingTemplateNode;
|
||||
|
||||
// restore previous stack so previous parse() call can resume working
|
||||
const previousStackEntry = stack.pop()!;
|
||||
@ -804,7 +794,7 @@ export const createParser = (
|
||||
|
||||
if (namedArguments && (token = stream.nextIf("OPERATOR", '='))) {
|
||||
if (value.type !== "name") {
|
||||
throw createParsingError(`A parameter name must be a string, "${value.type.toString()}" given.`, value, stream.source.name);
|
||||
throw createParsingError(`A parameter name must be a string, "${value.type.toString()}" given.`, value, stream.source);
|
||||
}
|
||||
|
||||
key = createConstantNode(value.attributes.name, value.line, value.column);
|
||||
@ -815,7 +805,7 @@ export const createParser = (
|
||||
const notConstantNode = checkConstantExpression(stream, value);
|
||||
|
||||
if (notConstantNode !== null) {
|
||||
throw createParsingError(`A default value for an argument must be a constant (a boolean, a string, a number, or an array).`, notConstantNode, stream.source.name);
|
||||
throw createParsingError(`A default value for an argument must be a constant (a boolean, a string, a number, or an array).`, notConstantNode, stream.source);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -906,7 +896,7 @@ export const createParser = (
|
||||
let value = token.value;
|
||||
|
||||
if (['true', 'false', 'none', 'null'].indexOf(value.toLowerCase()) > -1) {
|
||||
throw createParsingError(`You cannot assign a value to "${value}".`, token, stream.source.name);
|
||||
throw createParsingError(`You cannot assign a value to "${value}".`, token, stream.source);
|
||||
}
|
||||
|
||||
pushToRecord(targets, createAssignmentNode(value, token.line, token.column));
|
||||
@ -979,7 +969,7 @@ export const createParser = (
|
||||
token = stream.current;
|
||||
|
||||
if (!token.test("NAME")) {
|
||||
throw createParsingError(`Unexpected token "${typeToEnglish(token.type)}" of value "${token.value}".`, token, stream.source.name);
|
||||
throw createParsingError(`Unexpected token "${typeToEnglish(token.type)}" of value "${token.value}".`, token, stream.source);
|
||||
}
|
||||
|
||||
names[i++] = createAssignmentNode(token.value, token.line, token.column);
|
||||
@ -1216,7 +1206,7 @@ export const createParser = (
|
||||
throw createParsingError(`A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "${typeToEnglish(type)}" of value "${value}".`, {
|
||||
line,
|
||||
column
|
||||
}, stream.source.name);
|
||||
}, stream.source);
|
||||
}
|
||||
|
||||
stream.expect("PUNCTUATION", ':', 'A hash key must be followed by a colon (:)');
|
||||
@ -1351,10 +1341,10 @@ export const createParser = (
|
||||
node = parseHashExpression(stream);
|
||||
}
|
||||
else if (token.test("OPERATOR", '=') && (stream.look(-1).value === '==' || stream.look(-1).value === '!=')) {
|
||||
throw createParsingError(`Unexpected operator of value "${token.value}". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.`, token, stream.source.name);
|
||||
throw createParsingError(`Unexpected operator of value "${token.value}". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.`, token, stream.source);
|
||||
}
|
||||
else {
|
||||
throw createParsingError(`Unexpected token "${typeToEnglish(token.type)}" of value "${token.value}".`, token, stream.source.name);
|
||||
throw createParsingError(`Unexpected token "${typeToEnglish(token.type)}" of value "${token.value}".`, token, stream.source);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1428,7 +1418,7 @@ export const createParser = (
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw createParsingError('Expected name or number.', {line, column: column + 1}, stream.source.name);
|
||||
throw createParsingError('Expected name or number.', {line, column: column + 1}, stream.source);
|
||||
}
|
||||
|
||||
if ((node.type === "name") && (node.attributes.name === '_self' || getImportedTemplate(node.attributes.name))) {
|
||||
@ -1582,7 +1572,7 @@ export const createParser = (
|
||||
token = stream.current;
|
||||
|
||||
if (token.type !== "NAME") {
|
||||
throw createParsingError('A block must start with a tag name.', token, stream.source.name);
|
||||
throw createParsingError('A block must start with a tag name.', token, stream.source);
|
||||
}
|
||||
|
||||
if ((test !== null) && test(token)) {
|
||||
@ -1600,7 +1590,7 @@ export const createParser = (
|
||||
error = createParsingError(
|
||||
`Unexpected "${token.value}" tag`,
|
||||
token,
|
||||
stream.source.name
|
||||
stream.source
|
||||
);
|
||||
|
||||
error.appendMessage(` (expecting closing tag for the "${tag}" tag defined line ${line}).`);
|
||||
@ -1609,7 +1599,7 @@ export const createParser = (
|
||||
error = createParsingError(
|
||||
`Unknown "${token.value}" tag.`,
|
||||
token,
|
||||
stream.source.name
|
||||
stream.source
|
||||
);
|
||||
|
||||
error.addSuggestions(token.value, tags);
|
||||
|
@ -1,30 +0,0 @@
|
||||
import type {TwingSandboxSecurityNotAllowedFilterError} from "./security-not-allowed-filter-error";
|
||||
import type {TwingSandboxSecurityNotAllowedFunctionError} from "./security-not-allowed-function-error";
|
||||
import type {TwingSandboxSecurityNotAllowedMethodError} from "./security-not-allowed-method-error";
|
||||
import type {TwingSandboxSecurityNotAllowedPropertyError} from "./security-not-allowed-property-error";
|
||||
import type {TwingSandboxSecurityNotAllowedTagError} from "./security-not-allowed-tag-error";
|
||||
import {createBaseError, TwingErrorLocation, TwingBaseError} from "../error/base";
|
||||
|
||||
export type TwingSandboxSecurityError =
|
||||
| TwingSandboxSecurityNotAllowedFilterError
|
||||
| TwingSandboxSecurityNotAllowedFunctionError
|
||||
| TwingSandboxSecurityNotAllowedMethodError
|
||||
| TwingSandboxSecurityNotAllowedPropertyError
|
||||
| TwingSandboxSecurityNotAllowedTagError
|
||||
;
|
||||
|
||||
export const sandboxSecurityErrorName = 'TwingSandboxSecurityError';
|
||||
|
||||
/**
|
||||
* Exception thrown when a security error occurs at runtime.
|
||||
*/
|
||||
export interface BaseSandboxSecurityError extends TwingBaseError<typeof sandboxSecurityErrorName> {
|
||||
}
|
||||
|
||||
export const createBaseSandboxSecurityError = (message: string, location?: TwingErrorLocation, source?: string) => {
|
||||
const error = createBaseError(sandboxSecurityErrorName, message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createBaseSandboxSecurityError);
|
||||
|
||||
return error;
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
import {
|
||||
BaseSandboxSecurityError,
|
||||
createBaseSandboxSecurityError,
|
||||
TwingSandboxSecurityError
|
||||
} from "./security-error";
|
||||
import {TwingErrorLocation} from "../error/base";
|
||||
|
||||
/**
|
||||
* Exception thrown when a not allowed filter is used in a template.
|
||||
*/
|
||||
export interface TwingSandboxSecurityNotAllowedFilterError extends BaseSandboxSecurityError {
|
||||
readonly filterName: string;
|
||||
}
|
||||
|
||||
export const createSandboxSecurityNotAllowedFilterError = (message: string, filterName: string, location?: TwingErrorLocation, source?: string): TwingSandboxSecurityNotAllowedFilterError => {
|
||||
const error = createBaseSandboxSecurityError(message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createSandboxSecurityNotAllowedFilterError);
|
||||
|
||||
return Object.assign(error, {
|
||||
get filterName() {
|
||||
return filterName;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const isASandboxSecurityNotAllowedFilterError = (candidate: TwingSandboxSecurityError): candidate is TwingSandboxSecurityNotAllowedFilterError => {
|
||||
return (candidate as TwingSandboxSecurityNotAllowedFilterError).filterName !== undefined;
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
import {BaseSandboxSecurityError, createBaseSandboxSecurityError} from "./security-error";
|
||||
import {TwingErrorLocation} from "../error/base";
|
||||
|
||||
export interface TwingSandboxSecurityNotAllowedFunctionError extends BaseSandboxSecurityError {
|
||||
readonly functionName: string;
|
||||
}
|
||||
|
||||
export const createSandboxSecurityNotAllowedFunctionError = (message: string, functionName: string, location?: TwingErrorLocation, source?: string): TwingSandboxSecurityNotAllowedFunctionError => {
|
||||
const error = createBaseSandboxSecurityError(message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createSandboxSecurityNotAllowedFunctionError);
|
||||
|
||||
return Object.assign(error, {
|
||||
get functionName() {
|
||||
return functionName;
|
||||
}
|
||||
});
|
||||
};
|
@ -1,13 +0,0 @@
|
||||
import {BaseSandboxSecurityError, createBaseSandboxSecurityError} from "./security-error";
|
||||
import {TwingErrorLocation} from "../error/base";
|
||||
|
||||
export interface TwingSandboxSecurityNotAllowedMethodError extends BaseSandboxSecurityError {
|
||||
}
|
||||
|
||||
export const createSandboxSecurityNotAllowedMethodError = (message: string, location?: TwingErrorLocation, source?: string): TwingSandboxSecurityNotAllowedMethodError => {
|
||||
const error = createBaseSandboxSecurityError(message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createSandboxSecurityNotAllowedMethodError);
|
||||
|
||||
return error;
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import {BaseSandboxSecurityError, createBaseSandboxSecurityError} from "./security-error";
|
||||
import {TwingErrorLocation} from "../error/base";
|
||||
|
||||
/**
|
||||
* Exception thrown when a not allowed object property is used in a template.
|
||||
*/
|
||||
export interface TwingSandboxSecurityNotAllowedPropertyError extends BaseSandboxSecurityError {
|
||||
}
|
||||
|
||||
export const createSandboxSecurityNotAllowedPropertyError = (message: string, location?: TwingErrorLocation, source?: string): TwingSandboxSecurityNotAllowedPropertyError => {
|
||||
const error = createBaseSandboxSecurityError(message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createSandboxSecurityNotAllowedPropertyError);
|
||||
|
||||
return error;
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
import {BaseSandboxSecurityError, createBaseSandboxSecurityError, TwingSandboxSecurityError} from "./security-error";
|
||||
import {TwingSandboxSecurityNotAllowedPropertyError} from "./security-not-allowed-property-error";
|
||||
import {TwingErrorLocation} from "../error/base";
|
||||
|
||||
export interface TwingSandboxSecurityNotAllowedTagError extends BaseSandboxSecurityError {
|
||||
readonly tagName: string;
|
||||
}
|
||||
|
||||
export const createSandboxSecurityNotAllowedTagError = (message: string, tagName: string, location?: TwingErrorLocation, source?: string): TwingSandboxSecurityNotAllowedPropertyError => {
|
||||
const error = createBaseSandboxSecurityError(message, location, source);
|
||||
|
||||
Error.captureStackTrace(error, createSandboxSecurityNotAllowedTagError);
|
||||
|
||||
return Object.assign(error, {
|
||||
get tagName() {
|
||||
return tagName;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const isASandboxSecurityNotAllowedTagError = (candidate: TwingSandboxSecurityError): candidate is TwingSandboxSecurityNotAllowedTagError => {
|
||||
return (candidate as TwingSandboxSecurityNotAllowedTagError).tagName !== undefined;
|
||||
};
|
@ -1,8 +1,3 @@
|
||||
import {createSandboxSecurityNotAllowedFilterError} from "./security-not-allowed-filter-error";
|
||||
import {createSandboxSecurityNotAllowedTagError} from "./security-not-allowed-tag-error";
|
||||
import {createSandboxSecurityNotAllowedFunctionError} from "./security-not-allowed-function-error";
|
||||
import {createSandboxSecurityNotAllowedPropertyError} from "./security-not-allowed-property-error";
|
||||
import {createSandboxSecurityNotAllowedMethodError} from "./security-not-allowed-method-error";
|
||||
import {isAMarkup, TwingMarkup} from "../markup";
|
||||
|
||||
export interface TwingSandboxSecurityPolicy {
|
||||
@ -22,7 +17,11 @@ export interface TwingSandboxSecurityPolicy {
|
||||
*/
|
||||
checkPropertyAllowed(candidate: any | TwingMarkup, property: string): void;
|
||||
|
||||
checkSecurity(tags: Array<string>, filters: Array<string>, functions: Array<string>): void;
|
||||
checkSecurity(tags: Array<string>, filters: Array<string>, functions: Array<string>): {
|
||||
message: string;
|
||||
token: string;
|
||||
type: "filter" | "function" | "tag"
|
||||
} | null;
|
||||
}
|
||||
|
||||
export const createSandboxSecurityPolicy = (
|
||||
@ -51,15 +50,15 @@ export const createSandboxSecurityPolicy = (
|
||||
for (const [constructorName, methods] of allowedMethods) {
|
||||
if (candidate instanceof constructorName) {
|
||||
allowed = methods.includes(method);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
const constructorName = candidate.constructor.name || '(anonymous)';
|
||||
|
||||
throw createSandboxSecurityNotAllowedMethodError(`Calling "${method}" method on an instance of ${constructorName} is not allowed.`);
|
||||
|
||||
throw new Error(`Calling "${method}" method on an instance of ${constructorName} is not allowed.`);
|
||||
}
|
||||
},
|
||||
checkPropertyAllowed: (candidate, property) => {
|
||||
@ -76,27 +75,41 @@ export const createSandboxSecurityPolicy = (
|
||||
if (!allowed) {
|
||||
const constructorName = candidate.constructor.name || '(anonymous)';
|
||||
|
||||
throw createSandboxSecurityNotAllowedPropertyError(`Calling "${property}" property on an instance of ${constructorName} is not allowed.`);
|
||||
throw new Error(`Calling "${property}" property on an instance of ${constructorName} is not allowed.`);
|
||||
}
|
||||
},
|
||||
checkSecurity: (tags, filters, functions) => {
|
||||
for (const tag of tags) {
|
||||
if (!allowedTags.includes(tag)) {
|
||||
throw createSandboxSecurityNotAllowedTagError(`Tag "${tag}" is not allowed.`, tag);
|
||||
for (const tagName of tags) {
|
||||
if (!allowedTags.includes(tagName)) {
|
||||
return ({
|
||||
message: `Tag "${tagName}" is not allowed.`,
|
||||
token: tagName,
|
||||
type: "tag"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const filterName of filters) {
|
||||
if (!allowedFilters.includes(filterName)) {
|
||||
throw createSandboxSecurityNotAllowedFilterError(`Filter "${filterName}" is not allowed.`, filterName);
|
||||
return ({
|
||||
message: `Filter "${filterName}" is not allowed.`,
|
||||
token: filterName,
|
||||
type: "filter"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const function_ of functions) {
|
||||
if (!allowedFunctions.includes(function_)) {
|
||||
throw createSandboxSecurityNotAllowedFunctionError(`Function "${function_}" is not allowed.`, function_);
|
||||
for (const functionName of functions) {
|
||||
if (!allowedFunctions.includes(functionName)) {
|
||||
return ({
|
||||
message: `Function "${functionName}" is not allowed.`,
|
||||
token: functionName,
|
||||
type: "function"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
;
|
||||
|
@ -28,7 +28,7 @@ export const createAutoEscapeTagHandler = (): TwingTagHandler => {
|
||||
) {
|
||||
const {line, column} = expression;
|
||||
|
||||
throw createParsingError('An escaping strategy must be a string or false.', {line, column}, stream.source.name);
|
||||
throw createParsingError('An escaping strategy must be a string or false.', {line, column}, stream.source);
|
||||
}
|
||||
|
||||
const {value} = expression.attributes;
|
||||
|
@ -29,7 +29,7 @@ export const createBlockTagHandler = (): TwingTagHandler => {
|
||||
let block = parser.getBlock(name);
|
||||
|
||||
if (block !== null) {
|
||||
throw createParsingError(`The block '${name}' has already been defined at {${block.line}:${block.column}}.`, {line, column}, stream.source.name);
|
||||
throw createParsingError(`The block '${name}' has already been defined at {${block.line}:${block.column}}.`, {line, column}, stream.source);
|
||||
}
|
||||
|
||||
block = createBlockNode(name, createNode(), line, column);
|
||||
@ -55,7 +55,7 @@ export const createBlockTagHandler = (): TwingTagHandler => {
|
||||
if (value !== name) {
|
||||
const {line, column} = token;
|
||||
|
||||
throw createParsingError(`Expected endblock for block "${name}" (but "${value}" given).`, {line, column}, stream.source.name);
|
||||
throw createParsingError(`Expected endblock for block "${name}" (but "${value}" given).`, {line, column}, stream.source);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -22,13 +22,13 @@ export const createExtendsTagHandler = (): TwingTagHandler => {
|
||||
const {line, column} = token;
|
||||
|
||||
if (parser.peekBlockStack()) {
|
||||
throw createParsingError('Cannot use "extend" in a block.', {line, column}, stream.source.name);
|
||||
throw createParsingError('Cannot use "extend" in a block.', {line, column}, stream.source);
|
||||
} else if (!parser.isMainScope()) {
|
||||
throw createParsingError('Cannot use "extend" in a macro.', {line, column}, stream.source.name);
|
||||
throw createParsingError('Cannot use "extend" in a macro.', {line, column}, stream.source);
|
||||
}
|
||||
|
||||
if (parser.parent !== null) {
|
||||
throw createParsingError('Multiple extends tags are forbidden.', {line, column}, stream.source.name);
|
||||
throw createParsingError('Multiple extends tags are forbidden.', {line, column}, stream.source);
|
||||
}
|
||||
|
||||
parser.parent = parser.parseExpression(stream);
|
||||
|
@ -31,7 +31,7 @@ export const createForTagHandler = (): TwingTagHandler => {
|
||||
// the loop variable cannot be used in the condition
|
||||
const checkLoopUsageCondition = (stream: TwingTokenStream, node: TwingNode) => {
|
||||
if ((node.type === "attribute_accessor") && (node.children.target.type === "name") && (node.children.target.attributes.name === 'loop')) {
|
||||
throw createParsingError('The "loop" variable cannot be used in a looping condition.', node, stream.source.name);
|
||||
throw createParsingError('The "loop" variable cannot be used in a looping condition.', node, stream.source);
|
||||
}
|
||||
|
||||
for (const [, child] of getChildren(node)) {
|
||||
@ -46,7 +46,7 @@ export const createForTagHandler = (): TwingTagHandler => {
|
||||
const {attribute} = node.children;
|
||||
|
||||
if (attribute.type === "constant" && (['length', 'revindex0', 'revindex', 'last'].indexOf(attribute.attributes.value as string) > -1)) {
|
||||
throw createParsingError(`The "loop.${attribute.attributes.value}" variable is not defined when looping with a condition.`, node, stream.source.name);
|
||||
throw createParsingError(`The "loop.${attribute.attributes.value}" variable is not defined when looping with a condition.`, node, stream.source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ export const createMacroTagHandler = (): TwingTagHandler => {
|
||||
const {value: argumentName} = key.attributes;
|
||||
|
||||
if (argumentName === VARARGS_NAME) {
|
||||
throw createParsingError(`The argument "${VARARGS_NAME}" in macro "${name}" cannot be defined because the variable "${VARARGS_NAME}" is reserved for arbitrary arguments.`, macroArgument);
|
||||
throw createParsingError(`The argument "${VARARGS_NAME}" in macro "${name}" cannot be defined because the variable "${VARARGS_NAME}" is reserved for arbitrary arguments.`, macroArgument, stream.source);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ export const createMacroTagHandler = (): TwingTagHandler => {
|
||||
if (value != name) {
|
||||
const {line, column} = nextToken;
|
||||
|
||||
throw createParsingError(`Expected endmacro for macro "${name}" (but "${value}" given).`, {line, column}, stream.source.name);
|
||||
throw createParsingError(`Expected endmacro for macro "${name}" (but "${value}" given).`, {line, column}, stream.source);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ export const createSandboxTagHandler = (): TwingTagHandler => {
|
||||
|
||||
if (!(child.type === "text" && isMadeOfWhitespaceOnly(child.attributes.data))) {
|
||||
if (child.type !== "include" && child.type !== "embed") {
|
||||
throw createParsingError('Only "include" tags are allowed within a "sandbox" section.', child, stream.source.name);
|
||||
throw createParsingError('Only "include" tags are allowed within a "sandbox" section.', child, stream.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export const createSetTagHandler = (): TwingTagHandler => {
|
||||
if (getChildrenCount(names) !== getChildrenCount(values)) {
|
||||
const {line, column} = stream.current;
|
||||
|
||||
throw createParsingError('When using set, you must have the same number of variables and assignments.', {line, column}, stream.source.name);
|
||||
throw createParsingError('When using set, you must have the same number of variables and assignments.', {line, column}, stream.source);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -33,7 +33,7 @@ export const createSetTagHandler = (): TwingTagHandler => {
|
||||
if (getChildrenCount(names) > 1) {
|
||||
const {line, column} = stream.current;
|
||||
|
||||
throw createParsingError('When using set with a block, you cannot have a multi-target.', {line, column}, stream.source.name);
|
||||
throw createParsingError('When using set with a block, you cannot have a multi-target.', {line, column}, stream.source);
|
||||
}
|
||||
|
||||
stream.expect("TAG_END");
|
||||
|
@ -15,7 +15,7 @@ export const createUseTagHandler = (): TwingTagHandler => {
|
||||
const template = parser.parseExpression(stream);
|
||||
|
||||
if (template.type !== "constant") {
|
||||
throw createParsingError('The template references in a "use" statement must be a string.', {line, column}, stream.source.name);
|
||||
throw createParsingError('The template references in a "use" statement must be a string.', {line, column}, stream.source);
|
||||
}
|
||||
|
||||
const targets: Record<string, TwingConstantNode<string>> = {};
|
||||
|
@ -6,9 +6,8 @@ import {TwingTemplateNode} from "./node/template";
|
||||
import {mergeIterables} from "./helpers/merge-iterables";
|
||||
import {createRuntimeError} from "./error/runtime";
|
||||
import {createNode, getChildren, getChildrenCount, TwingBaseNode} from "./node";
|
||||
import {TwingError} from "./error";
|
||||
import {createMarkup, TwingMarkup} from "./markup";
|
||||
import {createTemplateLoadingError, isATemplateLoadingError} from "./error/loader";
|
||||
import {createTemplateLoadingError} from "./error/loader";
|
||||
import {cloneMap} from "./helpers/clone-map";
|
||||
import {iteratorToMap} from "./helpers/iterator-to-map";
|
||||
import {TwingSource} from "./source";
|
||||
@ -283,13 +282,14 @@ export const createTemplate = (
|
||||
}
|
||||
}
|
||||
else {
|
||||
// todo: use traceable method?
|
||||
return Promise.reject(createTemplateLoadingError((names as Array<string | null>).map((name) => {
|
||||
if (name === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return name;
|
||||
}), undefined, template.name));
|
||||
})));
|
||||
}
|
||||
};
|
||||
|
||||
@ -354,10 +354,10 @@ export const createTemplate = (
|
||||
if (block) {
|
||||
const [blockTemplate] = block!;
|
||||
|
||||
throw createRuntimeError(`Block "${name}" should not call parent() in "${blockTemplate.name}" as the block does not exist in the parent template "${template.name}".`, undefined, blockTemplate.name);
|
||||
throw new Error(`Block "${name}" should not call parent() in "${blockTemplate.name}" as the block does not exist in the parent template "${template.name}".`);
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`Block "${name}" on template "${template.name}" does not exist.`, undefined, template.name);
|
||||
throw new Error(`Block "${name}" on template "${template.name}" does not exist.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -382,7 +382,7 @@ export const createTemplate = (
|
||||
return parent.displayBlock(executionContext, name, false);
|
||||
}
|
||||
else {
|
||||
throw createRuntimeError(`The template has no parent and no traits defining the "${name}" block.`, undefined, template.name);
|
||||
throw new Error(`The template has no parent and no traits defining the "${name}" block.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -424,16 +424,6 @@ export const createTemplate = (
|
||||
return parent.execute(environment, context, blocks, outputBuffer, options);
|
||||
}
|
||||
});
|
||||
}).catch((error: TwingError) => {
|
||||
if (!error.source) {
|
||||
error.source = template.name;
|
||||
}
|
||||
|
||||
if (isATemplateLoadingError(error)) {
|
||||
error = createRuntimeError(error.rootMessage, error.location, error.source, error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
getBlocks: (executionContext) => {
|
||||
@ -471,9 +461,8 @@ export const createTemplate = (
|
||||
|
||||
const loadTemplate = getTraceableMethod(
|
||||
template.loadTemplate,
|
||||
parentNode.line,
|
||||
parentNode.column,
|
||||
template.name
|
||||
parentNode,
|
||||
template.source
|
||||
);
|
||||
|
||||
const loadedParent = await loadTemplate(executionContext, parentName);
|
||||
@ -501,15 +490,14 @@ export const createTemplate = (
|
||||
|
||||
const loadTemplate = getTraceableMethod(
|
||||
template.loadTemplate,
|
||||
templateNameNode.line,
|
||||
templateNameNode.column,
|
||||
template.name
|
||||
templateNameNode,
|
||||
template.source
|
||||
);
|
||||
|
||||
const traitTemplate = await loadTemplate(executionContext, templateName);
|
||||
|
||||
if (!traitTemplate.canBeUsedAsATrait) {
|
||||
throw createRuntimeError(`Template ${templateName} cannot be used as a trait.`, templateNameNode, template.name);
|
||||
throw createRuntimeError(`Template ${templateName} cannot be used as a trait.`, templateNameNode, template.source);
|
||||
}
|
||||
|
||||
const traitBlocks = cloneMap(await traitTemplate.getBlocks(executionContext));
|
||||
@ -518,7 +506,7 @@ export const createTemplate = (
|
||||
const traitBlock = traitBlocks.get(key);
|
||||
|
||||
if (!traitBlock) {
|
||||
throw createRuntimeError(`Block "${key}" is not defined in trait "${templateName}".`, templateNameNode, template.name);
|
||||
throw createRuntimeError(`Block "${key}" is not defined in trait "${templateName}".`, templateNameNode, template.source);
|
||||
}
|
||||
|
||||
const targetValue = (target as TwingConstantNode<string>).attributes.value;
|
||||
|
@ -65,7 +65,7 @@ export const createTokenStream = (
|
||||
throw createParsingError(
|
||||
`${message ? message + '. ' : ''}Unexpected token "${typeToEnglish(token.type)}" of value "${token.value}" ("${typeToEnglish(type)}" expected${value ? ` with value "${value}"` : ''}).`,
|
||||
{line, column},
|
||||
source.name
|
||||
source
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ runTest({
|
||||
{{ foo.bar() }}
|
||||
`
|
||||
},
|
||||
expectedErrorMessage: 'TwingRuntimeError: An exception has been thrown during the rendering of a template ("I am Error") in "index.twig" at line 2, column 4.',
|
||||
expectedErrorMessage: 'TwingRuntimeError: I am Error in "index.twig" at line 2, column 4',
|
||||
context: Promise.resolve({
|
||||
foo: {
|
||||
bar: () => {
|
||||
|
@ -22,7 +22,7 @@ const testCases: Array<[title: string, Record<string, any>, errorMessage: string
|
||||
return 'bar';
|
||||
}
|
||||
})
|
||||
}, 'TwingSandboxSecurityError: Calling "bar" property on an instance of (anonymous) is not allowed in "index.twig" at line 1, column 4.']
|
||||
}, 'TwingRuntimeError: Calling "bar" property on an instance of (anonymous) is not allowed in "index.twig" at line 1, column 4.']
|
||||
];
|
||||
|
||||
for (const [name, context, errorMessage] of testCases) {
|
||||
|
@ -22,7 +22,7 @@ const testCases: Array<[title: string, Record<string, any>, errorMessage: string
|
||||
return 'bar';
|
||||
}
|
||||
})
|
||||
}, 'TwingSandboxSecurityError: Calling "bar" method on an instance of (anonymous) is not allowed in "index.twig" at line 1, column 4.']
|
||||
}, 'TwingRuntimeError: Calling "bar" method on an instance of (anonymous) is not allowed in "index.twig" at line 1, column 4.']
|
||||
];
|
||||
|
||||
for (const [name, context, errorMessage] of testCases) {
|
||||
|
@ -31,17 +31,17 @@ for (const [value, expectation] of owaspTestCases) {
|
||||
|
||||
const specialCharacters: { [k: string]: string } = {
|
||||
/* HTML special chars - escape without exception to hex */
|
||||
'<': '\\3C ',
|
||||
'>': '\\3E ',
|
||||
'\'': '\\27 ',
|
||||
'"': '\\22 ',
|
||||
'&': '\\26 ',
|
||||
'<': '\\u003C',
|
||||
'>': '\\u003E',
|
||||
'\'': '\\u0027',
|
||||
'"': '\\u0022',
|
||||
'&': '\\u0026',
|
||||
/* Characters beyond ASCII value 255 to unicode escape */
|
||||
'Ā': '\\100 ',
|
||||
'Ā': '\\u0100',
|
||||
/* Immune chars excluded */
|
||||
',': '\\2C ',
|
||||
'.': '\\2E ',
|
||||
'_': '\\5F ',
|
||||
',': '\\u002C',
|
||||
'.': '\\u002E',
|
||||
'_': '\\u005F',
|
||||
/* Basic alnums excluded */
|
||||
'a': 'a',
|
||||
'A': 'A',
|
||||
@ -50,19 +50,19 @@ const specialCharacters: { [k: string]: string } = {
|
||||
'0': '0',
|
||||
'9': '9',
|
||||
/* Basic control characters and null */
|
||||
"\r": '\\D ',
|
||||
"\n": '\\A ',
|
||||
"\t": '\\9 ',
|
||||
"\0": '\\0 ',
|
||||
"\r": '\\u000D',
|
||||
"\n": '\\u000A',
|
||||
"\t": '\\u0009',
|
||||
"\0": '\\u0000',
|
||||
/* Encode spaces for quoteless attribute protection */
|
||||
' ': '\\20 ',
|
||||
' ': '\\u0020',
|
||||
};
|
||||
|
||||
for (const key in specialCharacters) {
|
||||
let value = specialCharacters[key];
|
||||
|
||||
runTest({
|
||||
description: `"escape" filter with "css" strategy on special character "${value}"`,
|
||||
description: `"escape" filter with "css" strategy on special character "${key}"`,
|
||||
templates: {
|
||||
"index.twig": `{{ key|escape("css") }}`
|
||||
},
|
||||
|
@ -24,7 +24,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Filter "e" is not allowed in "foo.twig" at line 2, column 8.';
|
||||
return 'TwingRuntimeError: Filter "e" is not allowed in "foo.twig" at line 2, column 8.';
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,5 +45,5 @@ runTest({
|
||||
allowedFunctions: ['include']
|
||||
})
|
||||
},
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Filter "e" is not allowed in "foo.twig" at line 2, column 8.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Filter "e" is not allowed in "foo.twig" at line 2, column 8.'
|
||||
});
|
||||
|
@ -20,5 +20,5 @@ runTest({
|
||||
{{ random([]) }}
|
||||
`
|
||||
},
|
||||
expectedErrorMessage: `TwingRuntimeError: The random function cannot pick from an empty array in "index.twig".`
|
||||
expectedErrorMessage: `TwingRuntimeError: The random function cannot pick from an empty array in "index.twig" at line 2, column 4.`
|
||||
});
|
||||
|
@ -11,7 +11,7 @@ runTest({
|
||||
toString: () => 'foo'
|
||||
}
|
||||
}),
|
||||
expectedErrorMessage: `TwingSandboxSecurityError: Tag "do" is not allowed in "index.twig" at line 1, column 4.`,
|
||||
expectedErrorMessage: `TwingRuntimeError: Tag "do" is not allowed in "index.twig" at line 1, column 4.`,
|
||||
sandboxed: true,
|
||||
sandboxPolicy: createSandboxSecurityPolicy()
|
||||
})
|
||||
|
@ -22,5 +22,5 @@ runTest({
|
||||
`
|
||||
},
|
||||
sandboxed: true,
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Filter "upper" is not allowed in "index.twig" at line 2, column 6.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Filter "upper" is not allowed in "index.twig" at line 2, column 6.'
|
||||
});
|
||||
|
@ -22,5 +22,5 @@ runTest({
|
||||
`
|
||||
},
|
||||
sandboxed: true,
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Function "dump" is not allowed in "index.twig" at line 2, column 4.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Function "dump" is not allowed in "index.twig" at line 2, column 4.'
|
||||
});
|
||||
|
@ -35,5 +35,5 @@ runTest({
|
||||
sandboxSecurityPolicyMethods: new Map([
|
||||
[Map, ['bar']]
|
||||
]),
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Calling "bar" method on an instance of Object is not allowed in "index.twig" at line 2, column 4.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Calling "bar" method on an instance of Object is not allowed in "index.twig" at line 2, column 4.'
|
||||
});
|
||||
|
@ -35,5 +35,5 @@ runTest({
|
||||
sandboxSecurityPolicyProperties: new Map([
|
||||
[Map, ['bar']]
|
||||
]),
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Calling "bar" property on an instance of Object is not allowed in "index.twig" at line 2, column 4.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Calling "bar" property on an instance of Object is not allowed in "index.twig" at line 2, column 4.'
|
||||
});
|
||||
|
@ -22,5 +22,5 @@ runTest({
|
||||
`
|
||||
},
|
||||
sandboxed: true,
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Tag "block" is not allowed in "index.twig" at line 2, column 4.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Tag "block" is not allowed in "index.twig" at line 2, column 4.'
|
||||
});
|
||||
|
@ -17,7 +17,7 @@ export class Basic extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
return 'TwingRuntimeError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
}
|
||||
|
||||
getContext() {
|
||||
@ -50,7 +50,7 @@ export class Set extends Basic {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage(): string {
|
||||
return 'TwingSandboxSecurityError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 2, column 12.';
|
||||
return 'TwingRuntimeError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 2, column 12.';
|
||||
}
|
||||
|
||||
getSandboxSecurityPolicyTags(): string[] {
|
||||
|
@ -17,7 +17,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
return 'TwingRuntimeError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
}
|
||||
|
||||
getSandboxSecurityPolicyFilters() {
|
||||
|
@ -17,7 +17,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
return 'TwingRuntimeError: Calling "toString" method on an instance of Object is not allowed in "foo.twig" at line 1, column 4.';
|
||||
}
|
||||
|
||||
getContext() {
|
||||
|
@ -18,7 +18,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Filter "upper" is not allowed in "foo.twig" at line 1, column 10.';
|
||||
return 'TwingRuntimeError: Filter "upper" is not allowed in "foo.twig" at line 1, column 10.';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Function "dump" is not allowed in "foo.twig" at line 1, column 4.';
|
||||
return 'TwingRuntimeError: Function "dump" is not allowed in "foo.twig" at line 1, column 4.';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage() {
|
||||
return 'TwingSandboxSecurityError: Tag "do" is not allowed in "foo.twig" at line 1, column 4.';
|
||||
return 'TwingRuntimeError: Tag "do" is not allowed in "foo.twig" at line 1, column 4.';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Test extends TestBase {
|
||||
}
|
||||
|
||||
getExpectedErrorMessage(): string {
|
||||
return 'TwingSandboxSecurityError: Function "range" is not allowed in "foo.twig" at line 2, column 5.';
|
||||
return 'TwingRuntimeError: Function "range" is not allowed in "foo.twig" at line 2, column 5.';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,5 +19,5 @@ runTest({
|
||||
bar: 'foo.bar'
|
||||
}
|
||||
}),
|
||||
expectedErrorMessage: 'TwingSandboxSecurityError: Calling "bar" property on an instance of Object is not allowed in "index.twig" at line 5, column 4.'
|
||||
expectedErrorMessage: 'TwingRuntimeError: Calling "bar" property on an instance of Object is not allowed in "index.twig" at line 5, column 4.'
|
||||
})
|
||||
|
@ -4,8 +4,8 @@ import * as index from "../../../src/lib";
|
||||
tape('library index', ({same, end}) => {
|
||||
const expected: Array<string> = [
|
||||
'isATwingError',
|
||||
'createTemplateLoadingError', 'isATemplateLoadingError',
|
||||
'createRuntimeError', 'isARuntimeError',
|
||||
'createTemplateLoadingError',
|
||||
'createRuntimeError',
|
||||
'createParsingError',
|
||||
'createFilesystemLoader',
|
||||
'createArrayLoader',
|
||||
|
@ -35,7 +35,7 @@ tape('createEnvironment ', ({test}) => {
|
||||
return environment.render('foo', {})
|
||||
.then(() => fail)
|
||||
.catch((error: any) => {
|
||||
same((error as Error).name, 'TwingTemplateLoadingError');
|
||||
same((error as Error).name, 'Error');
|
||||
same((error as Error).message, 'Unable to find template "foo".');
|
||||
})
|
||||
.finally(end);
|
||||
|
@ -2,12 +2,22 @@ import * as tape from "tape";
|
||||
import {createBaseError} from "../../../../../../src/lib/error/base";
|
||||
|
||||
tape('createBaseError', ({test}) => {
|
||||
test('creates a valid TwingRuntimeError', ({same, end}) => {
|
||||
test('creates a valid TwingBaseError', ({same, end}) => {
|
||||
const previousError = 'I am Error';
|
||||
const error = createBaseError('name', 'message', undefined, undefined, previousError);
|
||||
const error = createBaseError('name', 'message', {
|
||||
column: 5,
|
||||
line: 6
|
||||
}, {
|
||||
code: 'code',
|
||||
name: 'name'
|
||||
}, previousError);
|
||||
|
||||
same(error.previous, previousError);
|
||||
same(error.rootMessage, 'message');
|
||||
same(error.location.column, 5);
|
||||
same(error.location.line, 6);
|
||||
same(error.source.code, 'code');
|
||||
same(error.source.name, 'name');
|
||||
|
||||
end();
|
||||
})
|
||||
|
@ -1,11 +1,20 @@
|
||||
import * as tape from "tape";
|
||||
import {createRuntimeError, isARuntimeError} from "../../../../../../src/lib/error/runtime";
|
||||
import {createRuntimeError} from "../../../../../../src/lib/error/runtime";
|
||||
|
||||
tape('createRuntimeError', ({test}) => {
|
||||
test('creates a valid TwingRuntimeError', ({same, end}) => {
|
||||
const error = createRuntimeError('foo');
|
||||
const error = createRuntimeError('foo', {
|
||||
column: 5,
|
||||
line: 6
|
||||
}, {
|
||||
code: 'code',
|
||||
name: 'name'
|
||||
});
|
||||
|
||||
same(isARuntimeError(error), true);
|
||||
same(error.location.column, 5);
|
||||
same(error.location.line, 6);
|
||||
same(error.source.code, 'code');
|
||||
same(error.source.name, 'name');
|
||||
|
||||
end();
|
||||
})
|
||||
|
@ -2,13 +2,21 @@ import * as tape from "tape";
|
||||
import {TwingRuntimeError} from "../../../../../../src/lib/error/runtime";
|
||||
import {createBaseNode} from "../../../../../../src/lib/node";
|
||||
import {executeNode} from "../../../../../../src/lib/node-executor";
|
||||
import type {TwingExecutionContext} from "../../../../../../src/lib/execution-context";
|
||||
|
||||
tape('executeNode => ::()', ({test}) => {
|
||||
test('throws on unrecognized node type', ({same, fail, end}) => {
|
||||
return executeNode(createBaseNode("foo", {}, {}, 0, 0), {} as any)
|
||||
return executeNode(createBaseNode("foo", {}, {}, 0, 0), {
|
||||
template: {
|
||||
source: {
|
||||
code: 'code',
|
||||
name: 'name'
|
||||
}
|
||||
}
|
||||
} as TwingExecutionContext)
|
||||
.then(fail)
|
||||
.catch((error: TwingRuntimeError) => {
|
||||
same(error.message, 'Unrecognized node of type "foo" at line 0, column 0');
|
||||
same(error.message, 'Unrecognized node of type "foo" in "name" at line 0, column 0');
|
||||
same(error.name, 'TwingRuntimeError');
|
||||
})
|
||||
.finally(end);
|
||||
|
@ -3,16 +3,24 @@ import {executeBinaryNode} from "../../../../../../src/lib/node-executor/express
|
||||
import {createBaseBinaryNode} from "../../../../../../src/lib/node/expression/binary";
|
||||
import {createConstantNode} from "../../../../../../src/lib/node/expression/constant";
|
||||
import {TwingRuntimeError} from "../../../../../../src/lib/error/runtime";
|
||||
import type {TwingExecutionContext} from "../../../../../../src/lib/execution-context";
|
||||
|
||||
tape('executeBinaryNode => ::()', ({test}) => {
|
||||
test('throws on unrecognized node type', ({same, fail, end}) => {
|
||||
return executeBinaryNode(createBaseBinaryNode("foo", [
|
||||
createConstantNode(0, 0, 0),
|
||||
createConstantNode(0, 0, 0)
|
||||
], 0, 0), {} as any)
|
||||
], 0, 0), {
|
||||
template: {
|
||||
source: {
|
||||
code: 'code',
|
||||
name: 'name'
|
||||
}
|
||||
}
|
||||
} as TwingExecutionContext)
|
||||
.then(fail)
|
||||
.catch((error: TwingRuntimeError) => {
|
||||
same(error.message, 'Unrecognized binary node of type "foo" at line 0, column 0');
|
||||
same(error.message, 'Unrecognized binary node of type "foo" in "name" at line 0, column 0');
|
||||
same(error.name, 'TwingRuntimeError');
|
||||
})
|
||||
.finally(end);
|
||||
|
@ -3,13 +3,21 @@ import {createConstantNode} from "../../../../../../src/lib/node/expression/cons
|
||||
import {TwingRuntimeError} from "../../../../../../src/lib/error/runtime";
|
||||
import {executeUnaryNode} from "../../../../../../src/lib/node-executor/expression/unary";
|
||||
import {createBaseUnaryNode} from "../../../../../../src/lib/node/expression/unary";
|
||||
import type {TwingExecutionContext} from "../../../../../../src/lib/execution-context";
|
||||
|
||||
tape('executeUnaryNode', ({test}) => {
|
||||
test('throws on unrecognized node type', ({same, fail, end}) => {
|
||||
return executeUnaryNode(createBaseUnaryNode("foo", createConstantNode(0, 0, 0), 0, 0), {} as any)
|
||||
return executeUnaryNode(createBaseUnaryNode("foo", createConstantNode(0, 0, 0), 0, 0), {
|
||||
template: {
|
||||
source: {
|
||||
code: 'code',
|
||||
name: 'name'
|
||||
}
|
||||
}
|
||||
} as TwingExecutionContext)
|
||||
.then(fail)
|
||||
.catch((error: TwingRuntimeError) => {
|
||||
same(error.message, 'Unrecognized unary node of type "foo" at line 0, column 0');
|
||||
same(error.message, 'Unrecognized unary node of type "foo" in "name" at line 0, column 0');
|
||||
same(error.name, 'TwingRuntimeError');
|
||||
|
||||
})
|
||||
|
@ -39,7 +39,10 @@ tape('node-traverser', (test) => {
|
||||
const node = traverse(createBaseNode(null, {}, {
|
||||
0: nodeToRemove,
|
||||
1: nodeToKeep
|
||||
}));
|
||||
}), {
|
||||
name: 'name',
|
||||
code: 'code'
|
||||
});
|
||||
|
||||
test.same(node?.children[0], undefined);
|
||||
test.same(node?.children[1].type, 'foo');
|
||||
|
Loading…
Reference in New Issue
Block a user