diff --git a/src/youch.ts b/src/youch.ts index d33e71d..bc001a5 100644 --- a/src/youch.ts +++ b/src/youch.ts @@ -8,7 +8,7 @@ */ import { ErrorParser } from 'youch-core' -import type { Parser, SourceLoader, Transformer } from 'youch-core/types' +import type { Parser, SourceLoader, Transformer, YouchParserOptions } from 'youch-core/types' import { Metadata } from './metadata.js' import { Templates } from './templates.js' @@ -18,7 +18,12 @@ import { YouchANSIOptions, YouchHTMLOptions, YouchJSONOptions } from './types.js * Youch exposes the API to render errors to HTML output */ export class Youch { - #errorParser = new ErrorParser() + /** + * Properties to be shared with the Error parser + */ + #sourceLoader?: SourceLoader + #parsers: Parser[] = [] + #transformers: Transformer[] = [] /** * Manage templates used for converting error to the HTML @@ -31,12 +36,27 @@ export class Youch { */ metadata = new Metadata() + /** + * Creates an instance of the ErrorParser and applies the + * source loader, parsers and transformers on it + */ + #createErrorParser(options: YouchParserOptions) { + const errorParser = new ErrorParser(options) + if (this.#sourceLoader) { + errorParser.defineSourceLoader(this.#sourceLoader) + } + this.#parsers.forEach((parser) => errorParser.useParser(parser)) + this.#transformers.forEach((transformer) => errorParser.useTransformer(transformer)) + + return errorParser + } + /** * Define custom implementation for loading the source code * of a stack frame. */ defineSourceLoader(loader: SourceLoader): this { - this.#errorParser.defineSourceLoader(loader) + this.#sourceLoader = loader return this } @@ -46,7 +66,7 @@ export class Youch { * modify the error */ useParser(parser: Parser): this { - this.#errorParser.useParser(parser) + this.#parsers.push(parser) return this } @@ -56,7 +76,7 @@ export class Youch { * properties of the parsed error. */ useTransformer(transformer: Transformer): this { - this.#errorParser.useTransformer(transformer) + this.#transformers.push(transformer) return this } @@ -65,7 +85,7 @@ export class Youch { */ async toJSON(error: unknown, options?: YouchJSONOptions) { options = { ...options } - return new ErrorParser({ offset: options.offset }).parse(error) + return this.#createErrorParser({ offset: options.offset }).parse(error) } /** @@ -74,7 +94,7 @@ export class Youch { async toHTML(error: unknown, options?: YouchHTMLOptions) { options = { ...options } - const parsedError = await new ErrorParser({ offset: options.offset }).parse(error) + const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error) return this.templates.toHTML({ title: options.title ?? 'An error has occurred', ide: options.ide ?? process.env.IDE ?? 'vscode', @@ -89,7 +109,7 @@ export class Youch { */ async toANSI(error: unknown, options?: YouchANSIOptions) { options = { ...options } - const parsedError = await new ErrorParser({ offset: options.offset }).parse(error) + const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error) return this.templates.toANSI({ title: '', diff --git a/tests/youch.spec.ts b/tests/youch.spec.ts new file mode 100644 index 0000000..5deeaf2 --- /dev/null +++ b/tests/youch.spec.ts @@ -0,0 +1,54 @@ +/* + * youch + * + * (c) Poppinss + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { test } from '@japa/runner' +import { Youch } from '../src/youch.js' +import { stripAnsi } from '../src/helpers.js' + +test.group('Youch', () => { + test('convert error toJSON', async ({ assert }) => { + const parsedError = await new Youch().toJSON(new Error('Something went wrong')) + assert.equal(parsedError.message, 'Something went wrong') + assert.equal(parsedError.frames[0].lineNumber, 16) + }) + + test('apply parser to the error', async ({ assert }) => { + const parsedError = await new Youch() + .useParser((source) => { + return new Error('Wrapped', { cause: source }) + }) + .toJSON(new Error('Something went wrong')) + + assert.equal(parsedError.message, 'Wrapped') + assert.equal((parsedError.cause as any).message, 'Something went wrong') + assert.equal(parsedError.frames[0].lineNumber, 24) + }) + + test('apply transformer to the error', async ({ assert }) => { + const parsedError = await new Youch() + .useTransformer((error) => { + error.frames = error.frames.filter((frame) => { + return frame.type === 'app' + }) + }) + .toJSON(new Error('Something went wrong')) + + assert.lengthOf(parsedError.frames, 1) + }) + + test('convert error to ANSI output', async ({ assert }) => { + const output = await new Youch().toANSI(new Error('Something went wrong')) + assert.include(stripAnsi(output), 'at Object.executor (tests/youch.spec.ts:45:45)') + }) + + test('convert error to HTML output', async ({ assert }) => { + const output = await new Youch().toHTML(new Error('Something went wrong')) + assert.include(output, 'at Object.executor (tests/youch.spec.ts:51:45)') + }) +})