Repository: typst/typst
File: crates/typst-cli/src/compile.rs (Rust)

Code before:
    command: &CompileCommand,
    watching: bool,
) -> StrResult<bool> {
    let main_result = world.source(world.main());
    let Err(file_error) = &main_result else {
        return Ok(true);
    };

    set_failed();
    if watching {
        Status::Error.print(command).unwrap();
    }

    let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
    let mut main_result = main_result.at(Span::detached());

    // 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) = &command.common.input {
            let extension = path.extension();
            let exists_typ_file = path.with_extension("typ").exists();

            let hint_given = match extension {
                Some(extension) if extension != "typ" => {
                    main_result = main_result.hint(eco_format!(
                        "a file with the `.{}` extension is not usually a Typst file",
                        extension.to_string_lossy()
                    ));

                    true
                }

                Some(_) => {
                    // No hints if the file is already a .typ file.
                    // The file is indeed just invalid.
                    false
                }

                None => {
                    main_result = main_result
                        .hint("a file without an extension is not usually a Typst file");

                    true
                }
            };

            if hint_given && exists_typ_file {
                main_result = main_result

Review comment (suggestion):
I think we could do without the `hint_given` boolean by checking for a `Some("typ")` extension first and then only proceeding if it's not that (since in that case no hints will be given). Then we can give the generic hint and, finally, we'd add the two hints for the `Some(_)` and `None` cases. The control flow would be a bit simpler that way.

Code after:
}

/// 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<SourceDiagnostic> {
    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,