wdl_core/concern/lint/warning.rs
1//! Lint warnings.
2
3use crate::concern::lint::Level;
4use crate::concern::lint::TagSet;
5use crate::concern::Code;
6use crate::display;
7use crate::file::Location;
8
9mod builder;
10
11pub use builder::Builder;
12use nonempty::NonEmpty;
13use serde::Deserialize;
14use serde::Serialize;
15
16/// A lint warning.
17#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
18pub struct Warning {
19 /// The code.
20 code: Code,
21
22 /// The lint level.
23 level: Level,
24
25 /// The lint tags.
26 tags: TagSet,
27
28 /// The locations.
29 locations: NonEmpty<Location>,
30
31 /// The subject.
32 subject: String,
33
34 /// The body.
35 body: String,
36
37 /// The (optional) text to describe how to fix the issue.
38 fix: Option<String>,
39}
40
41impl Warning {
42 /// Gets the code for this [`Warning`].
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// use std::path::PathBuf;
48 ///
49 /// use wdl_core::concern::code::Kind;
50 /// use wdl_core::concern::lint;
51 /// use wdl_core::concern::lint::warning::Builder;
52 /// use wdl_core::concern::lint::Level;
53 /// use wdl_core::concern::lint::TagSet;
54 /// use wdl_core::concern::Code;
55 /// use wdl_core::file::Location;
56 /// use wdl_core::Version;
57 ///
58 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
59 /// let warning = Builder::default()
60 /// .code(code)
61 /// .level(Level::High)
62 /// .tags(TagSet::new(&[lint::Tag::Style]))
63 /// .push_location(Location::Unplaced)
64 /// .subject("Hello, world!")
65 /// .body("A body.")
66 /// .fix("How to fix the issue.")
67 /// .try_build()?;
68 ///
69 /// assert_eq!(warning.code().grammar(), &Version::V1);
70 /// assert_eq!(warning.code().index().get(), 1);
71 ///
72 /// # Ok::<(), Box<dyn std::error::Error>>(())
73 /// ```
74 pub fn code(&self) -> &Code {
75 &self.code
76 }
77
78 /// Gets the lint level for this [`Warning`].
79 ///
80 /// # Examples
81 ///
82 /// ```
83 /// use std::path::PathBuf;
84 ///
85 /// use wdl_core::concern::code::Kind;
86 /// use wdl_core::concern::lint;
87 /// use wdl_core::concern::lint::warning::Builder;
88 /// use wdl_core::concern::lint::Level;
89 /// use wdl_core::concern::lint::TagSet;
90 /// use wdl_core::concern::Code;
91 /// use wdl_core::file::Location;
92 /// use wdl_core::Version;
93 ///
94 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
95 /// let warning = Builder::default()
96 /// .code(code)
97 /// .level(Level::High)
98 /// .tags(TagSet::new(&[lint::Tag::Style]))
99 /// .push_location(Location::Unplaced)
100 /// .subject("Hello, world!")
101 /// .body("A body.")
102 /// .fix("How to fix the issue.")
103 /// .try_build()?;
104 ///
105 /// assert_eq!(warning.level(), &Level::High);
106 ///
107 /// # Ok::<(), Box<dyn std::error::Error>>(())
108 /// ```
109 pub fn level(&self) -> &Level {
110 &self.level
111 }
112
113 /// Gets the lint tags for this [`Warning`].
114 ///
115 /// # Examples
116 ///
117 /// ```
118 /// use std::path::PathBuf;
119 ///
120 /// use wdl_core::concern::code::Kind;
121 /// use wdl_core::concern::lint;
122 /// use wdl_core::concern::lint::warning::Builder;
123 /// use wdl_core::concern::lint::Level;
124 /// use wdl_core::concern::lint::TagSet;
125 /// use wdl_core::concern::Code;
126 /// use wdl_core::file::Location;
127 /// use wdl_core::Version;
128 ///
129 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
130 /// let warning = Builder::default()
131 /// .code(code)
132 /// .level(Level::High)
133 /// .tags(TagSet::new(&[lint::Tag::Style]))
134 /// .push_location(Location::Unplaced)
135 /// .subject("Hello, world!")
136 /// .body("A body.")
137 /// .fix("How to fix the issue.")
138 /// .try_build()?;
139 ///
140 /// assert_eq!(warning.tags(), &TagSet::new(&[lint::Tag::Style]));
141 ///
142 /// # Ok::<(), Box<dyn std::error::Error>>(())
143 /// ```
144 pub fn tags(&self) -> &TagSet {
145 &self.tags
146 }
147
148 /// Gets the locations for this [`Warning`].
149 ///
150 /// # Examples
151 ///
152 /// ```
153 /// use std::path::PathBuf;
154 ///
155 /// use wdl_core::concern::code::Kind;
156 /// use wdl_core::concern::lint;
157 /// use wdl_core::concern::lint::warning::Builder;
158 /// use wdl_core::concern::lint::Level;
159 /// use wdl_core::concern::lint::TagSet;
160 /// use wdl_core::concern::Code;
161 /// use wdl_core::file::Location;
162 /// use wdl_core::Version;
163 ///
164 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
165 /// let warning = Builder::default()
166 /// .code(code)
167 /// .level(Level::High)
168 /// .tags(TagSet::new(&[lint::Tag::Style]))
169 /// .subject("Hello, world!")
170 /// .push_location(Location::Unplaced)
171 /// .body("A body.")
172 /// .fix("How to fix the issue.")
173 /// .try_build()?;
174 ///
175 /// assert_eq!(warning.locations().first(), &Location::Unplaced);
176 ///
177 /// # Ok::<(), Box<dyn std::error::Error>>(())
178 /// ```
179 pub fn locations(&self) -> &NonEmpty<Location> {
180 &self.locations
181 }
182
183 /// Gets the subject for this [`Warning`].
184 ///
185 /// # Examples
186 ///
187 /// ```
188 /// use std::path::PathBuf;
189 ///
190 /// use wdl_core::concern::code::Kind;
191 /// use wdl_core::concern::lint;
192 /// use wdl_core::concern::lint::warning::Builder;
193 /// use wdl_core::concern::lint::Level;
194 /// use wdl_core::concern::lint::TagSet;
195 /// use wdl_core::concern::Code;
196 /// use wdl_core::file::Location;
197 /// use wdl_core::Version;
198 ///
199 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
200 /// let warning = Builder::default()
201 /// .code(code)
202 /// .level(Level::High)
203 /// .tags(TagSet::new(&[lint::Tag::Style]))
204 /// .subject("Hello, world!")
205 /// .push_location(Location::Unplaced)
206 /// .body("A body.")
207 /// .fix("How to fix the issue.")
208 /// .try_build()?;
209 ///
210 /// assert_eq!(warning.subject(), "Hello, world!");
211 ///
212 /// # Ok::<(), Box<dyn std::error::Error>>(())
213 /// ```
214 pub fn subject(&self) -> &str {
215 self.subject.as_ref()
216 }
217
218 /// Gets the body for this [`Warning`].
219 ///
220 /// # Examples
221 ///
222 /// ```
223 /// use std::path::PathBuf;
224 ///
225 /// use wdl_core::concern::code::Kind;
226 /// use wdl_core::concern::lint;
227 /// use wdl_core::concern::lint::warning::Builder;
228 /// use wdl_core::concern::lint::Level;
229 /// use wdl_core::concern::lint::TagSet;
230 /// use wdl_core::concern::Code;
231 /// use wdl_core::file::Location;
232 /// use wdl_core::Version;
233 ///
234 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
235 /// let warning = Builder::default()
236 /// .code(code)
237 /// .level(Level::High)
238 /// .tags(TagSet::new(&[lint::Tag::Style]))
239 /// .push_location(Location::Unplaced)
240 /// .subject("Hello, world!")
241 /// .body("A body.")
242 /// .fix("How to fix the issue.")
243 /// .try_build()?;
244 ///
245 /// assert_eq!(warning.body(), "A body.");
246 ///
247 /// # Ok::<(), Box<dyn std::error::Error>>(())
248 /// ```
249 pub fn body(&self) -> &str {
250 self.body.as_str()
251 }
252
253 /// Gets the fix text for this [`Warning`].
254 ///
255 /// # Examples
256 ///
257 /// ```
258 /// use std::path::PathBuf;
259 ///
260 /// use wdl_core::concern::code::Kind;
261 /// use wdl_core::concern::lint;
262 /// use wdl_core::concern::lint::warning::Builder;
263 /// use wdl_core::concern::lint::Level;
264 /// use wdl_core::concern::lint::TagSet;
265 /// use wdl_core::concern::Code;
266 /// use wdl_core::file::Location;
267 /// use wdl_core::Version;
268 ///
269 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
270 /// let warning = Builder::default()
271 /// .code(code)
272 /// .level(Level::High)
273 /// .tags(TagSet::new(&[lint::Tag::Style]))
274 /// .push_location(Location::Unplaced)
275 /// .subject("Hello, world!")
276 /// .body("A body.")
277 /// .fix("How to fix the issue.")
278 /// .try_build()?;
279 ///
280 /// assert_eq!(warning.fix(), Some("How to fix the issue."));
281 ///
282 /// # Ok::<(), Box<dyn std::error::Error>>(())
283 /// ```
284 pub fn fix(&self) -> Option<&str> {
285 self.fix.as_deref()
286 }
287
288 /// Displays a [`Warning`] according to the `mode` specified.
289 ///
290 /// # Examples
291 ///
292 /// ```
293 /// use std::fmt::Write as _;
294 /// use std::path::PathBuf;
295 ///
296 /// use wdl_core::concern::lint::warning::Builder;
297 /// use wdl_core::concern::lint::TagSet;
298 /// use wdl_core::concern::lint;
299 /// use wdl_core::concern::lint::Level;
300 /// use wdl_core::concern::Code;
301 /// use wdl_core::concern::code::Kind;
302 /// use wdl_core::file::Location;
303 /// use wdl_core::Version;
304 /// use wdl_core::display;
305 ///
306 /// let code = Code::try_new(Kind::Warning, Version::V1, 1)?;
307 /// let warning = Builder::default()
308 /// .code(code)
309 /// .level(Level::High)
310 /// .tags(TagSet::new(&[lint::Tag::Style]))
311 /// .push_location(Location::Unplaced)
312 /// .subject("Hello, world!")
313 /// .body("A body.")
314 /// .fix("Apply ample foobar.")
315 /// .try_build()?;
316 ///
317 /// let mut result = String::new();
318 /// warning.display(&mut result, display::Mode::OneLine)?;
319 /// assert_eq!(result, String::from("[v1::W001::[Style]::High] Hello, world!"));
320 ///
321 /// result.clear();
322 /// warning.display(&mut result, display::Mode::Full)?;
323 /// assert_eq!(result, String::from("[v1::W001::[Style]::High] Hello, world!\n\nA body.\n\nTo fix this warning, apply ample foobar."));
324 ///
325 /// # Ok::<(), Box<dyn std::error::Error>>(())
326 pub fn display(&self, f: &mut impl std::fmt::Write, mode: display::Mode) -> std::fmt::Result {
327 match mode {
328 display::Mode::OneLine => display_one_line(self, f),
329 display::Mode::Full => display_full(self, f),
330 }
331 }
332}
333
334/// Displays the warning as a single line.
335fn display_one_line(warning: &Warning, f: &mut impl std::fmt::Write) -> std::fmt::Result {
336 write!(
337 f,
338 "[{}::{}::{:?}] {}",
339 warning.code, warning.tags, warning.level, warning.subject
340 )?;
341
342 let locations = warning
343 .locations
344 .iter()
345 .flat_map(|location| location.to_string())
346 .collect::<Vec<_>>();
347
348 if !locations.is_empty() {
349 write!(f, " ({})", locations.join(", "))?;
350 }
351
352 Ok(())
353}
354
355/// Displays all information about the warning.
356fn display_full(warning: &Warning, f: &mut impl std::fmt::Write) -> std::fmt::Result {
357 display_one_line(warning, f)?;
358 write!(f, "\n\n{}", warning.body)?;
359
360 if let Some(fix) = warning.fix() {
361 write!(f, "\n\nTo fix this warning, {}", fix.to_ascii_lowercase())?;
362 }
363
364 Ok(())
365}
366
367impl std::fmt::Display for Warning {
368 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
369 self.display(f, display::Mode::OneLine)
370 }
371}
372
373#[cfg(test)]
374mod tests {
375 use super::*;
376 use crate::concern::lint::Tag;
377
378 #[test]
379 fn display() -> Result<(), Box<dyn std::error::Error>> {
380 let code = Code::try_new(crate::concern::code::Kind::Warning, crate::Version::V1, 1)?;
381 let warning = Builder::default()
382 .code(code)
383 .level(Level::Medium)
384 .tags(TagSet::new(&[Tag::Style]))
385 .push_location(Location::Unplaced)
386 .subject("Hello, world!")
387 .body("A body.")
388 .fix("How to fix the issue.")
389 .try_build()?;
390
391 assert_eq!(
392 warning.to_string(),
393 "[v1::W001::[Style]::Medium] Hello, world!"
394 );
395
396 Ok(())
397 }
398}