前端初始化
This commit is contained in:
+187
@@ -0,0 +1,187 @@
|
||||
//#region src/formatters/plain.ts
|
||||
/**
|
||||
* Renders a diagnostic into a multi-line, unicode-decorated string suitable
|
||||
* for terminal output. The first line is `[<name>] <message>`; 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
|
||||
Reference in New Issue
Block a user