0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-06 22:10:10 -05:00

Improve “file not found” error display (#288)

This commit is contained in:
Drew Powers 2021-06-02 09:53:24 -06:00 committed by GitHub
parent e3df5e80e1
commit d2330a5825
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 23 deletions

View file

@ -0,0 +1,6 @@
---
'astro': patch
'astro-parser': patch
---
Improve error display for missing local files

View file

@ -1,5 +1,5 @@
import type { SourceMap } from 'magic-string'; import type { SourceMap } from 'magic-string';
export type { CompileError } from './utils/error'; export { CompileError } from './utils/error';
export interface BaseNode { export interface BaseNode {
start: number; start: number;

View file

@ -5,14 +5,23 @@ import get_code_frame from './get_code_frame.js';
export class CompileError extends Error { export class CompileError extends Error {
code: string; code: string;
start: { line: number; column: number };
end: { line: number; column: number }; end: { line: number; column: number };
pos: number;
filename: string; filename: string;
frame: 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() { 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, message: string,
props: { props: {
name: string; name: string;
code: string;
source: string; source: string;
filename: string; filename: string;
start: number; start: number;
end?: number; end?: number;
} }
): never { ): never {
const err = new CompileError(message); const err = new CompileError({ message, start: props.start, end: props.end, filename: props.filename });
err.name = props.name; 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; throw err;
} }

View file

@ -66,6 +66,7 @@ export default async function dev(astroConfig: AstroConfig) {
break; break;
} }
case 500: { case 500: {
res.setHeader('Content-Type', 'text/html;charset=utf-8');
switch (result.type) { switch (result.type) {
case 'parse-error': { case 'parse-error': {
const err = result.error; const err = result.error;

View file

@ -131,7 +131,7 @@ export function parseError(opts: LogOptions, err: CompileError) {
'parse-error', '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}`))} ${bold(red(`𝘅 ${err.message}`))}

View file

@ -1,16 +1,16 @@
import 'source-map-support/register.js'; 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 { LogOptions } from './logger';
import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro'; import type { AstroConfig, CollectionResult, CollectionRSS, CreateCollection, Params, RuntimeMode } from './@types/astro';
import resolve from 'resolve'; import resolve from 'resolve';
import { existsSync } from 'fs'; import { existsSync, promises as fs } from 'fs';
import { fileURLToPath, pathToFileURL } from 'url'; import { fileURLToPath, pathToFileURL } from 'url';
import { posix as path } from 'path'; import { posix as path } from 'path';
import { performance } from 'perf_hooks'; 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 { 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 { debug, info } from './logger.js';
import { searchForPage } from './search.js'; import { searchForPage } from './search.js';
import snowpackExternals from './external.js'; import snowpackExternals from './external.js';
@ -40,7 +40,7 @@ type LoadResultSuccess = {
}; };
type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo }; type LoadResultNotFound = { statusCode: 404; error: Error; collectionInfo?: CollectionInfo };
type LoadResultRedirect = { statusCode: 301 | 302; location: string; 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 }; export type LoadResult = (LoadResultSuccess | LoadResultNotFound | LoadResultRedirect | LoadResultError) & { collectionInfo?: CollectionInfo };
@ -242,6 +242,40 @@ async function load(config: RuntimeConfig, rawPathname: string | undefined): Pro
error: err, 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 dont 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 { return {
statusCode: 500, statusCode: 500,
type: 'unknown', type: 'unknown',