diff --git a/src/errors.mjs b/src/errors.mjs new file mode 100644 index 0000000..277a220 --- /dev/null +++ b/src/errors.mjs @@ -0,0 +1,95 @@ +/** + * Runtime error – user attempted to call a function + * which is not a predefined function, nor specified + * in `options.extraFunctions`. + * + * @prop {string} functionName + * @prop {string} I18N_STRING has the value `'UNKNOWN_FUNCTION'` + */ +export class UnknownFunctionError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_FUNCTION' + + constructor (funcName) { + super(`Unknown function: ${funcName}()`) + this.functionName = funcName + } +} + +/** + * Runtime error – user attempted to access a property which + * is not present in the `data` object, nor in the `constants`. + * If the property is meant to be empty, use `undefined` or + * `null` as its value. If you need to use optional properties + * in your `data`, define a `customProp` function. + * + * @prop {string} propertyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_PROPERTY'` + */ +export class UnknownPropertyError +extends ReferenceError { + I18N_STRING = 'UNKNOWN_PROPERTY' + + constructor (propName) { + super(`Property “${propName}” does not exist.`) + this.propertyName = propName + } +} + +/** + * Compile time error – you specified an option which + * was not recognized by Filtrex. Double-check your + * spelling and the version of Filtrex you are using. + * + * @prop {string} keyName + * @prop {string} I18N_STRING has the value `'UNKNOWN_OPTION'` + */ +export class UnknownOptionError +extends TypeError { + I18N_STRING = 'UNKNOWN_OPTION' + + constructor (key) { + super(`Unknown option: ${key}`) + this.keyName = key + } +} + +/** + * Runtime error – user passed a different type than the one + * accepted by the function or operator. + * + * The possible values of `expectedType` and `recievedType` + * are: `"undefined"`, `"null"`, `"true"`, `"false"`, `"number"`, + * `"text"`, `"unknown type"`, `"list"`, `"object"`, `"text or number"` + * and `"logical value (“true” or “false”)"` + * + * @prop {string} expectedType + * @prop {string} recievedType + * @prop {string} I18N_STRING has the value `'UNEXPECTED_TYPE'` + */ +export class UnexpectedTypeError +extends TypeError { + I18N_STRING = 'UNEXPECTED_TYPE' + + constructor (expected, got) { + super(`Expected a ${expected}, but got a ${got} instead.`) + + this.expectedType = expected + this.recievedType = got + } +} + +/** + * An internal error. This was not meant to happen, please report + * at https://github.com/m93a/filtrex/ + * + * @prop {string} I18N_STRING has the value `'INTERNAL'` + */ +export class InternalError +extends Error { + I18N_STRING = 'INTERNAL' + + constructor (message) { + super(message) + } +} diff --git a/src/filtrex.mjs b/src/filtrex.mjs index c24ab6a..5a0cc86 100644 --- a/src/filtrex.mjs +++ b/src/filtrex.mjs @@ -1,6 +1,7 @@ // the parser is dynamically generated from generateParser.js at compile time import { parser } from './parser.mjs' import { hasOwnProperty, bool, num, numstr, mod, arr, str, flatten, code } from './utils.mjs' +import { UnknownFunctionError, UnknownPropertyError, UnknownOptionError, InternalError } from './errors.mjs' // Shared utility functions const std = @@ -11,7 +12,7 @@ const std = }, unknown(funcName) { - throw new ReferenceError('Unknown function: ' + funcName + '()') + throw new UnknownFunctionError(funcName) }, coerceArray: arr, @@ -32,22 +33,22 @@ const std = let built = '' if (literal[0] !== quote || literal[literal.length-1] !== quote) - throw new Error(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal doesn't begin/end with the right quotation mark.`) for (let i = 1; i < literal.length - 1; i++) { if (literal[i] === "\\") { i++; - if (i >= literal.length - 1) throw new Error(`Unexpected internal error: Unescaped backslash at the end of string literal.`) + if (i >= literal.length - 1) throw new InternalError(`Unexpected internal error: Unescaped backslash at the end of string literal.`) if (literal[i] === "\\") built += '\\' else if (literal[i] === quote) built += quote - else throw new Error(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) + else throw new InternalError(`Unexpected internal error: Invalid escaped character in string literal: ${literal[i]}`) } else if (literal[i] === quote) { - throw new Error(`Unexpected internal error: String literal contains unescaped quotation mark.`) + throw new InternalError(`Unexpected internal error: String literal contains unescaped quotation mark.`) } else { @@ -103,7 +104,7 @@ export function compileExpression(expression, options) { for (const key of Object.keys(options)) { if (!(["extraFunctions", "customProp", "operators"].includes(key))) - throw new TypeError(`Unknown option: ${key}`) + throw new UnknownOptionError(key) } @@ -174,7 +175,7 @@ export function compileExpression(expression, options) { if (hasOwnProperty(obj||{}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } function safeGetter(obj) { @@ -182,7 +183,7 @@ export function compileExpression(expression, options) { if (hasOwnProperty(obj||{}, name)) return obj[name] - throw new ReferenceError(`Property “${name}” does not exist.`) + throw new UnknownPropertyError(name) } } @@ -195,7 +196,7 @@ export function compileExpression(expression, options) { if (hasOwnProperty(fns, name) && typeof fns[name] === "function") return fns[name](...args) - throw new ReferenceError(`Unknown function: ${name}()`) + throw new UnknownFunctionError(name) } } diff --git a/src/utils.mjs b/src/utils.mjs index 152f603..759e3f4 100644 --- a/src/utils.mjs +++ b/src/utils.mjs @@ -1,3 +1,5 @@ +import { UnexpectedTypeError } from './errors.mjs' + /** * Determines whether an object has a property with the specified name. * @param {object} obj the object to be checked @@ -77,33 +79,33 @@ export function num(value) { value = unwrap(value) if (typeof value === 'number') return value - throw new TypeError(`Expected a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('number', prettyType(value)) } export function str(value) { value = unwrap(value) if (typeof value === 'string') return value - throw new TypeError(`Expected a text, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text', prettyType(value)) } export function numstr(value) { value = unwrap(value) if (typeof value === 'string' || typeof value === 'number') return value - throw new TypeError(`Expected a text or a number, but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('text or number', prettyType(value)) } export function bool(value) { value = unwrap(value) if (typeof value === 'boolean') return value - throw new TypeError(`Expected a logical value (“true” or “false”), but got a ${prettyType(value)} instead.`) + throw new UnexpectedTypeError('logical value (“true” or “false”)', prettyType(value)) } export function arr(value) { if (value === undefined || value === null) { - throw new TypeError(`Expected a list, but got ${value} instead.`) + throw new UnexpectedTypeError('list', prettyType(value)) } if (Array.isArray(value)) {