From 6c68f1ab2f527c644c0d15e69a53e561e7dd602e Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Mon, 21 Nov 2022 13:34:22 -0400 Subject: [PATCH] Add JSDoc comments to errors for doc generation (#5355) * Start adding JSDocs to errors for doc generation * Progress * Add titles * Update with feedback * Update tests * Update packages/astro/src/core/errors/README.md Co-authored-by: Sarah Rainsberger * Update packages/astro/src/core/errors/README.md Co-authored-by: Sarah Rainsberger * Misc tweaks * Remove unnecessary character * Fix errors in build not having proper information Co-authored-by: Sarah Rainsberger --- .github/CODEOWNERS | 1 + packages/astro/e2e/errors.test.js | 2 +- packages/astro/src/core/compile/compile.ts | 10 +- packages/astro/src/core/endpoint/index.ts | 6 +- packages/astro/src/core/errors/README.md | 74 +++- packages/astro/src/core/errors/errors-data.ts | 409 ++++++++++++++++-- packages/astro/src/core/errors/errors.ts | 18 +- packages/astro/src/core/render/result.ts | 8 +- packages/astro/src/core/render/route-cache.ts | 2 +- packages/astro/src/core/routing/validation.ts | 6 +- .../astro/src/runtime/server/hydration.ts | 5 +- 11 files changed, 455 insertions(+), 86 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 14195c36b..0f1dfcefd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,3 @@ README.md @withastro/maintainers-docs packages/astro/src/@types/astro.ts @withastro/maintainers-docs +packages/astro/src/core/errors/errors-data.ts @withastro/maintainers-docs diff --git a/packages/astro/e2e/errors.test.js b/packages/astro/e2e/errors.test.js index ee0b70e67..c9aff5269 100644 --- a/packages/astro/e2e/errors.test.js +++ b/packages/astro/e2e/errors.test.js @@ -39,7 +39,7 @@ test.describe('Error display', () => { const message = await getErrorOverlayMessage(page); expect(message).toMatch( - 'Could not import "../abc.astro".\n\nThis is often caused by a typo in the import path. Please make sure the file exists.' + 'Could not import `../abc.astro`.\n\nThis is often caused by a typo in the import path. Please make sure the file exists.' ); await Promise.all([ diff --git a/packages/astro/src/core/compile/compile.ts b/packages/astro/src/core/compile/compile.ts index 716c45e86..a305ef5ec 100644 --- a/packages/astro/src/core/compile/compile.ts +++ b/packages/astro/src/core/compile/compile.ts @@ -90,14 +90,18 @@ async function compile({ return result; case 1: { let error = cssTransformErrors[0]; - if (!error.code) { - error.code = AstroErrorData.UnknownCSSError.code; + if (!error.errorCode) { + error.errorCode = AstroErrorData.UnknownCSSError.code; } throw cssTransformErrors[0]; } default: { - throw new AggregateError({ ...cssTransformErrors[0], errors: cssTransformErrors }); + throw new AggregateError({ + ...cssTransformErrors[0], + code: cssTransformErrors[0].errorCode, + errors: cssTransformErrors, + }); } } }); diff --git a/packages/astro/src/core/endpoint/index.ts b/packages/astro/src/core/endpoint/index.ts index a957ef9a7..0743f75c3 100644 --- a/packages/astro/src/core/endpoint/index.ts +++ b/packages/astro/src/core/endpoint/index.ts @@ -54,8 +54,8 @@ function createAPIContext({ if (!(clientAddressSymbol in request)) { if (adapterName) { throw new AstroError({ - ...AstroErrorData.SSRClientAddressNotAvailableInAdapter, - message: AstroErrorData.SSRClientAddressNotAvailableInAdapter.message(adapterName), + ...AstroErrorData.ClientAddressNotAvailable, + message: AstroErrorData.ClientAddressNotAvailable.message(adapterName), }); } else { throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable); @@ -124,6 +124,6 @@ function isRedirect(statusCode: number) { export function throwIfRedirectNotAllowed(response: Response, config: AstroConfig) { if (config.output !== 'server' && isRedirect(response.status)) { - throw new AstroError(AstroErrorData.StaticRedirectNotAllowed); + throw new AstroError(AstroErrorData.StaticRedirectNotAvailable); } } diff --git a/packages/astro/src/core/errors/README.md b/packages/astro/src/core/errors/README.md index 60d37bf68..af2c05b9e 100644 --- a/packages/astro/src/core/errors/README.md +++ b/packages/astro/src/core/errors/README.md @@ -5,14 +5,38 @@ ## Writing error messages for Astro ### Tips + +**Error Format** + +Name (key of the object definition): +- As with the error code, this property is a static reference to the error. The shape should be similar to JavaScript's native errors (TypeError, ReferenceError): pascal-cased, no spaces, no special characters etc. (ex: `ClientAddressNotAvailable`) +- This is the only part of the error message that should not be written as a full, proper sentence complete with Capitalization and end punctuation. + +Title: +- Use this property to briefly describe the error in a few words. This is the user's way to see at a glance what has happened and will be prominently displayed in the UI (ex: `{feature} is not available in static mode.`) Do not include further details such as why this error occurred or possible solutions. + +Message: +- Begin with **what happened** and **why**. (ex: `Could not use {feature} because Server⁠-⁠side Rendering is not enabled.`) +- Then, **describe the action the user should take**. (ex: `Update your Astro config with `output: 'server'` to enable Server⁠-⁠side Rendering.`) +- Although this does not need to be as brief as the `title`, try to keep sentences short, clear and direct to give the reader all the necessary information quickly as possible. +- Instead of writing a longer message, consider using a `hint`. + +Hint: +- A `hint` can be used for any additional info that might help the user. (ex: a link to the documentation, or a common cause) + +**Writing Style** +- Write in proper sentences. Include periods at the end of sentences. Avoid using exclamation marks! (Leave them to Houston!) +- Technical jargon is mostly okay! But, most abbreviations should be avoided. If a developer is unfamiliar with a technical term, spelling it out in full allows them to look it up on the web more easily. +- Describe the _what_, _why_ and _action to take_ from the user's perspective. Assume they don't know Astro internals, and care only about how Astro is _used_. (ex: `You are missing...` vs `Astro/file cannot find...`) +- Avoid using cutesy language. (ex: Oops!) This tone minimizes the significance of the error, which _is_ important to the developer. The developer may be frustrated and your error message shouldn't be making jokes about their struggles. Only include words and phrases that help the developer **interpret the error** and **fix the problem**. **Choosing an Error Code** Choose any available error code in the appropriate range: - 01xxx and 02xxx are reserved for compiler errors and warnings respectively - 03xxx: Astro errors (your error most likely goes here!) -- 04xxx: CSS errors -- 05xxx: Vite errors +- 04xxx: Vite errors +- 05xxx: CSS errors - 06xxx: Markdown errors - 07xxx: Configuration errors - 07xxx-98xxx <- Need to add a category? Add it here! @@ -23,31 +47,49 @@ As long as it is unique, the exact error code used is unimportant. For example, Users are not reading codes sequentially. They're much more likely to directly land on the error or search for a specific code. If you are unsure about which error code to choose, ask [Erika](https://github.com/Princesseuh)! - -**Error Code Format** -- Begin with **what happened** and **why** (ex: `Could not use {feature} because Server⁠-⁠side Rendering is not enabled`) -- Then, **describe the action the user should take** (ex: `Update your Astro config with `output: 'server'` to enable Server⁠-⁠side Rendering.`) -- A `hint` can be used for any additional info that might help the user (ex: a link to the documentation, or a common cause) -**Error Code Writing Style** -- Technical jargon is mostly okay! But, most abbreviations should be avoided. If a developer is unfamiliar with a technical term, spelling it out in full allows them to look it up on the web more easily. -- Describe the what, why and action to take from the user's perspective. Assume they don't know Astro internals, and care only about how Astro is _used_ (ex: `You are missing...` vs `Astro/file cannot find...`) -- Avoid using cutesy language (ex: Oops!). This tone minimizes the significance of the error, which _is_ important to the developer. The developer may be frustrated and your error message shouldn't be making jokes about their struggles. Only include words and phrases that help the developer **interpret the error** and **fix the problem**. - -### CLI specifics: +### CLI specifics tips: - If the error happened **during an action that changes the state of the project** (ex: editing configuration, creating files), the error should **reassure the user** about the state of their project (ex: "Failed to update configuration. Your project has been restored to its previous state.") - If an "error" happened because of a conscious user action (ex: pressing CTRL+C during a choice), it is okay to add more personality (ex: "Operation cancelled. See you later, astronaut!"). Do keep in mind the previous point however (ex: "Operation cancelled. No worries, your project folder has already been created") ### Shape - **Error codes and names are permanent**, and should never be changed, nor deleted. Users should always be able to find an error by searching, and this ensures a matching result. When an error is no longer relevant, it should be deprecated, not removed. -- Contextual information may be used to enhance the message or the hint. However, the error code itself should not be included in the message as it will already be shown as part of the the error. -- Do not prefix `message` and `hint` with descriptive words such as "Error:" or "Hint:" as it may lead to duplicated labels in the UI / CLI. +- Contextual information may be used to enhance the message or the hint. However, the code that caused the error or the position of the error should not be included in the message as they will already be shown as part of the error. +- Do not prefix `title`, `message` and `hint` with descriptive words such as "Error:" or "Hint:" as it may lead to duplicated labels in the UI / CLI. + +### Documentation support through JSDoc + +Using JSDoc comments, [a reference for every error message](https://docs.astro.build/en/reference/error-reference/) is built automatically on our docs. Users can then search for a error code to find more information on how to fix the error they encountered. + +Here's how to create and format the comments: + +```js +/** + * @docs <- Needed for the comment to be used for docs + * @message <- (Optional) Clearer error message to show in cases where the original one is too complex (ex: because of conditional messages) + * @see <- List of additional references users can look at + * @description <- Description of the error + */ +``` +Example: +```js +/** + * @docs + * @message Route returned a `returnedValue`. Only a Response can be returned from Astro files. + * @see + * - [Response](https://docs.astro.build/en/guides/server-side-rendering/#response) + * @description + * Only instances of [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) can be returned inside Astro files. + */ +``` + +The `@message` property is intended to provide slightly more context when it is helpful: a more descriptive error message or a collection of common messages if there are multiple possible error messages. Try to avoid making substantial changes to existing messages so that they are easy to find for users who copy and search the exact content of an error message. ### Always remember Error are a reactive strategy. They are the last line of defense against a mistake. -Before adding a new error message, ask yourself, "Was there a way this situation could've been avoided in the first place?" (docs, editor tooling etc). +While adding a new error message, ask yourself, "Was there a way this situation could've been avoided in the first place?" (docs, editor tooling etc). **If you can prevent the error, you don't need an error message!** diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts index f02ad185f..8e8f41c43 100644 --- a/packages/astro/src/core/errors/errors-data.ts +++ b/packages/astro/src/core/errors/errors-data.ts @@ -4,6 +4,7 @@ interface ErrorData { code: number; + title: string; message?: string | ((...params: any) => string); hint?: string | ((...params: any) => string); } @@ -12,47 +13,137 @@ interface ErrorData { const defineErrors = >(errs: T) => errs; export const AstroErrorData = defineErrors({ UnknownCompilerError: { + title: 'Unknown compiler error.', code: 1000, }, // 1xxx and 2xxx codes are reserved for compiler errors and warnings respectively - StaticRedirectNotAllowed: { + /** + * @docs + * @kind heading + * @name Astro Errors + */ + /** + * @docs + * @see + * - [Enabling SSR in Your Project](https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project) + * - [Astro.redirect](https://docs.astro.build/en/guides/server-side-rendering/#astroredirect) + * @description + * The `Astro.redirect` function is only available when [Server-side rendering](/en/guides/server-side-rendering/) is enabled. + * + * To redirect on a static website, the [meta refresh attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) can be used. Certain hosts also provide config-based redirects (ex: [Netlify redirects](https://docs.netlify.com/routing/redirects/)). + */ + StaticRedirectNotAvailable: { + title: '`Astro.redirect` is not available in static mode.', code: 3001, message: - "Redirects are only available when using output: 'server'. Update your Astro config if you need SSR features.", + "Redirects are only available when using `output: 'server'`. Update your Astro config if you need SSR features.", hint: 'See https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project for more information on how to enable SSR.', }, - SSRClientAddressNotAvailableInAdapter: { + /** + * @docs + * @see + * - [Official integrations](https://docs.astro.build/en/guides/integrations-guide/#official-integrations) + * - [Astro.clientAddress](https://docs.astro.build/en/reference/api-reference/#astroclientaddress) + * @description + * The adapter you.'re using unfortunately does not support `Astro.clientAddress`. + */ + ClientAddressNotAvailable: { + title: '`Astro.clientAddress` is not available in current adapter.', code: 3002, message: (adapterName: string) => - `Astro.clientAddress is not available in the ${adapterName} adapter. File an issue with the adapter to add support.`, + `\`Astro.clientAddress\` is not available in the \`${adapterName}\` adapter. File an issue with the adapter to add support.`, }, + /** + * @docs + * @see + * - [Enabling SSR in Your Project](https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project) + * - [Astro.clientAddress](https://docs.astro.build/en/reference/api-reference/#astroclientaddress) + * @description + * The `Astro.clientAddress` property is only available when [Server-side rendering](https://docs.astro.build/en/guides/server-side-rendering/) is enabled. + * + * To get the user's IP address in static mode, different APIs such as [Ipify](https://www.ipify.org/) can be used in a [Client-side script](https://docs.astro.build/en/core-concepts/astro-components/#client-side-scripts) or it may be possible to get the user's IP using a serverless function hosted on your hosting provider. + */ StaticClientAddressNotAvailable: { + title: '`Astro.clientAddress` is not available in static mode.', code: 3003, message: - "Astro.clientAddress is only available when using output: 'server'. Update your Astro config if you need SSR features.", + "`Astro.clientAddress` is only available when using `output: 'server'`. Update your Astro config if you need SSR features.", hint: 'See https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project for more information on how to enable SSR.', }, + /** + * @docs + * @see + * - [getStaticPaths()](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * @description + * A [dynamic route](https://docs.astro.build/en/core-concepts/routing/#dynamic-routes) was matched, but no corresponding path was found for the requested parameters. This is often caused by a typo in either the generated or the requested path. + */ NoMatchingStaticPathFound: { + title: 'No static path found for requested path.', code: 3004, message: (pathName: string) => - `A getStaticPaths route pattern was matched, but no matching static path was found for requested path ${pathName}.`, + `A \`getStaticPaths()\` route pattern was matched, but no matching static path was found for requested path \`${pathName}\`.`, hint: (possibleRoutes: string[]) => `Possible dynamic routes being matched: ${possibleRoutes.join(', ')}.`, }, + /** + * @docs + * @message Route returned a `RETURNED_VALUE`. Only a Response can be returned from Astro files. + * @see + * - [Response](https://docs.astro.build/en/guides/server-side-rendering/#response) + * @description + * Only instances of [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) can be returned inside Astro files. + * ```astro title="pages/login.astro" + * --- + * return new Response(null, { + * status: 404, + * statusText: 'Not found' + * }); + * + * // Alternatively, for redirects, Astro.redirect also returns an instance of Response + * return Astro.redirect('/login'); + * --- + * ``` + * + */ OnlyResponseCanBeReturned: { + title: 'Invalid type returned by Astro page.', code: 3005, message: (route: string | undefined, returnedValue: string) => `Route ${ route ? route : '' - } returned a ${returnedValue}. Only a Response can be returned from Astro files.`, + } returned a \`${returnedValue}\`. Only a Response can be returned from Astro files.`, hint: 'See https://docs.astro.build/en/guides/server-side-rendering/#response for more information.', }, + /** + * @docs + * @see + * - [`client:media`](https://docs.astro.build/en/reference/directives-reference/#clientmedia) + * @description + * A [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries) parameter is required when using the `client:media` directive. + * + * ```astro + * + * ``` + */ MissingMediaQueryDirective: { + title: 'Missing value for `client:media` directive.', code: 3006, - message: (componentName: string) => - `Media query not provided for "client:media" directive. A media query similar to <${componentName} client:media="(max-width: 600px)" /> must be provided`, + message: + 'Media query not provided for `client:media` directive. A media query similar to `client:media="(max-width: 600px)"` must be provided', }, + /** + * @docs + * @message Unable to render `COMPONENT_NAME`. There are `RENDERER_COUNT` renderer(s) configured in your `astro.config.mjs` file, but none were able to server-side render `COMPONENT_NAME`. + * @see + * - [Frameworks components](https://docs.astro.build/en/core-concepts/framework-components/) + * - [UI Frameworks](https://docs.astro.build/en/guides/integrations-guide/#official-integrations) + * @description + * None of the installed integrations were able to render the component you imported. Make sure to install the appropriate integration for the type of component you are trying to include in your page. + * + * For JSX / TSX files, [@astrojs/react](https://docs.astro.build/en/guides/integrations-guide/react/), [@astrojs/preact](https://docs.astro.build/en/guides/integrations-guide/preact/) or [@astrojs/solid-js](https://docs.astro.build/en/guides/integrations-guide/solid-js/) can be used. For Vue and Svelte files, the [@astrojs/vue](https://docs.astro.build/en/guides/integrations-guide/vue/) and [@astrojs/svelte](https://docs.astro.build/en/guides/integrations-guide/svelte/) integrations can be used respectively + */ NoMatchingRenderer: { + title: 'No matching renderer found.', code: 3007, message: ( componentName: string, @@ -60,135 +151,365 @@ export const AstroErrorData = defineErrors({ plural: boolean, validRenderersCount: number ) => - `Unable to render ${componentName}! + `Unable to render \`${componentName}\`. ${ validRenderersCount > 0 - ? `There ${plural ? 'are' : 'is'} ${validRenderersCount} renderer${ - plural ? 's' : '' + ? `There ${plural ? 'are.' : 'is.'} ${validRenderersCount} renderer${ + plural ? 's.' : '' } configured in your \`astro.config.mjs\` file, -but ${plural ? 'none were' : 'it was not'} able to server-side render ${componentName}.` +but ${plural ? 'none were.' : 'it was not.'} able to server-side render \`${componentName}\`.` : `No valid renderer was found ${ componentExtension - ? `for the .${componentExtension} file extension.` + ? `for the \`.${componentExtension}\` file extension.` : `for this file extension.` }` }`, hint: (probableRenderers: string) => `Did you mean to enable the ${probableRenderers} integration?\n\nSee https://docs.astro.build/en/core-concepts/framework-components/ for more information on how to install and configure integrations.`, }, + /** + * @docs + * @see + * - [addRenderer option](https://docs.astro.build/en/reference/integrations-reference/#addrenderer-option) + * - [Hydrating framework components](https://docs.astro.build/en/core-concepts/framework-components/#hydrating-interactive-components) + * @description + * Astro tried to hydrate a component on the client, but the renderer used does not provide a client entrypoint to use to hydrate. + * + */ NoClientEntrypoint: { + title: 'No client entrypoint specified in renderer.', code: 3008, message: (componentName: string, clientDirective: string, rendererName: string) => - `${componentName} component has a \`client:${clientDirective}\` directive, but no client entrypoint was provided by ${rendererName}!`, + `\`${componentName}\` component has a \`client:${clientDirective}\` directive, but no client entrypoint was provided by \`${rendererName}\`.`, hint: 'See https://docs.astro.build/en/reference/integrations-reference/#addrenderer-option for more information on how to configure your renderer.', }, + /** + * @docs + * @see + * - [`client:only`](https://docs.astro.build/en/reference/directives-reference/#clientonly) + * @description + * + * `client:only` components are not ran on the server, as such Astro does not know (and cannot guess) which renderer to use and require a hint. Like such: + * + * ```astro + * + * ``` + */ NoClientOnlyHint: { + title: 'Missing hint on client:only directive.', code: 3009, message: (componentName: string) => - `Unable to render ${componentName}! When using the \`client:only\` hydration strategy, Astro needs a hint to use the correct renderer.`, + `Unable to render \`${componentName}\`. When using the \`client:only\` hydration strategy, Astro needs a hint to use the correct renderer.`, hint: (probableRenderers: string) => - `Did you mean to pass client:only="${probableRenderers}"? See https://docs.astro.build/en/reference/directives-reference/#clientonly for more information on client:only`, + `Did you mean to pass \`client:only="${probableRenderers}"\`? See https://docs.astro.build/en/reference/directives-reference/#clientonly for more information on client:only`, }, - InvalidStaticPathParam: { + /** + * @docs + * @see + * - [`getStaticPaths()`](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * - [`params`](https://docs.astro.build/en/reference/api-reference/#params) + * @description + * The `params` property in `getStaticPaths`'s return value (an array of objects) should also be an object. + * + * ```astro title="pages/blog/[id].astro" + * --- + * export async function getStaticPaths() { + * return [ + * { params: { slug: "blog" } }, + * { params: { slug: "about" } } + * ]; + *} + *--- + * ``` + */ + InvalidGetStaticPathParam: { + title: 'Invalid value returned by a `getStaticPaths` path.', code: 3010, message: (paramType) => - `Invalid params given to getStaticPaths path. Expected an object, got ${paramType}`, + `Invalid params given to \`getStaticPaths\` path. Expected an \`object\`, got \`${paramType}\``, hint: 'See https://docs.astro.build/en/reference/api-reference/#getstaticpaths for more information on getStaticPaths.', }, + /** + * @docs + * @see + * - [`getStaticPaths()`](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * - [`params`](https://docs.astro.build/en/reference/api-reference/#params) + * @description + * `getStaticPaths`'s return value must be an array of objects. + * + * ```ts title="pages/blog/[id].astro" + * export async function getStaticPaths() { + * return [ // <-- Array + * { params: { slug: "blog" } }, + * { params: { slug: "about" } } + * ]; + *} + * ``` + */ InvalidGetStaticPathsReturn: { + title: 'Invalid value returned by getStaticPaths.', code: 3011, message: (returnType) => - `Invalid type returned by getStaticPaths. Expected an array, got ${returnType}`, + `Invalid type returned by \`getStaticPaths\`. Expected an \`array\`, got \`${returnType}\``, hint: 'See https://docs.astro.build/en/reference/api-reference/#getstaticpaths for more information on getStaticPaths.', }, - GetStaticPathsDeprecatedRSS: { + /** + * @docs + * @see + * - [RSS Guide](https://docs.astro.build/en/guides/rss/) + * @description + * `getStaticPaths` no longer expose an helper for generating a RSS feed. We recommend migrating to the [@astrojs/rss](https://docs.astro.build/en/guides/rss/#setting-up-astrojsrss)integration instead. + */ + GetStaticPathsRemovedRSSHelper: { + title: 'getStaticPaths RSS helper is not available anymore.', code: 3012, message: - 'The RSS helper has been removed from getStaticPaths! Try the new @astrojs/rss package instead.', + 'The RSS helper has been removed from `getStaticPaths`. Try the new @astrojs/rss package instead.', hint: 'See https://docs.astro.build/en/guides/rss/ for more information.', }, + /** + * @docs + * @see + * - [`getStaticPaths()`](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * - [`params`](https://docs.astro.build/en/reference/api-reference/#params) + * @description + * Every route specified by `getStaticPaths` require a `params` property specifying the path parameters needed to match the route. + * + * For instance, the following code: + * ```astro title="pages/blog/[id].astro" + * --- + * export async function getStaticPaths() { + * return [ + * { params: { id: '1' } } + * ]; + * } + * --- + * ``` + * Will create the following route: `site.com/blog/1`. + */ GetStaticPathsExpectedParams: { + title: 'Missing params property on `getStaticPaths` route.', code: 3013, - message: 'Missing or empty required params property on getStaticPaths route', + message: 'Missing or empty required `params` property on `getStaticPaths` route.', hint: 'See https://docs.astro.build/en/reference/api-reference/#getstaticpaths for more information on getStaticPaths.', }, + /** + * @docs + * @see + * - [`getStaticPaths()`](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * - [`params`](https://docs.astro.build/en/reference/api-reference/#params) + * @description + * Since `params` are encoded into the URL, only certain types are supported as values. + * + * ```astro title="/route/[id].astro" + * --- + * export async function getStaticPaths() { + * return [ + * { params: { id: '1' } } // Works + * { params: { id: 2 } } // Works + * { params: { id: false } } // Does not work + * ]; + * } + * --- + * ``` + * + * In routes using [rest parameters](https://docs.astro.build/en/core-concepts/routing/#rest-parameters), `undefined` can be used to represent a path with no parameters passed in the URL: + * + * ```astro title="/route/[...id].astro" + * --- + * export async function getStaticPaths() { + * return [ + * { params: { id: 1 } } // /route/1 + * { params: { id: 2 } } // /route/2 + * { params: { id: undefined } } // /route/ + * ]; + * } + * --- + * ``` + */ GetStaticPathsInvalidRouteParam: { + title: 'Invalid value for `getStaticPaths` route parameter.', code: 3014, - message: (key: string, value: any) => - `Invalid getStaticPaths route parameter for \`${key}\`. Expected a string or number, received \`${typeof value}\` ("${value}")`, + message: (key: string, value: any, valueType: any) => + `Invalid getStaticPaths route parameter for \`${key}\`. Expected undefined, a string or a number, received \`${valueType}\` (\`${value}\`)`, hint: 'See https://docs.astro.build/en/reference/api-reference/#getstaticpaths for more information on getStaticPaths.', }, + /** + * @docs + * @see + * - [Dynamic Routes](https://docs.astro.build/en/core-concepts/routing/#dynamic-routes) + * - [`getStaticPaths()`](https://docs.astro.build/en/reference/api-reference/#getstaticpaths) + * - [Server-side Rendering](https://docs.astro.build/en/guides/server-side-rendering/) + * @description + * In [Static Mode](https://docs.astro.build/en/core-concepts/routing/#static-ssg-mode), all routes must be determined at build time. As such, dynamic routes must `export` a `getStaticPaths` function returning the different paths to generate. + */ GetStaticPathsRequired: { + title: '`getStaticPaths()` function required for dynamic routes.', code: 3015, message: - 'getStaticPaths() function is required for dynamic routes. Make sure that you `export` a `getStaticPaths` function from your dynamic route.', + '`getStaticPaths()` function is required for dynamic routes. Make sure that you `export` a `getStaticPaths` function from your dynamic route.', hint: `See https://docs.astro.build/en/core-concepts/routing/#dynamic-routes for more information on dynamic routes. Alternatively, set \`output: "server"\` in your Astro config file to switch to a non-static server build. See https://docs.astro.build/en/guides/server-side-rendering/ for more information on non-static rendering.`, }, + /** + * @docs + * @see + * - [Named slots](https://docs.astro.build/en/core-concepts/astro-components/#named-slots) + * @description + * Certain words cannot be used for slot names due to being already used internally. + */ ReservedSlotName: { + title: 'Invalid slot name.', code: 3016, message: (slotName: string) => - `Unable to create a slot named "${slotName}". ${slotName}" is a reserved slot name! Please update the name of this slot.`, + `Unable to create a slot named \`${slotName}\`. \`${slotName}\` is a reserved slot name. Please update the name of this slot.`, }, + /** + * @docs + * @see + * - [Server-side Rendering](https://docs.astro.build/en/guides/server-side-rendering/) + * - [Adding an Adapter](https://docs.astro.build/en/guides/server-side-rendering/#adding-an-adapter) + * @description + * To use server-side rendering, an adapter needs to be installed so Astro knows how to generate the proper output for your targetted deployment platform. + */ NoAdapterInstalled: { + title: 'Cannot use Server-side Rendering without an adapter.', code: 3017, message: `Cannot use \`output: 'server'\` without an adapter. Please install and configure the appropriate server adapter for your final deployment.`, hint: 'See https://docs.astro.build/en/guides/server-side-rendering/ for more information.', }, + /** + * @docs + * @description + * No import statement was found for one of the components. If there is an import statement, make sure you are using the same identifier in both the imports and the component usage. + */ NoMatchingImport: { + title: 'No import found for component.', code: 3018, message: (componentName: string) => - `Could not render ${componentName}. No matching import has been found for ${componentName}.`, + `Could not render \`${componentName}\`. No matching import has been found for \`${componentName}\`.`, hint: 'Please make sure the component is properly imported.', }, - // CSS Errors - 4xxx - UnknownCSSError: { + // Vite Errors - 4xxx + UnknownViteError: { + title: 'Unknown Vite Error.', code: 4000, }, - CSSSyntaxError: { - code: 4001, - }, - // Vite Errors - 5xxx - UnknownViteError: { - code: 5000, - }, + /** + * @docs + * @see + * - [Type Imports](https://docs.astro.build/en/guides/typescript/#type-imports) + * @description + * Astro could not import the requested file. Oftentimes, this is caused by the import path being wrong (either because the file does not exist, or there is a typo in the path) + * + * This message can also appear when a type is imported without specifying that it is a [type import](https://docs.astro.build/en/guides/typescript/#type-imports). + */ FailedToLoadModuleSSR: { - code: 5001, - message: (importName: string) => `Could not import "${importName}".`, + title: 'Could not import file.', + code: 4001, + message: (importName: string) => `Could not import \`${importName}\`.`, hint: 'This is often caused by a typo in the import path. Please make sure the file exists.', }, + /** + * @docs + * @see + * - [Glob Patterns](https://docs.astro.build/en/guides/imports/#glob-patterns) + * @description + * Astro encountered an invalid glob pattern. This is often caused by the glob pattern not being a valid file path. + */ InvalidGlob: { - code: 5002, + title: 'Invalid glob pattern.', + code: 4002, message: (globPattern: string) => - `Invalid glob pattern: "${globPattern}". Glob patterns must start with './', '../' or '/'.`, + `Invalid glob pattern: \`${globPattern}\`. Glob patterns must start with './', '../' or '/'.`, hint: 'See https://docs.astro.build/en/guides/imports/#glob-patterns for more information on supported glob patterns.', }, + /** + * @docs + * @kind heading + * @name CSS Errors + */ + // CSS Errors - 5xxx + UnknownCSSError: { + title: 'Unknown CSS Error.', + code: 5000, + }, + /** + * @docs + * @message + * **Example error messages:**
+ * CSSSyntaxError: Missed semicolon
+ * CSSSyntaxError: Unclosed string
+ * @description + * Astro encountered an error while parsing your CSS, due to a syntax error. This is often caused by a missing semicolon + */ + CSSSyntaxError: { + title: 'CSS Syntax Error.', + code: 5001, + }, + /** + * @docs + * @kind heading + * @name Markdown Errors + */ // Markdown Errors - 6xxx UnknownMarkdownError: { + title: 'Unknown Markdown Error.', code: 6000, }, + /** + * @docs + * @message + * **Example error messages:**
+ * can not read an implicit mapping pair; a colon is missed
+ * unexpected end of the stream within a double quoted scalar
+ * can not read a block mapping entry; a multiline key may not be an implicit key + * @description + * Astro encountered an error while parsing the frontmatter of your Markdown file. + * This is often caused by a mistake in the syntax, such as a missing colon or a missing end quote. + */ MarkdownFrontmatterParseError: { + title: 'Failed to parse Markdown frontmatter.', code: 6001, }, // Config Errors - 7xxx UnknownConfigError: { + title: 'Unknown configuration error.', code: 7000, }, + /** + * @docs + * @see + * - [--config](https://docs.astro.build/en/reference/cli-reference/#--config-path) + * @description + * The specified configuration file using `--config` could not be found. Make sure that it exists or that the path is correct + */ ConfigNotFound: { + title: 'Specified configuration file not found.', code: 7001, message: (configFile: string) => - `Unable to resolve --config "${configFile}"! Does the file exist?`, + `Unable to resolve \`--config "${configFile}"\`. Does the file exist?`, }, + /** + * @docs + * @see + * - [Configuration reference](https://docs.astro.build/en/reference/configuration-reference/) + * - [Migration guide](https://docs.astro.build/en/migrate/) + * @description + * Astro detected a legacy configuration option in your configuration file. + */ ConfigLegacyKey: { + title: 'Legacy configuration detected.', code: 7002, - message: (legacyConfigKey: string) => `Legacy configuration detected: "${legacyConfigKey}".`, - hint: 'Please update your configuration to the new format!\nSee https://astro.build/config for more information.', + message: (legacyConfigKey: string) => `Legacy configuration detected: \`${legacyConfigKey}\`.`, + hint: 'Please update your configuration to the new format.\nSee https://astro.build/config for more information.', }, // Generic catch-all UnknownError: { + title: 'Unknown Error.', code: 99999, }, } as const); diff --git a/packages/astro/src/core/errors/errors.ts b/packages/astro/src/core/errors/errors.ts index 1ec1b9c27..89bf3be6b 100644 --- a/packages/astro/src/core/errors/errors.ts +++ b/packages/astro/src/core/errors/errors.ts @@ -5,6 +5,7 @@ import { getErrorDataByCode } from './utils.js'; interface ErrorProperties { code: AstroErrorCodes | DiagnosticCode; + title?: string; name?: string; message?: string; location?: ErrorLocation; @@ -28,8 +29,12 @@ type ErrorTypes = | 'AggregateError'; export class AstroError extends Error { - public code: AstroErrorCodes | DiagnosticCode; + // NOTE: If this property is named `code`, Rollup will use it to fill the `pluginCode` property downstream + // This cause issues since we expect `pluginCode` to be a string containing code + // @see https://github.com/rollup/rollup/blob/9a741639f69f204ded8ea404675f725b8d56adca/src/utils/error.ts#L725 + public errorCode: AstroErrorCodes | DiagnosticCode; public loc: ErrorLocation | undefined; + public title: string | undefined; public hint: string | undefined; public frame: string | undefined; @@ -38,15 +43,16 @@ export class AstroError extends Error { constructor(props: ErrorProperties, ...params: any) { super(...params); - const { code, name, message, stack, location, hint, frame } = props; + const { code, name, title, message, stack, location, hint, frame } = props; - this.code = code; + this.errorCode = code; if (name) { this.name = name; } else { // If we don't have a name, let's generate one from the code - this.name = getErrorDataByCode(this.code)?.name ?? 'UnknownError'; + this.name = getErrorDataByCode(this.errorCode)?.name ?? 'UnknownError'; } + this.title = title; if (message) this.message = message; // Only set this if we actually have a stack passed, otherwise uses Error's this.stack = stack ? stack : this.stack; @@ -56,9 +62,9 @@ export class AstroError extends Error { } public setErrorCode(errorCode: AstroErrorCodes) { - this.code = errorCode; + this.errorCode = errorCode; - this.name = getErrorDataByCode(this.code)?.name ?? 'UnknownError'; + this.name = getErrorDataByCode(this.errorCode)?.name ?? 'UnknownError'; } public setLocation(location: ErrorLocation): void { diff --git a/packages/astro/src/core/render/result.ts b/packages/astro/src/core/render/result.ts index d0ad26b31..90732c0fc 100644 --- a/packages/astro/src/core/render/result.ts +++ b/packages/astro/src/core/render/result.ts @@ -24,7 +24,7 @@ function onlyAvailableInSSR(name: 'Astro.redirect') { return function _onlyAvailableInSSR() { switch (name) { case 'Astro.redirect': - throw new AstroError(AstroErrorData.StaticRedirectNotAllowed); + throw new AstroError(AstroErrorData.StaticRedirectNotAvailable); } }; } @@ -177,10 +177,8 @@ export function createResult(args: CreateResultArgs): SSRResult { if (!(clientAddressSymbol in request)) { if (args.adapterName) { throw new AstroError({ - ...AstroErrorData.SSRClientAddressNotAvailableInAdapter, - message: AstroErrorData.SSRClientAddressNotAvailableInAdapter.message( - args.adapterName - ), + ...AstroErrorData.ClientAddressNotAvailable, + message: AstroErrorData.ClientAddressNotAvailable.message(args.adapterName), }); } else { throw new AstroError(AstroErrorData.StaticClientAddressNotAvailable); diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts index 35cbaab3e..a49e98c71 100644 --- a/packages/astro/src/core/render/route-cache.ts +++ b/packages/astro/src/core/render/route-cache.ts @@ -45,7 +45,7 @@ export async function callGetStaticPaths({ staticPaths = await mod.getStaticPaths({ paginate: generatePaginateFunction(route), rss() { - throw new AstroError(AstroErrorData.GetStaticPathsDeprecatedRSS); + throw new AstroError(AstroErrorData.GetStaticPathsRemovedRSSHelper); }, }); diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts index 1dcd1bbfe..9e13764b0 100644 --- a/packages/astro/src/core/routing/validation.ts +++ b/packages/astro/src/core/routing/validation.ts @@ -10,7 +10,7 @@ export function validateGetStaticPathsParameter([key, value]: [string, any], rou if (!VALID_PARAM_TYPES.includes(typeof value)) { throw new AstroError({ ...AstroErrorData.GetStaticPathsInvalidRouteParam, - message: AstroErrorData.GetStaticPathsInvalidRouteParam.message(key, value), + message: AstroErrorData.GetStaticPathsInvalidRouteParam.message(key, value, typeof value), location: { file: route, }, @@ -74,8 +74,8 @@ export function validateGetStaticPathsResult( if (typeof pathObject.params !== 'object') { throw new AstroError({ - ...AstroErrorData.InvalidStaticPathParam, - message: AstroErrorData.InvalidStaticPathParam.message(typeof pathObject.params), + ...AstroErrorData.InvalidGetStaticPathParam, + message: AstroErrorData.InvalidGetStaticPathParam.message(typeof pathObject.params), location: { file: route.component, }, diff --git a/packages/astro/src/runtime/server/hydration.ts b/packages/astro/src/runtime/server/hydration.ts index 07a42445b..4729708e7 100644 --- a/packages/astro/src/runtime/server/hydration.ts +++ b/packages/astro/src/runtime/server/hydration.ts @@ -87,10 +87,7 @@ export function extractDirectives( extracted.hydration.directive === 'media' && typeof extracted.hydration.value !== 'string' ) { - throw new AstroError({ - ...AstroErrorData.MissingMediaQueryDirective, - message: AstroErrorData.MissingMediaQueryDirective.message(displayName), - }); + throw new AstroError(AstroErrorData.MissingMediaQueryDirective); } break;