use std::collections::HashSet;
use std::path::Path;
use std::process;
use harn_lint::LintSeverity;
use harn_parser::{TypeChecker, TypeDiagnostic};
use crate::package::CheckConfig;
use crate::parse_source_file;
use super::outcome::{print_lint_diagnostics, CommandOutcome};
pub(crate) fn lint_file_inner(
path: &Path,
config: &CheckConfig,
externally_imported_names: &HashSet<String>,
module_graph: &harn_modules::ModuleGraph,
require_file_header: bool,
complexity_threshold: Option<usize>,
persona_step_allowlist: &[String],
) -> CommandOutcome {
let path_str = path.to_string_lossy().into_owned();
let (source, program) = parse_source_file(&path_str);
let options = harn_lint::LintOptions {
file_path: Some(path),
require_file_header,
complexity_threshold,
persona_step_allowlist,
};
let mut diagnostics = harn_lint::lint_with_module_graph(
&program,
&config.disable_rules,
Some(&source),
externally_imported_names,
module_graph,
path,
&options,
);
let type_diags = type_check_for_lint(path, config, module_graph, &program, &source);
diagnostics.extend(harn_lint::lint_diagnostics_from_type_diagnostics(
&type_diags,
&config.disable_rules,
));
if diagnostics.is_empty() {
println!("{path_str}: no issues found");
return CommandOutcome::default();
}
let has_warning = diagnostics
.iter()
.any(|d| d.severity == LintSeverity::Warning);
let has_error = print_lint_diagnostics(&path_str, &source, &diagnostics);
CommandOutcome {
has_error,
has_warning,
}
}
pub(crate) fn lint_fix_file(
path: &Path,
config: &CheckConfig,
externally_imported_names: &HashSet<String>,
module_graph: &harn_modules::ModuleGraph,
require_file_header: bool,
complexity_threshold: Option<usize>,
persona_step_allowlist: &[String],
) -> usize {
let path_str = path.to_string_lossy().into_owned();
let (source, program) = parse_source_file(&path_str);
let options = harn_lint::LintOptions {
file_path: Some(path),
require_file_header,
complexity_threshold,
persona_step_allowlist,
};
let lint_diags = harn_lint::lint_with_module_graph(
&program,
&config.disable_rules,
Some(&source),
externally_imported_names,
module_graph,
path,
&options,
);
let type_diags = type_check_for_lint(path, config, module_graph, &program, &source);
let mut edits: Vec<&harn_lexer::FixEdit> = lint_diags
.iter()
.filter_map(|d| d.fix.as_ref())
.chain(
type_diags
.iter()
.filter(|d| !harn_lint::type_diagnostic_lint_disabled(d, &config.disable_rules))
.filter_map(|d| d.fix.as_ref()),
)
.flatten()
.collect();
if edits.is_empty() {
return 0;
}
edits.sort_by_key(|edit| std::cmp::Reverse(edit.span.start));
let mut accepted: Vec<&harn_lexer::FixEdit> = Vec::new();
for edit in &edits {
let overlaps = accepted
.iter()
.any(|prev| edit.span.start < prev.span.end && edit.span.end > prev.span.start);
if !overlaps {
accepted.push(edit);
}
}
let mut result = source.clone();
for edit in &accepted {
let before = &result[..edit.span.start];
let after = &result[edit.span.end..];
result = format!("{before}{}{after}", edit.replacement);
}
let applied = accepted.len();
std::fs::write(path, &result).unwrap_or_else(|e| {
eprintln!("Failed to write {path_str}: {e}");
process::exit(1);
});
println!("{path_str}: applied {applied} fix(es)");
let (source2, program2) = parse_source_file(&path_str);
let mut remaining = harn_lint::lint_with_module_graph(
&program2,
&config.disable_rules,
Some(&source2),
externally_imported_names,
module_graph,
path,
&options,
);
let type_remaining = type_check_for_lint(path, config, module_graph, &program2, &source2);
remaining.extend(harn_lint::lint_diagnostics_from_type_diagnostics(
&type_remaining,
&config.disable_rules,
));
if !remaining.is_empty() {
print_lint_diagnostics(&path_str, &source2, &remaining);
}
applied
}
fn type_check_for_lint(
path: &Path,
config: &CheckConfig,
module_graph: &harn_modules::ModuleGraph,
program: &[harn_parser::SNode],
source: &str,
) -> Vec<TypeDiagnostic> {
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);
}
if let Some(imported) = module_graph.imported_type_declarations_for_file(path) {
checker = checker.with_imported_type_decls(imported);
}
checker.check_with_source(program, source)
}