From d360e753bccf996819392539aaaa031f458559e9 Mon Sep 17 00:00:00 2001 From: PepinhoJp Date: Tue, 4 Jun 2024 12:23:38 -0300 Subject: [PATCH] Improving error message for invalid file types (#4216) --- crates/typst-cli/src/compile.rs | 56 ++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index e145a8205..584ccc858 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -5,10 +5,10 @@ use std::path::{Path, PathBuf}; use chrono::{Datelike, Timelike}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term; -use ecow::{eco_format, EcoString}; +use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use parking_lot::RwLock; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use typst::diag::{bail, At, Severity, SourceDiagnostic, StrResult}; +use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult}; use typst::eval::Tracer; use typst::foundations::{Datetime, Smart}; use typst::layout::{Frame, PageRanges}; @@ -97,8 +97,10 @@ pub fn compile_once( Status::Compiling.print(command).unwrap(); } - // Check if main file can be read and opened. - if let Err(errors) = world.source(world.main()).at(Span::detached()) { + if let Err(errors) = world + .source(world.main()) + .map_err(|err| hint_invalid_main_file(err, &command.common.input)) + { set_failed(); if watching { Status::Error.print(command).unwrap(); @@ -483,6 +485,52 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> { Ok(()) } +/// Adds useful hints when the main source file couldn't be read +/// and returns the final diagnostic. +fn hint_invalid_main_file( + file_error: FileError, + input: &Input, +) -> EcoVec { + let is_utf8_error = matches!(file_error, FileError::InvalidUtf8); + let mut diagnostic = + SourceDiagnostic::error(Span::detached(), EcoString::from(file_error)); + + // Attempt to provide helpful hints for UTF-8 errors. + // Perhaps the user mistyped the filename. + // For example, they could have written "file.pdf" instead of + // "file.typ". + if is_utf8_error { + if let Input::Path(path) = input { + let extension = path.extension(); + if extension.is_some_and(|extension| extension == "typ") { + // No hints if the file is already a .typ file. + // The file is indeed just invalid. + return eco_vec![diagnostic]; + } + + match extension { + Some(extension) => { + diagnostic.hint(eco_format!( + "a file with the `.{}` extension is not usually a Typst file", + extension.to_string_lossy() + )); + } + + None => { + diagnostic + .hint("a file without an extension is not usually a Typst file"); + } + }; + + if path.with_extension("typ").exists() { + diagnostic.hint("check if you meant to use the `.typ` extension instead"); + } + } + } + + eco_vec![diagnostic] +} + /// Print diagnostic messages to the terminal. pub fn print_diagnostics( world: &SystemWorld,