Skip to main content

strixonomy_diagnostics/
engine.rs

1use crate::config::DiagnosticConfig;
2use crate::input::DiagnosticInput;
3use crate::rules::{
4    broken_imports, duplicate_labels, missing_labels, orphan_classes, parse_errors,
5    undefined_prefixes,
6};
7use std::cell::RefCell;
8use std::collections::HashMap;
9use std::path::PathBuf;
10use strixonomy_core::{
11    read_to_string_capped, Diagnostic, DiagnosticCode, DiagnosticSeverity, SourceLocation,
12    MAX_FILE_BYTES,
13};
14
15/// Collect all diagnostics for a catalog snapshot.
16pub fn collect_diagnostics(input: &DiagnosticInput<'_>) -> Vec<Diagnostic> {
17    collect_diagnostics_with_config(input, &HashMap::new(), None)
18}
19
20/// Collect diagnostics with optional workspace rule configuration.
21pub fn collect_diagnostics_with_config(
22    input: &DiagnosticInput<'_>,
23    source_overrides: &HashMap<PathBuf, String>,
24    config: Option<&DiagnosticConfig>,
25) -> Vec<Diagnostic> {
26    let mut diagnostics = collect_diagnostics_with_sources(input, source_overrides);
27    if let Some(cfg) = config {
28        diagnostics.retain(|d| cfg.is_rule_enabled(d.code));
29        for d in &mut diagnostics {
30            if let Some(sev) = cfg.severity_override(d.code) {
31                d.severity = sev;
32            }
33        }
34    }
35    diagnostics
36}
37
38/// Collect diagnostics, optionally using in-memory source text overrides (LSP open buffers).
39pub fn collect_diagnostics_with_sources(
40    input: &DiagnosticInput<'_>,
41    source_overrides: &HashMap<PathBuf, String>,
42) -> Vec<Diagnostic> {
43    let io_failures: RefCell<Vec<Diagnostic>> = RefCell::new(Vec::new());
44
45    let source = |path: &std::path::Path| -> String {
46        if let Some(text) = source_overrides.get(path) {
47            return text.clone();
48        }
49        if let Ok(canonical) = path.canonicalize() {
50            if let Some(text) = source_overrides.get(&canonical) {
51                return text.clone();
52            }
53        }
54        for (override_path, text) in source_overrides {
55            if strixonomy_core::paths_refer_to_same(override_path, path) {
56                return text.clone();
57            }
58        }
59        match read_to_string_capped(path, MAX_FILE_BYTES) {
60            Ok(text) => text,
61            Err(err) => {
62                io_failures.borrow_mut().push(Diagnostic {
63                    code: DiagnosticCode::IoReadError,
64                    severity: DiagnosticSeverity::Warning,
65                    message: format!("could not read file for lint analysis: {err}"),
66                    file: path.to_path_buf(),
67                    range: SourceLocation::default(),
68                    entity_iri: None,
69                    quick_fix: None,
70                    plugin_id: None,
71                    plugin_code: None,
72                });
73                String::new()
74            }
75        }
76    };
77
78    let mut diagnostics = Vec::new();
79    diagnostics.extend(parse_errors(input, &source));
80    diagnostics.extend(broken_imports(input, &source));
81    diagnostics.extend(undefined_prefixes(input, &source));
82    diagnostics.extend(duplicate_labels(input, &source));
83    diagnostics.extend(missing_labels(input, &source));
84    diagnostics.extend(orphan_classes(input, &source));
85    diagnostics.extend(io_failures.into_inner());
86    diagnostics
87}