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:
parent
e3df5e80e1
commit
d2330a5825
6 changed files with 61 additions and 23 deletions
6
.changeset/thirty-fans-know.md
Normal file
6
.changeset/thirty-fans-know.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
'astro-parser': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve error display for missing local files
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}`))}
|
||||||
|
|
||||||
|
|
|
@ -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 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 {
|
return {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
type: 'unknown',
|
type: 'unknown',
|
||||||
|
|
Loading…
Reference in a new issue