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 missing_go_prefix(module_name: &str, span: Span, is_blank: bool) -> LisetteDiagnostic {
44    let suggestion = if is_blank {
45        format!("import _ \"go:{}\"", module_name)
46    } else {
47        format!("import \"go:{}\"", module_name)
48    };
49    LisetteDiagnostic::error(format!("Invalid module path `{}`", module_name))
50        .with_resolve_code("missing_go_prefix")
51        .with_span_label(&span, "Go imports require the `go:` prefix")
52        .with_help(format!(
53            "`{}` is a declared Go dependency. Did you mean `{}`?",
54            module_name, suggestion
55        ))
56}
57
58pub fn cannot_import_prelude(span: Span) -> LisetteDiagnostic {
59    LisetteDiagnostic::error("Invalid import")
60        .with_resolve_code("cannot_import_prelude")
61        .with_span_label(&span, "prelude is automatically available")
62        .with_help("Remove this import. Use e.g. `Option` or `prelude.Option` directly.")
63}
64
65pub fn test_file_not_supported(filename: &str) -> LisetteDiagnostic {
66    LisetteDiagnostic::error(format!("Test file `{}` is not yet supported", filename))
67        .with_resolve_code("test_file_not_supported")
68        .with_help("Files ending in `_test.lis` are reserved for future testing support. Rename this file to compile it.")
69}
70
71pub fn go_stdlib_unavailable_on_target(
72    go_pkg: &str,
73    target: &str,
74    available: &str,
75    span: Span,
76) -> LisetteDiagnostic {
77    LisetteDiagnostic::error(format!("`go:{}` is not available on `{}`", go_pkg, target))
78        .with_resolve_code("go_stdlib_unavailable_on_target")
79        .with_span_label(&span, "stdlib package not available on this target")
80        .with_help(format!(
81            "This Go stdlib package exists, but its surface differs across platforms. Available on: {}",
82            available
83        ))
84}
85
86pub fn undeclared_go_import(go_pkg: &str, span: Span) -> LisetteDiagnostic {
87    LisetteDiagnostic::error("Undeclared Go dependency")
88        .with_resolve_code("undeclared_go_import")
89        .with_span_label(&span, "not in lisette.toml")
90        .with_help(format!(
91            "Run `lis add {}` to add this dependency, or add it manually to `[dependencies.go]` in `lisette.toml`",
92            go_pkg
93        ))
94}
95
96pub fn missing_go_typedef(
97    go_pkg: &str,
98    module: &str,
99    version: &str,
100    span: Span,
101) -> LisetteDiagnostic {
102    let help = if go_pkg == module {
103        format!(
104            "Module `{}` {} is declared but no typedef was found. Run `lis check` to regenerate all typedefs, or `lis add {}@{}` to regenerate this one.",
105            module, version, module, version
106        )
107    } else {
108        format!(
109            "Subpackage `{}` of module `{}` {} has no typedef. Run `lis add {}@{}` to regenerate the module's typedefs, including any subpackages.",
110            go_pkg, module, version, module, version
111        )
112    };
113
114    LisetteDiagnostic::error("Missing Go typedef")
115        .with_resolve_code("missing_go_typedef")
116        .with_span_label(&span, "no .d.lis file found")
117        .with_help(help)
118}
119
120pub fn unreadable_go_typedef(path: &std::path::Path, error: &str, span: Span) -> LisetteDiagnostic {
121    LisetteDiagnostic::error("Failed to read Go typedef")
122        .with_resolve_code("unreadable_go_typedef")
123        .with_span_label(&span, "typedef exists but could not be read")
124        .with_help(format!("Failed to read `{}`: {}", path.display(), error,))
125}
126
127pub fn go_toolchain_missing(go_pkg: &str, span: Span) -> LisetteDiagnostic {
128    LisetteDiagnostic::error(format!(
129        "Cannot generate Go typedef for `{}`: `go` is not installed",
130        go_pkg
131    ))
132    .with_resolve_code("go_toolchain_missing")
133    .with_span_label(&span, "needs the Go toolchain")
134    .with_help("Install Go from https://go.dev/dl/")
135}
136
137pub fn bindgen_failed(
138    go_pkg: &str,
139    module: &str,
140    version: &str,
141    stderr: &str,
142    span: Span,
143) -> LisetteDiagnostic {
144    let trimmed = stderr.trim();
145    let stderr_block = if trimmed.is_empty() {
146        String::new()
147    } else {
148        format!("\n\n{}", trimmed)
149    };
150
151    LisetteDiagnostic::error(format!(
152        "Failed to generate Go typedef for `{}` ({} {})",
153        go_pkg, module, version
154    ))
155    .with_resolve_code("bindgen_failed")
156    .with_span_label(&span, "bindgen failed for this import")
157    .with_help(format!(
158        "Re-run with `lis bindgen {}` to inspect the failure in isolation.{}",
159        go_pkg, stderr_block
160    ))
161}
162
163pub fn import_cycle(path: &[String]) -> LisetteDiagnostic {
164    let modules: Vec<_> = path[..path.len() - 1].to_vec();
165
166    let is_self_import = modules.len() == 1;
167
168    let chain = if is_self_import {
169        format!("{} -> {}", modules[0], modules[0])
170    } else {
171        modules.join(" -> ")
172    };
173
174    let first_module = &modules[0];
175    let first_end = first_module.len();
176    let first_center = first_module.len() / 2;
177
178    let last_module = if is_self_import {
179        &modules[0]
180    } else {
181        modules.last().expect("cycle must have at least one module")
182    };
183    let last_start = chain.len() - last_module.len();
184    let last_end = chain.len();
185    let last_center = last_start + last_module.len() / 2;
186
187    let mut underline = String::new();
188    for i in 0..chain.len() {
189        if i < first_end {
190            if i == first_center {
191                underline.push('┬');
192            } else {
193                underline.push('─');
194            }
195        } else if i >= last_start && i < last_end {
196            if i == last_center {
197                underline.push('┬');
198            } else {
199                underline.push('─');
200            }
201        } else {
202            underline.push(' ');
203        }
204    }
205
206    let mut connect_line = String::new();
207    for i in 0..=last_center {
208        if i < first_center {
209            connect_line.push(' ');
210        } else if i == first_center {
211            connect_line.push('╰');
212        } else if i < last_center {
213            connect_line.push('─');
214        } else {
215            connect_line.push('╯');
216        }
217    }
218
219    let art = format!("{}\n{}\n{}", chain, underline, connect_line);
220
221    let help = if is_self_import {
222        "Remove the self-import"
223    } else {
224        "To break the cycle, remove one of the imports or extract common dependencies into a separate module"
225    };
226
227    LisetteDiagnostic::error(format!("Import cycle detected\n\n{}", art))
228        .with_resolve_code("import_cycle")
229        .with_help(help)
230}