Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion js/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ Deno.test({
);
},
Error,
"The module's source code could not be parsed",
"at file:///a/test.js:",
);
},
});
Expand Down
126 changes: 122 additions & 4 deletions src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,14 +562,132 @@ impl std::error::Error for ModuleErrorKind {
}
}

/// Cleans up SWC token descriptions in error messages.
/// e.g. `'string literal ("./foo.js", "./foo.js")'` -> `"./foo.js"`
fn clean_parse_message(message: &str) -> String {
// Replace `'string literal ("{value}", "{value}")'` with `"{value}"`
let mut result = message.to_string();
while let Some(start) = result.find("'string literal (\"") {
let after = &result[start + 18..]; // skip `'string literal ("`
if let Some(quote_end) = after.find('"') {
let value = &after[..quote_end];
// Find the closing `)'`
if let Some(end) = result[start..].find(")'") {
let full_end = start + end + 2;
result =
format!("{}\"{}\"{}", &result[..start], value, &result[full_end..]);
continue;
}
}
break;
}
result
}

/// Reformats a parse diagnostic message into a nicer format with
/// line numbers and carets, with the location shown after the snippet.
///
/// Input format (from ParseDiagnostic::Display):
/// `{message} at {specifier}:{line}:{col}\n\n {source_line}\n {underline}`
///
/// Output format:
/// `{message}\n |\nN | {source_line}\n | {carets}\n at {location}`
fn format_parse_diagnostic(
f: &mut fmt::Formatter<'_>,
diagnostic: &str,
) -> fmt::Result {
// Try to parse the diagnostic format: "{message} at {location}\n\n {snippet}"
if let Some(at_pos) = diagnostic.find(" at ") {
let raw_message = &diagnostic[..at_pos];
let message = clean_parse_message(raw_message);

// Find the end of the location line (first newline after " at ")
let after_at = &diagnostic[at_pos + 4..];
let (location, rest) = if let Some(newline_pos) = after_at.find('\n') {
(&after_at[..newline_pos], &after_at[newline_pos..])
} else {
(after_at, "")
};

// Parse location to extract line number
// Location format: {specifier}:{line}:{col}
let line_num = location
.rsplit(':')
.nth(1)
.and_then(|s| s.parse::<usize>().ok());

write!(f, "{message}")?;

if !rest.is_empty() {
// The rest contains the source snippet, indented with " "
// Reformat with line numbers
let lines: Vec<&str> = rest.lines().collect();

// Find source line and underline (skip empty lines)
let mut source_line = None;
let mut underline = None;
for line in &lines {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if source_line.is_none() {
source_line = Some(trimmed);
} else if underline.is_none() {
underline = Some(trimmed);
}
}

if let (Some(src), Some(ul)) = (source_line, underline) {
writeln!(f)?;
if let Some(num) = line_num {
let num_str = num.to_string();
let padding = " ".repeat(num_str.len());
writeln!(f, "{padding} |")?;
writeln!(f, "{num_str} | {src}")?;
// Calculate indent: align the underline under the source
let src_start = source_line
.and_then(|s| {
lines
.iter()
.find(|l| l.trim() == s)
.map(|l| l.len() - l.trim_start().len())
})
.unwrap_or(2);
let ul_start = underline
.and_then(|u| {
lines
.iter()
.find(|l| l.trim() == u)
.map(|l| l.len() - l.trim_start().len())
})
.unwrap_or(2);
let offset = ul_start.saturating_sub(src_start);
writeln!(f, "{padding} | {}{}", " ".repeat(offset), ul)?;
} else {
writeln!(f)?;
writeln!(f, " {src}")?;
writeln!(f, " {ul}")?;
}
}
}

write!(f, " at {location}")?;

Ok(())
} else {
// Fallback: just write the diagnostic as-is
write!(f, "{}", diagnostic)
}
}

impl fmt::Display for ModuleErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Load { err, .. } => err.fmt(f),
Self::Parse { diagnostic, .. } => write!(
f,
"The module's source code could not be parsed: {diagnostic}"
),
Self::Parse { diagnostic, .. } => {
format_parse_diagnostic(f, &diagnostic.to_string())
}
Self::WasmParse { specifier, err, .. } => write!(
f,
"The Wasm module could not be parsed: {err}\n Specifier: {specifier}"
Expand Down
12 changes: 6 additions & 6 deletions tests/specs/ecosystem/mrii/rocket_io/0_1_3.test
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ error: Error: [ERR_PACKAGE_PATH_NOT_EXPORTED] Package subpath './build/esm/socke
at ext:deno_cli_tsc/97_ts_host.js:749:49
at spanned (ext:deno_cli_tsc/97_ts_host.js:16:12)
at Object.host.<computed> [as resolveModuleNameLiterals] (ext:deno_cli_tsc/97_ts_host.js:749:14)
at resolveModuleNamesWorker (ext:deno_cli_tsc/00_typescript.js:127677:20)
at resolveNamesReusingOldState (ext:deno_cli_tsc/00_typescript.js:127813:14)
at resolveModuleNamesReusingOldState (ext:deno_cli_tsc/00_typescript.js:127769:12)
at processImportedModules (ext:deno_cli_tsc/00_typescript.js:129227:118)
at findSourceFileWorker (ext:deno_cli_tsc/00_typescript.js:129034:7)
at findSourceFile (ext:deno_cli_tsc/00_typescript.js:128886:20)
at resolveModuleNamesWorker (ext:deno_cli_tsc/00_typescript.js:127688:20)
at resolveNamesReusingOldState (ext:deno_cli_tsc/00_typescript.js:127824:14)
at resolveModuleNamesReusingOldState (ext:deno_cli_tsc/00_typescript.js:127780:12)
at processImportedModules (ext:deno_cli_tsc/00_typescript.js:129238:118)
at findSourceFileWorker (ext:deno_cli_tsc/00_typescript.js:129045:7)
at findSourceFile (ext:deno_cli_tsc/00_typescript.js:128897:20)

2 changes: 1 addition & 1 deletion tests/specs/graph/cjs/file_export.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class Test {}
"modules": [
{
"specifier": "file:///file.cjs",
"error": "The module's source code could not be parsed: 'import', and 'export' cannot be used outside of module code at file:///file.cjs:1:1\n\n export class Test {}\n ~~~~~~"
"error": "'import', and 'export' cannot be used outside of module code\n |\n1 | export class Test {}\n | ~~~~~~\n at file:///file.cjs:1:1"
},
{
"kind": "esm",
Expand Down
Loading