Skip to main content

lisette_diagnostics/
module_graph.rs

1use crate::LisetteDiagnostic;
2use syntax::ast::Span;
3
4pub fn module_not_found(
5    module_name: &str,
6    span: Span,
7    is_go_stdlib: bool,
8    standalone: bool,
9    src_prefix_hint: Option<String>,
10) -> LisetteDiagnostic {
11    let help = if let Some(stripped) = src_prefix_hint {
12        format!(
13            "Did you mean `import \"{}\"`? The `src/` prefix is not needed — imports are relative to the source directory.",
14            stripped
15        )
16    } else if is_go_stdlib {
17        format!(
18            "No `{}` module found in your local project. Did you mean `import \"go:{}\"` from Go's stdlib?",
19            module_name, module_name
20        )
21    } else if standalone {
22        "When executing `lis run` on an individual file, that file may import only from the Go standard library. To import modules normally, use `lis new` to create a project."
23            .to_string()
24    } else {
25        "Check the module path and ensure the file exists".to_string()
26    };
27
28    LisetteDiagnostic::error("Module not found")
29        .with_resolve_code("module_not_found")
30        .with_span_label(&span, "not found")
31        .with_help(help)
32}
33
34pub fn invalid_module_path(module_name: &str, span: Span) -> LisetteDiagnostic {
35    LisetteDiagnostic::error(format!("Invalid module path `{}`", module_name))
36        .with_resolve_code("invalid_module_path")
37        .with_span_label(&span, "module paths cannot contain `.`")
38        .with_help(
39            "Project imports use bare folder names like `import \"util\"` or `import \"nested/deep/module\"`. Relative-path syntax (`./sub`, `../sub`) is not supported.",
40        )
41}
42
43pub fn cannot_import_prelude(span: Span) -> LisetteDiagnostic {
44    LisetteDiagnostic::error("Invalid import")
45        .with_resolve_code("cannot_import_prelude")
46        .with_span_label(&span, "prelude is automatically available")
47        .with_help("Remove this import. Use e.g. `Option` or `prelude.Option` directly.")
48}
49
50pub fn test_file_not_supported(filename: &str) -> LisetteDiagnostic {
51    LisetteDiagnostic::error(format!("Test file `{}` is not yet supported", filename))
52        .with_resolve_code("test_file_not_supported")
53        .with_help("Files ending in `_test.lis` are reserved for future testing support. Rename this file to compile it.")
54}
55
56pub fn undeclared_go_import(go_pkg: &str, span: Span) -> LisetteDiagnostic {
57    LisetteDiagnostic::error("Undeclared Go dependency")
58        .with_resolve_code("undeclared_go_import")
59        .with_span_label(&span, "not in lisette.toml")
60        .with_help(format!(
61            "Run `lis add {}` to add this dependency, or add it manually to `[dependencies.go]` in `lisette.toml`",
62            go_pkg
63        ))
64}
65
66pub fn missing_go_typedef(
67    go_pkg: &str,
68    module: &str,
69    version: &str,
70    span: Span,
71) -> LisetteDiagnostic {
72    LisetteDiagnostic::error("Missing Go typedef")
73        .with_resolve_code("missing_go_typedef")
74        .with_span_label(&span, "no .d.lis file found")
75        .with_help(format!(
76            "Package `{}` is declared via `{}` {} but no typedef was found. Run `lis check` to regenerate all typedefs, or `lis add {}@{}` to regenerate this one.",
77            go_pkg, module, version, module, version
78        ))
79}
80
81pub fn unreadable_go_typedef(path: &std::path::Path, error: &str, span: Span) -> LisetteDiagnostic {
82    LisetteDiagnostic::error("Failed to read Go typedef")
83        .with_resolve_code("unreadable_go_typedef")
84        .with_span_label(&span, "typedef exists but could not be read")
85        .with_help(format!("Failed to read `{}`: {}", path.display(), error,))
86}
87
88pub fn import_cycle(path: &[String]) -> LisetteDiagnostic {
89    let modules: Vec<_> = path[..path.len() - 1].to_vec();
90
91    let is_self_import = modules.len() == 1;
92
93    let chain = if is_self_import {
94        format!("{} -> {}", modules[0], modules[0])
95    } else {
96        modules.join(" -> ")
97    };
98
99    let first_module = &modules[0];
100    let first_end = first_module.len();
101    let first_center = first_module.len() / 2;
102
103    let last_module = if is_self_import {
104        &modules[0]
105    } else {
106        modules.last().expect("cycle must have at least one module")
107    };
108    let last_start = chain.len() - last_module.len();
109    let last_end = chain.len();
110    let last_center = last_start + last_module.len() / 2;
111
112    let mut underline = String::new();
113    for i in 0..chain.len() {
114        if i < first_end {
115            if i == first_center {
116                underline.push('┬');
117            } else {
118                underline.push('─');
119            }
120        } else if i >= last_start && i < last_end {
121            if i == last_center {
122                underline.push('┬');
123            } else {
124                underline.push('─');
125            }
126        } else {
127            underline.push(' ');
128        }
129    }
130
131    let mut connect_line = String::new();
132    for i in 0..=last_center {
133        if i < first_center {
134            connect_line.push(' ');
135        } else if i == first_center {
136            connect_line.push('╰');
137        } else if i < last_center {
138            connect_line.push('─');
139        } else {
140            connect_line.push('╯');
141        }
142    }
143
144    let art = format!("{}\n{}\n{}", chain, underline, connect_line);
145
146    let help = if is_self_import {
147        "Remove the self-import"
148    } else {
149        "To break the cycle, remove one of imports or extract common dependencies into a separate module"
150    };
151
152    LisetteDiagnostic::error(format!("Import cycle detected\n\n{}", art))
153        .with_resolve_code("import_cycle")
154        .with_help(help)
155}