harn-cli 0.7.15

CLI for the Harn programming language — run, test, REPL, format, and lint
use std::path::Path;

use harn_lint::LintSeverity;
use harn_parser::{DiagnosticSeverity, TypeChecker};

use crate::package::{CheckConfig, PreflightSeverity};
use crate::parse_source_file;

use super::outcome::{print_lint_diagnostics, CommandOutcome};
use super::preflight::{collect_preflight_diagnostics, is_preflight_allowed};

pub(crate) fn check_file_inner(
    path: &Path,
    config: &CheckConfig,
    externally_imported_names: &std::collections::HashSet<String>,
    module_graph: &harn_modules::ModuleGraph,
) -> CommandOutcome {
    let path_str = path.to_string_lossy().into_owned();
    let (source, program) = parse_source_file(&path_str);

    let mut has_error = false;
    let mut has_warning = false;
    let mut diagnostic_count = 0;

    let mut checker = TypeChecker::with_strict_types(config.strict_types);
    if let Some(imported) = module_graph.imported_names_for_file(path) {
        checker = checker.with_imported_names(imported);
    }
    let type_diagnostics = checker.check(&program);
    for diag in &type_diagnostics {
        let severity = match diag.severity {
            DiagnosticSeverity::Error => {
                has_error = true;
                "error"
            }
            DiagnosticSeverity::Warning => {
                has_warning = true;
                "warning"
            }
        };
        diagnostic_count += 1;
        if let Some(span) = &diag.span {
            let rendered = harn_parser::diagnostic::render_diagnostic(
                &source,
                &path_str,
                span,
                severity,
                &diag.message,
                None,
                diag.help.as_deref(),
            );
            eprint!("{rendered}");
        } else {
            eprintln!("{severity}: {}", diag.message);
        }
    }

    let lint_diagnostics = harn_lint::lint_with_module_graph(
        &program,
        &config.disable_rules,
        Some(&source),
        externally_imported_names,
        module_graph,
        path,
        &harn_lint::LintOptions {
            file_path: Some(path),
            require_file_header: false,
            complexity_threshold: None,
        },
    );
    diagnostic_count += lint_diagnostics.len();
    if lint_diagnostics
        .iter()
        .any(|d| d.severity == LintSeverity::Warning)
    {
        has_warning = true;
    }
    if print_lint_diagnostics(&path_str, &source, &lint_diagnostics) {
        has_error = true;
    }

    let preflight_diagnostics = collect_preflight_diagnostics(path, &source, &program, config);
    let preflight_severity = PreflightSeverity::from_opt(config.preflight_severity.as_deref());
    if preflight_severity != PreflightSeverity::Off {
        let (severity_label, category) = match preflight_severity {
            PreflightSeverity::Warning => ("warning", "preflight"),
            _ => ("error", "preflight"),
        };
        for diag in &preflight_diagnostics {
            if is_preflight_allowed(&diag.tags, &config.preflight_allow) {
                continue;
            }
            match preflight_severity {
                PreflightSeverity::Warning => has_warning = true,
                PreflightSeverity::Error => has_error = true,
                PreflightSeverity::Off => unreachable!(),
            }
            diagnostic_count += 1;
            let rendered = harn_parser::diagnostic::render_diagnostic(
                &diag.source,
                &diag.path,
                &diag.span,
                severity_label,
                &diag.message,
                Some(category),
                diag.help.as_deref(),
            );
            eprint!("{rendered}");
        }
    }

    if diagnostic_count == 0 {
        println!("{path_str}: ok");
    }

    CommandOutcome {
        has_error,
        has_warning,
    }
}