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