mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-18 05:50:43 -07:00
139 lines
4.1 KiB
TypeScript
139 lines
4.1 KiB
TypeScript
import * as _ from './underscore'
|
|
import { Token } from '../tokens/token'
|
|
import { Template } from '../template/template'
|
|
|
|
/**
|
|
* targeting ES5, extends Error won't create a proper prototype chain, need a trait to keep track of classes
|
|
*/
|
|
const TRAIT = '__liquidClass__'
|
|
|
|
export abstract class LiquidError extends Error {
|
|
public token!: Token
|
|
public context = ''
|
|
public originalError?: Error
|
|
public constructor (err: Error | string, token: Token) {
|
|
/**
|
|
* note: for ES5 targeting, `this` will be replaced by return value of Error(),
|
|
* thus everything on `this` will be lost, avoid calling `LiquidError` methods here
|
|
*/
|
|
super(typeof err === 'string' ? err : err.message)
|
|
if (typeof err !== 'string') Object.defineProperty(this, 'originalError', { value: err, enumerable: false })
|
|
Object.defineProperty(this, 'token', { value: token, enumerable: false })
|
|
Object.defineProperty(this, TRAIT, { value: 'LiquidError', enumerable: false })
|
|
}
|
|
protected update () {
|
|
Object.defineProperty(this, 'context', { value: mkContext(this.token), enumerable: false })
|
|
this.message = mkMessage(this.message, this.token)
|
|
this.stack = this.message + '\n' + this.context +
|
|
'\n' + this.stack
|
|
if (this.originalError) this.stack += '\nFrom ' + this.originalError.stack
|
|
}
|
|
static is (obj: unknown): obj is LiquidError {
|
|
return obj?.[TRAIT] === 'LiquidError'
|
|
}
|
|
}
|
|
|
|
export class TokenizationError extends LiquidError {
|
|
public constructor (message: string, token: Token) {
|
|
super(message, token)
|
|
this.name = 'TokenizationError'
|
|
super.update()
|
|
}
|
|
}
|
|
|
|
export class ParseError extends LiquidError {
|
|
public constructor (err: Error, token: Token) {
|
|
super(err, token)
|
|
this.name = 'ParseError'
|
|
this.message = err.message
|
|
super.update()
|
|
}
|
|
}
|
|
|
|
export class RenderError extends LiquidError {
|
|
public constructor (err: Error, tpl: Template) {
|
|
super(err, tpl.token)
|
|
this.name = 'RenderError'
|
|
this.message = err.message
|
|
super.update()
|
|
}
|
|
public static is (obj: any): obj is RenderError {
|
|
return obj.name === 'RenderError'
|
|
}
|
|
}
|
|
|
|
export class LiquidErrors extends LiquidError {
|
|
public constructor (public errors: RenderError[]) {
|
|
super(errors[0], errors[0].token)
|
|
this.name = 'LiquidErrors'
|
|
const s = errors.length > 1 ? 's' : ''
|
|
this.message = `${errors.length} error${s} found`
|
|
super.update()
|
|
}
|
|
public static is (obj: any): obj is LiquidErrors {
|
|
return obj.name === 'LiquidErrors'
|
|
}
|
|
}
|
|
|
|
export class UndefinedVariableError extends LiquidError {
|
|
public constructor (err: Error, token: Token) {
|
|
super(err, token)
|
|
this.name = 'UndefinedVariableError'
|
|
this.message = err.message
|
|
super.update()
|
|
}
|
|
}
|
|
|
|
// only used internally; raised where we don't have token information,
|
|
// so it can't be an UndefinedVariableError.
|
|
export class InternalUndefinedVariableError extends Error {
|
|
variableName: string
|
|
|
|
public constructor (variableName: string) {
|
|
super(`undefined variable: ${variableName}`)
|
|
this.name = 'InternalUndefinedVariableError'
|
|
this.variableName = variableName
|
|
}
|
|
}
|
|
|
|
export class AssertionError extends Error {
|
|
public constructor (message: string) {
|
|
super(message)
|
|
this.name = 'AssertionError'
|
|
this.message = message + ''
|
|
}
|
|
}
|
|
|
|
function mkContext (token: Token) {
|
|
const [line, col] = token.getPosition()
|
|
const lines = token.input.split('\n')
|
|
const begin = Math.max(line - 2, 1)
|
|
const end = Math.min(line + 3, lines.length)
|
|
|
|
const context = _
|
|
.range(begin, end + 1)
|
|
.map(lineNumber => {
|
|
const rowIndicator = (lineNumber === line) ? '>> ' : ' '
|
|
const num = _.padStart(String(lineNumber), String(end).length)
|
|
let text = `${rowIndicator}${num}| `
|
|
|
|
const colIndicator = lineNumber === line
|
|
? '\n' + _.padStart('^', col + text.length)
|
|
: ''
|
|
|
|
text += lines[lineNumber - 1]
|
|
text += colIndicator
|
|
return text
|
|
})
|
|
.join('\n')
|
|
|
|
return context
|
|
}
|
|
|
|
function mkMessage (msg: string, token: Token) {
|
|
if (token.file) msg += `, file:${token.file}`
|
|
const [line, col] = token.getPosition()
|
|
msg += `, line:${line}, col:${col}`
|
|
return msg
|
|
}
|