Skip to content

Commit 492bfa1

Browse files
committed
Parse File ... not found messages
This will help us keep the module set in sync more reliably.
1 parent f75c43c commit 492bfa1

File tree

2 files changed

+327
-0
lines changed

2 files changed

+327
-0
lines changed

src/ghci/parse/ghc_message/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ use module_import_cycle_diagnostic::module_import_cycle_diagnostic;
4343
mod no_location_info_diagnostic;
4444
use no_location_info_diagnostic::no_location_info_diagnostic;
4545

46+
mod not_found;
47+
use not_found::not_found;
48+
pub use not_found::NotFound;
49+
4650
use super::rest_of_line;
4751
use super::CompilingModule;
4852

@@ -51,6 +55,13 @@ use super::CompilingModule;
5155
/// These include progress updates on compilation, errors and warnings, or GHCi messages.
5256
#[derive(Debug, Clone, PartialEq, Eq)]
5357
pub enum GhcMessage {
58+
/// A module or file was not found.
59+
///
60+
/// ```text
61+
/// File src/Foo.hs not found
62+
/// Module Foo not found
63+
/// ```
64+
NotFound(NotFound),
5465
/// A module being compiled.
5566
///
5667
/// ```text
@@ -172,6 +183,7 @@ fn parse_messages_inner(input: &mut &str) -> PResult<Vec<GhcMessage>> {
172183
.map(GhcMessage::Diagnostic)
173184
.map(Item::One),
174185
compilation_summary.map(GhcMessage::Summary).map(Item::One),
186+
not_found.map(GhcMessage::NotFound).map(Item::One),
175187
cant_find_file_diagnostic
176188
.map(GhcMessage::Diagnostic)
177189
.map(Item::One),
@@ -219,6 +231,10 @@ mod tests {
219231
Preprocessing library 'test-dev' for my-simple-package-0.1.0.0..
220232
GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help
221233
Loaded GHCi configuration from /Users/wiggles/.ghci
234+
File src/Puppy.hs not found
235+
File src/
236+
Puppy.hs not found
237+
Module Puppy.Doggy not found
222238
[1 of 4] Compiling MyLib ( src/MyLib.hs, interpreted )
223239
[2 of 4] Compiling MyModule ( src/MyModule.hs, interpreted )
224240
@@ -239,6 +255,9 @@ mod tests {
239255
GhcMessage::LoadConfig {
240256
path: "/Users/wiggles/.ghci".into()
241257
},
258+
GhcMessage::NotFound(NotFound::File("src/Puppy.hs".into())),
259+
GhcMessage::NotFound(NotFound::File("src/\nPuppy.hs".into())),
260+
GhcMessage::NotFound(NotFound::Module("Puppy.Doggy".into())),
242261
GhcMessage::Compiling(CompilingModule {
243262
name: "MyLib".into(),
244263
path: "src/MyLib.hs".into(),
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
use std::str::FromStr;
2+
3+
use camino::Utf8PathBuf;
4+
use miette::miette;
5+
use winnow::combinator::alt;
6+
use winnow::combinator::rest;
7+
use winnow::PResult;
8+
use winnow::Parser;
9+
10+
use crate::ghci::parse::haskell_grammar::module_name;
11+
use crate::ghci::parse::haskell_source_file;
12+
use crate::ghci::parse::lines::line_ending_or_eof;
13+
14+
use super::single_quoted::single_quoted;
15+
16+
/// A module or file was not found.
17+
#[derive(Debug, Clone, PartialEq, Eq)]
18+
pub enum NotFound {
19+
/// A file for import was not found.
20+
///
21+
/// ```text
22+
/// File src/Foo.hs not found
23+
/// ```
24+
File(Utf8PathBuf),
25+
26+
/// A module for import was not found.
27+
///
28+
/// ```text
29+
/// Module Foo not found
30+
/// ```
31+
Module(String),
32+
33+
/// A target was not found as a source file or recognized as a module name.
34+
///
35+
/// ```text
36+
/// target ‘src/Puppy’ is not a module name or a source file
37+
/// ```
38+
Unrecognized(String),
39+
}
40+
41+
impl FromStr for NotFound {
42+
type Err = miette::Report;
43+
44+
fn from_str(s: &str) -> Result<Self, Self::Err> {
45+
not_found.parse(s).map_err(|err| miette!("{err}"))
46+
}
47+
}
48+
49+
/// Parse a `File src/Foo.hs not found` message.
50+
fn file_not_found(input: &mut &str) -> PResult<Utf8PathBuf> {
51+
let _ = "File ".parse_next(input)?;
52+
let (path, _) = haskell_source_file(" not found").parse_next(input)?;
53+
let _ = line_ending_or_eof.parse_next(input)?;
54+
Ok(path)
55+
}
56+
57+
/// Parse a `Module Foo not found` message.
58+
fn module_not_found(input: &mut &str) -> PResult<String> {
59+
let _ = "Module ".parse_next(input)?;
60+
let module = module_name(input)?;
61+
let _ = " not found".parse_next(input)?;
62+
let _ = line_ending_or_eof.parse_next(input)?;
63+
Ok(module.to_owned())
64+
}
65+
66+
/// Parse a `target 'Foo' is not a module name or a source file` message.
67+
fn unrecognized(input: &mut &str) -> PResult<String> {
68+
let _ = "target ".parse_next(input)?;
69+
let (name, _) = single_quoted(
70+
rest,
71+
(" is not a module name or a source file", line_ending_or_eof),
72+
)
73+
.parse_next(input)?;
74+
Ok(name.to_owned())
75+
}
76+
77+
pub fn not_found(input: &mut &str) -> PResult<NotFound> {
78+
alt((
79+
file_not_found.map(NotFound::File),
80+
module_not_found.map(NotFound::Module),
81+
unrecognized.map(NotFound::Unrecognized),
82+
))
83+
.parse_next(input)
84+
}
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use super::*;
89+
use pretty_assertions::assert_eq;
90+
91+
#[test]
92+
fn test_parse_module_not_found() {
93+
assert_eq!(
94+
"Module Puppy not found".parse::<NotFound>().unwrap(),
95+
NotFound::Module("Puppy".into())
96+
);
97+
98+
assert_eq!(
99+
"Module Puppy not found\n".parse::<NotFound>().unwrap(),
100+
NotFound::Module("Puppy".into())
101+
);
102+
103+
assert_eq!(
104+
"Module Puppy.Doggy' not found\n"
105+
.parse::<NotFound>()
106+
.unwrap(),
107+
NotFound::Module("Puppy.Doggy'".into())
108+
);
109+
110+
// Negative cases.
111+
assert!("Module Puppy Doggy not found\n"
112+
.parse::<NotFound>()
113+
.is_err());
114+
assert!("Module Puppy\\ Doggy not found\n"
115+
.parse::<NotFound>()
116+
.is_err());
117+
assert!("Module Puppy*.Doggy not found\n"
118+
.parse::<NotFound>()
119+
.is_err());
120+
assert!("Module Puppy.Doggy not\n".parse::<NotFound>().is_err());
121+
assert!("Module Puppy\n.Doggy not found\n"
122+
.parse::<NotFound>()
123+
.is_err());
124+
}
125+
126+
#[test]
127+
fn test_parse_file_not_found() {
128+
assert_eq!(
129+
"File src/Puppy.hs not found".parse::<NotFound>().unwrap(),
130+
NotFound::File("src/Puppy.hs".into())
131+
);
132+
133+
assert_eq!(
134+
"File src/Puppy.hs not found\n".parse::<NotFound>().unwrap(),
135+
NotFound::File("src/Puppy.hs".into())
136+
);
137+
138+
assert_eq!(
139+
"File src/ Puppy.hs not found\n"
140+
.parse::<NotFound>()
141+
.unwrap(),
142+
NotFound::File("src/ Puppy.hs".into())
143+
);
144+
145+
assert_eq!(
146+
"File src/\nPuppy.hs not found\n"
147+
.parse::<NotFound>()
148+
.unwrap(),
149+
NotFound::File("src/\nPuppy.hs".into())
150+
);
151+
152+
assert_eq!(
153+
"File src/Puppy.hs.lhs not found\n"
154+
.parse::<NotFound>()
155+
.unwrap(),
156+
NotFound::File("src/Puppy.hs.lhs".into())
157+
);
158+
159+
assert_eq!(
160+
"File src/Puppy.hs not foun.lhs not found\n"
161+
.parse::<NotFound>()
162+
.unwrap(),
163+
NotFound::File("src/Puppy.hs not foun.lhs".into())
164+
);
165+
166+
// Negative cases.
167+
168+
// No extension.
169+
assert!("File src/Puppy not found\n".parse::<NotFound>().is_err());
170+
171+
// Non-Haskell extension.
172+
assert!("File src/Puppy.x not found\n".parse::<NotFound>().is_err());
173+
assert!("File src/Puppy.hs.bak not found\n"
174+
.parse::<NotFound>()
175+
.is_err());
176+
177+
// Extra punctuation.
178+
assert!("File src/Puppy.hs not found!\n"
179+
.parse::<NotFound>()
180+
.is_err());
181+
182+
// Case sensitivity.
183+
assert!("file src/Puppy.hs not found!\n"
184+
.parse::<NotFound>()
185+
.is_err());
186+
}
187+
188+
#[test]
189+
fn test_parse_unrecognized_not_found() {
190+
// The input and quoting here is maddeningly open-ended so there's a ton of these cases.
191+
192+
assert_eq!(
193+
"target ‘src/Puppy’ is not a module name or a source file"
194+
.parse::<NotFound>()
195+
.unwrap(),
196+
NotFound::Unrecognized("src/Puppy".into()),
197+
);
198+
199+
// Newline at end.
200+
assert_eq!(
201+
"target ‘src/Puppy’ is not a module name or a source file\n"
202+
.parse::<NotFound>()
203+
.unwrap(),
204+
NotFound::Unrecognized("src/Puppy".into()),
205+
);
206+
207+
// Empty string.
208+
assert_eq!(
209+
"target ‘’ is not a module name or a source file"
210+
.parse::<NotFound>()
211+
.unwrap(),
212+
NotFound::Unrecognized("".into()),
213+
);
214+
215+
// Whitespace.
216+
assert_eq!(
217+
"target ‘src/ Puppy’ is not a module name or a source file"
218+
.parse::<NotFound>()
219+
.unwrap(),
220+
NotFound::Unrecognized("src/ Puppy".into()),
221+
);
222+
assert_eq!(
223+
"target ‘ src/Puppy’ is not a module name or a source file"
224+
.parse::<NotFound>()
225+
.unwrap(),
226+
NotFound::Unrecognized(" src/Puppy".into()),
227+
);
228+
assert_eq!(
229+
"target ‘src/Puppy ’ is not a module name or a source file"
230+
.parse::<NotFound>()
231+
.unwrap(),
232+
NotFound::Unrecognized("src/Puppy ".into()),
233+
);
234+
235+
// Internal quotes!
236+
assert_eq!(
237+
"target ‘src/Pupp'y’ is not a module name or a source file"
238+
.parse::<NotFound>()
239+
.unwrap(),
240+
NotFound::Unrecognized("src/Pupp'y".into()),
241+
);
242+
assert_eq!(
243+
"target ‘src/Pupp'''y’ is not a module name or a source file"
244+
.parse::<NotFound>()
245+
.unwrap(),
246+
NotFound::Unrecognized("src/Pupp'''y".into()),
247+
);
248+
assert_eq!(
249+
"target ‘'src/Puppy'’ is not a module name or a source file"
250+
.parse::<NotFound>()
251+
.unwrap(),
252+
NotFound::Unrecognized("'src/Puppy'".into()),
253+
);
254+
assert_eq!(
255+
"target ‘‘src/Puppy’’ is not a module name or a source file"
256+
.parse::<NotFound>()
257+
.unwrap(),
258+
NotFound::Unrecognized("‘src/Puppy’".into()),
259+
);
260+
261+
// Newlines oh my!
262+
assert_eq!(
263+
"target ‘src\n/Puppy\n’ is not a module name or a source file"
264+
.parse::<NotFound>()
265+
.unwrap(),
266+
NotFound::Unrecognized("src\n/Puppy\n".into()),
267+
);
268+
269+
// ASCII quotes.
270+
assert_eq!(
271+
"target `src/Puppy' is not a module name or a source file"
272+
.parse::<NotFound>()
273+
.unwrap(),
274+
NotFound::Unrecognized("src/Puppy".into()),
275+
);
276+
assert_eq!(
277+
"target ``src/Puppy' is not a module name or a source file"
278+
.parse::<NotFound>()
279+
.unwrap(),
280+
NotFound::Unrecognized("`src/Puppy".into()),
281+
);
282+
assert_eq!(
283+
"target `src/Pupp'y`' is not a module name or a source file"
284+
.parse::<NotFound>()
285+
.unwrap(),
286+
NotFound::Unrecognized("src/Pupp'y`".into()),
287+
);
288+
289+
assert_eq!(
290+
"target 'src/Puppy' is not a module name or a source file"
291+
.parse::<NotFound>()
292+
.unwrap(),
293+
NotFound::Unrecognized("'src/Puppy'".into()),
294+
);
295+
assert_eq!(
296+
"target 'src/Pupp'y' is not a module name or a source file"
297+
.parse::<NotFound>()
298+
.unwrap(),
299+
NotFound::Unrecognized("'src/Pupp'y'".into()),
300+
);
301+
assert_eq!(
302+
"target src/Puppy' is not a module name or a source file"
303+
.parse::<NotFound>()
304+
.unwrap(),
305+
NotFound::Unrecognized("src/Puppy'".into()),
306+
);
307+
}
308+
}

0 commit comments

Comments
 (0)