//#region src/formatters/plain.ts /** * Renders a diagnostic into a multi-line, unicode-decorated string suitable * for terminal output. The first line is `[] `; optional * details (`fix`, `sources`, `docs`) follow with `├▶`/`╰▶` connectors. */ function formatDiagnostic(diagnostic) { const header = `[${diagnostic.name}] ${diagnostic.message}`; const details = []; if (diagnostic.fix) details.push(`fix: ${diagnostic.fix}`); if (diagnostic.sources?.length) details.push(`sources: ${diagnostic.sources.join(", ")}`); if (diagnostic.docs) details.push(`see: ${diagnostic.docs}`); if (details.length === 0) return header; return [header, ...details.map((detail, i) => { return `${i < details.length - 1 ? "├▶" : "╰▶"} ${detail}`; })].join("\n"); } //#endregion //#region src/utils.ts /** * Transforms a value or a function that returns a value to a value. * * @param valFn either a value or a function that returns a value * @param args arguments to pass to the function if `valFn` is a function * * @internal */ function toValueWithArgs(valFn, ...args) { return typeof valFn === "function" ? valFn(...args) : valFn; } //#endregion //#region src/diagnostic.ts /** * Creates a console reporter that renders each diagnostic with `formatter` and * prints the result via `console[method]`. Both default sensibly (`'warn'` and * {@link formatDiagnostic}); `method` can also be overridden per call through * the reporter options. */ /* @__NO_SIDE_EFFECTS__ */ function createConsoleReporter({ method: defaultMethod = "warn", formatter = formatDiagnostic } = {}) { return (diagnostic, { method = defaultMethod } = {}) => { console[method](formatter(diagnostic)); }; } const captureStackTrace = Error.captureStackTrace; var Diagnostic = class Diagnostic extends Error { name; /** * The diagnostic code, e.g. `MATH_E001`. * Also appears as the `name` property. */ code; /** * URL to extended documentation for this diagnostic code. * Auto-generated from {@link DefineDiagnosticsOptions.docsBase}. */ docs; /** * Optional actionable instructions on how to resolve the problem. */ fix; /** * Locations in user code that contributed to this diagnostic, in * `file:line:column` format. Relevant when the stack trace doesn't reflect * the user's source (e.g. compilers, bundlers), otherwise redundant with the * stack and should be omitted. */ sources; /** * Alias for {@link Error.message}: the reason this diagnostic was raised. */ get why() { return this.message; } /** * @param init structured initializer; `why` is required * @param captureFrom V8 stack-cutoff frame. Defaults to {@link Diagnostic} * so the top of the trace is the `new Diagnostic(...)` call site. * `defineDiagnostics` passes its action method to strip its own frames too. * Ignored on engines without `Error.captureStackTrace`. */ constructor(init, captureFrom = Diagnostic) { super(init.why, { cause: init.cause }); this.code = this.name = init.code; this.fix = init.fix; this.docs = init.docs; this.sources = init.sources; captureStackTrace?.(this, captureFrom); } /** * Converts the diagnostic into a serializable structured object. */ toJSON() { return { name: this.name, why: this.why, fix: this.fix, docs: this.docs, sources: this.sources, cause: this.cause, stack: this.stack }; } }; /** * Resolves the docs URL for a code from a `docsBase` (string template or * resolver function). Shared by {@link defineDiagnostics} and * {@link defineProdDiagnostics}. Per-code `docs` overrides are handled by the * caller; this only covers the `docsBase`-derived case. * * @internal */ function deriveDocs(docsBase, code) { return typeof docsBase === "string" ? `${docsBase}/${code.toLowerCase()}` : docsBase?.(code); } /** * Creates a typed diagnostics object from a set of code definitions. Each * code becomes a callable {@link DiagnosticHandle}: invoke to report, or * `throw` the result to raise. No `new` required, no proxy. */ /* @__NO_SIDE_EFFECTS__ */ function defineDiagnostics(options) { const reporters = options.reporters ?? []; const result = {}; const { docsBase } = options; for (const code of Object.keys(options.codes)) { const def = options.codes[code]; const docs = def.docs === false ? void 0 : def.docs || deriveDocs(docsBase, code); const handle = (params = {}, reporterOptions = {}) => { const diagnostic = new Diagnostic({ code, why: toValueWithArgs(def.why, params), fix: toValueWithArgs(def.fix, params), docs, cause: params.cause, sources: params.sources }, handle); for (const reporter of reporters) reporter(diagnostic, reporterOptions); return diagnostic; }; result[code] = handle; } return result; } //#endregion //#region src/prod-diagnostics.ts /** * Production counterpart to {@link defineDiagnostics}. Returns a `Proxy` that * builds a minimal {@link Diagnostic} for any accessed code: the code becomes * the instance `name`, `docs` is derived from `docsBase`, and `why` points to * the docs URL when one exists (empty otherwise, so the thrown header is just * the code). It carries no catalog text, so it stays tiny in a bundle. * * The strip plugin (`@nostics/unplugin`) can rewrite a `defineDiagnostics()` * call into a `process.env.NODE_ENV === 'production'` ternary that selects this * factory in production, dropping every `why`/`fix` string from the bundle. * * @example * ```ts * const diagnostics = defineProdDiagnostics({ docsBase: 'https://docs.example.com' }) * throw diagnostics.NUXT_B2011() // NUXT_B2011: https://docs.example.com/nuxt_b2011 * ``` */ /* @__NO_SIDE_EFFECTS__ */ function defineProdDiagnostics(options = {}) { const { docsBase, reporters = [] } = options; return new Proxy({}, { get(_target, code) { if (typeof code !== "string") return void 0; const handle = (params = {}, reporterOptions = {}) => { const docs = deriveDocs(docsBase, code); const diagnostic = new Diagnostic({ code, why: docs ?? "", docs, cause: params.cause, sources: params.sources }, handle); for (const reporter of reporters) reporter(diagnostic, reporterOptions); return diagnostic; }; return handle; } }); } //#endregion export { Diagnostic, createConsoleReporter, defineDiagnostics, defineProdDiagnostics, formatDiagnostic }; //# sourceMappingURL=index.mjs.map