diff options
author | 2021-06-02 09:53:24 -0600 | |
---|---|---|
committer | 2021-06-02 09:53:24 -0600 | |
commit | d2330a58256c212288bbba04ca41f1d65d37e711 (patch) | |
tree | ab0cd107b831f2ad50c35bec8b51e2991522780e | |
parent | e3df5e80e16b6dec75ec7f44a1ea4c0692606fdb (diff) | |
download | astro-d2330a58256c212288bbba04ca41f1d65d37e711.tar.gz astro-d2330a58256c212288bbba04ca41f1d65d37e711.tar.zst astro-d2330a58256c212288bbba04ca41f1d65d37e711.zip |
Improve “file not found” error display (#288)
-rw-r--r-- | .changeset/thirty-fans-know.md | 6 | ||||
-rw-r--r-- | packages/astro-parser/src/interfaces.ts | 2 | ||||
-rw-r--r-- | packages/astro-parser/src/utils/error.ts | 29 | ||||
-rw-r--r-- | packages/astro/src/dev.ts | 1 | ||||
-rw-r--r-- | packages/astro/src/logger.ts | 2 | ||||
-rw-r--r-- | packages/astro/src/runtime.ts | 44 |
6 files changed, 61 insertions, 23 deletions
diff --git a/.changeset/thirty-fans-know.md b/.changeset/thirty-fans-know.md new file mode 100644 index 000000000..ff8010931 --- /dev/null +++ b/.changeset/thirty-fans-know.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'astro-parser': patch +--- + +Improve error display for missing local files diff --git a/packages/astro-parser/src/interfaces.ts b/packages/astro-parser/src/interfaces.ts index 40eeb04f5..f3fa5da9d 100644 --- a/packages/astro-parser/src/interfaces.ts +++ b/packages/astro-parser/src/interfaces.ts @@ -1,5 +1,5 @@ import type { SourceMap } from 'magic-string'; -export type { CompileError } from './utils/error'; +export { CompileError } from './utils/error'; export interface BaseNode { start: number; diff --git a/packages/astro-parser/src/utils/error.ts b/packages/astro-parser/src/utils/error.ts index 8ebb5b093..00eed866f 100644 --- a/packages/astro-parser/src/utils/error.ts +++ b/packages/astro-parser/src/utils/error.ts @@ -5,14 +5,23 @@ import get_code_frame from './get_code_frame.js'; export class CompileError extends Error { code: string; - start: { line: number; column: number }; end: { line: number; column: number }; - pos: number; filename: string; frame: string; + start: { line: number; column: number }; + + constructor({ code, filename, start, end, message }: { code: string; filename: string; start: number; message: string; end?: number }) { + super(message); + + this.start = locate(code, start, { offsetLine: 1 }); + this.end = locate(code, end || start, { offsetLine: 1 }); + this.filename = filename; + this.message = message; + this.frame = get_code_frame(code, this.start.line - 1, this.start.column); + } toString() { - return `${this.message} (${this.start.line}:${this.start.column})\n${this.frame}`; + return `${this.filename}:${this.start.line}:${this.start.column}\n\t${this.message}\n${this.frame}`; } } @@ -21,26 +30,14 @@ export default function error( message: string, props: { name: string; - code: string; source: string; filename: string; start: number; end?: number; } ): never { - const err = new CompileError(message); + const err = new CompileError({ message, start: props.start, end: props.end, filename: props.filename }); err.name = props.name; - const start = locate(props.source, props.start, { offsetLine: 1 }); - const end = locate(props.source, props.end || props.start, { offsetLine: 1 }); - - err.code = props.code; - err.start = start; - err.end = end; - err.pos = props.start; - err.filename = props.filename; - - err.frame = get_code_frame(props.source, start.line - 1, start.column); - throw err; } diff --git a/packages/astro/src/dev.ts b/packages/astro/src/dev.ts index 7f61492d7..5d1f20cff 100644 --- a/packages/astro/src/dev.ts +++ b/packages/astro/src/dev.ts @@ -66,6 +66,7 @@ export default async function dev(astroConfig: AstroConfig) { break; } case 500: { + res.setHeader('Content-Type', 'text/html;charset=utf-8'); switch (result.type) { case 'parse-error': { const err = result.error; diff --git a/packages/astro/src/logger.ts b/packages/astro/src/logger.ts index 282e8506e..84572e86e 100644 --- a/packages/astro/src/logger.ts +++ b/packages/astro/src/logger.ts @@ -131,7 +131,7 @@ export function parseError(opts: LogOptions, err: CompileError) { 'parse-error', ` - ${underline(bold(grey(`${err.filename}:${err.start.line}:${err.start.column}`)))} + ${underline(bold(grey(`${err.filename || ''}:${err.start.line}:${err.start.column}`)))} ${bold(red(`𝘅 ${err.message}`))} diff --git a/packages/astro/src/runtime.ts b/packages/astro/src/runtime.ts index 2beb9d7f3..a73df33d6 100644 --- a/packages/astro/src/runtime.ts +++ b/packages/astro/src/runtime.ts @@ -1,16 +1,16 @@ import 'source-map-support/register.js'; -import type { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig } from 'snowpack'; -import type { CompileError } from 'astro-parser'; import type { LogOptions } from './logger'; import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro'; import resolve from 'resolve'; -import { existsSync } from 'fs'; +import { existsSync, promises as fs } from 'fs'; import { fileURLToPath, pathToFileURL } from 'url'; import { posix as path } from 'path'; import { performance } from 'perf_hooks'; +import { SnowpackDevServer, ServerRuntime as SnowpackServerRuntime, SnowpackConfig, NotFoundError } from 'snowpack'; +import { CompileError } from 'astro-parser'; import { loadConfiguration, logger as snowpackLogger, startServer as startSnowpackServer } from 'snowpack'; -import { canonicalURL, stopTimer } from './build/util.js'; +import { canonicalURL, getSrcPath, stopTimer } from './build/util.js'; import { debug, info } from './logger.js'; import { searchForPage } from './search.js'; import snowpackExternals from './external.js'; @@ -40,7 +40,7 @@ type LoadResultSuccess = { }; type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo }; type LoadResultRedirect = { statusCode: 301 | 302; location: string; collectionInfo?: CollectionInfo }; -type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'unknown'; error: Error }); +type LoadResultError = { statusCode: 500 } & ({ type: 'parse-error'; error: CompileError } | { type: 'not-found'; error: CompileError } | { type: 'unknown'; error: Error }); export type LoadResult = (LoadResultSuccess | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo }; @@ -242,6 +242,40 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro error: err, }; } + + if (err instanceof NotFoundError && rawPathname) { + const fileMatch = err.toString().match(/\(([^\)]+)\)/); + const missingFile: string | undefined = (fileMatch && fileMatch[1].replace(/^\/_astro/, '').replace(/\.proxy\.js$/, '')) || undefined; + const distPath = path.extname(rawPathname) ? rawPathname : rawPathname.replace(/\/?$/, '/index.html'); + const srcFile = getSrcPath(distPath, { astroConfig: config.astroConfig }); + const code = existsSync(srcFile) ? await fs.readFile(srcFile, 'utf8') : ''; + + // try and find the import statement within the module. this is a bit hacky, as we don’t know the line, but + // given that we know this is for sure a “not found” error, and we know what file is erring, + // we can make some safe assumptions about how to locate the line in question + let start = 0; + const segments = missingFile ? missingFile.split('/').filter((segment) => !!segment) : []; + while (segments.length) { + const importMatch = code.indexOf(segments.join('/')); + if (importMatch >= 0) { + start = importMatch; + break; + } + segments.shift(); + } + + return { + statusCode: 500, + type: 'not-found', + error: new CompileError({ + code, + filename: srcFile.pathname, + start, + message: `Could not find${missingFile ? ` "${missingFile}"` : ' file'}`, + }), + }; + } + return { statusCode: 500, type: 'unknown', |