• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pomsky-lang / pomsky / 12301483439

12 Dec 2024 05:19PM UTC coverage: 80.275% (-0.2%) from 80.471%
12301483439

push

github

Aloso
feat: test command

360 of 593 new or added lines in 11 files covered. (60.71%)

20 existing lines in 7 files now uncovered.

4607 of 5739 relevant lines covered (80.28%)

374427.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.6
/pomsky-bin/src/result/mod.rs
1
use std::{fmt, path::Path};
2

3
use pomsky::diagnose::{DiagnosticCode, DiagnosticKind};
4
use serde::{Deserialize, Serialize};
5

6
mod serde_code;
7

8
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
9
pub struct CompilationResult {
10
    /// Schema version
11
    pub version: Version,
12
    /// Whether compilation succeeded
13
    ///
14
    /// Equivalent to `result.output.is_some()`
15
    pub success: bool,
16
    /// File that was compiled
17
    pub path: Option<String>,
18
    /// Compilation result
19
    #[serde(skip_serializing_if = "Option::is_none")]
20
    pub output: Option<String>,
21
    /// Array of errors and warnings
22
    pub diagnostics: Vec<Diagnostic>,
23
    /// Compilation time
24
    pub timings: Timings,
25
}
26

27
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
28
pub enum Version {
29
    #[serde(rename = "1")]
30
    V1,
31
}
32

33
impl CompilationResult {
34
    #[allow(clippy::too_many_arguments)]
35
    pub(crate) fn success(
16✔
36
        path: Option<&Path>,
16✔
37
        output: String,
16✔
38
        time_all_micros: u128,
16✔
39
        time_test_micros: u128,
16✔
40
        diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
16✔
41
        source_code: &str,
16✔
42
        warnings: &crate::args::DiagnosticSet,
16✔
43
        json: bool,
16✔
44
    ) -> Self {
16✔
45
        Self {
16✔
46
            path: path
16✔
47
                .map(|p| p.canonicalize().as_deref().unwrap_or(p).to_string_lossy().to_string()),
16✔
48
            version: Version::V1,
16✔
49
            success: true,
16✔
50
            output: Some(output),
16✔
51
            diagnostics: Self::convert_diagnostics(diagnostics, source_code, warnings, json),
16✔
52
            timings: Timings::from_micros(time_all_micros, time_test_micros),
16✔
53
        }
16✔
54
    }
16✔
55

56
    pub(crate) fn error(
3✔
57
        path: Option<&Path>,
3✔
58
        time_all_micros: u128,
3✔
59
        time_test_micros: u128,
3✔
60
        diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
3✔
61
        source_code: &str,
3✔
62
        warnings: &crate::args::DiagnosticSet,
3✔
63
        json: bool,
3✔
64
    ) -> Self {
3✔
65
        Self {
3✔
66
            path: path
3✔
67
                .map(|p| p.canonicalize().as_deref().unwrap_or(p).to_string_lossy().to_string()),
3✔
68
            version: Version::V1,
3✔
69
            success: false,
3✔
70
            output: None,
3✔
71
            diagnostics: Self::convert_diagnostics(diagnostics, source_code, warnings, json),
3✔
72
            timings: Timings::from_micros(time_all_micros, time_test_micros),
3✔
73
        }
3✔
74
    }
3✔
75

76
    fn convert_diagnostics(
19✔
77
        diagnostics: impl IntoIterator<Item = pomsky::diagnose::Diagnostic>,
19✔
78
        source_code: &str,
19✔
79
        warnings: &crate::args::DiagnosticSet,
19✔
80
        json: bool,
19✔
81
    ) -> Vec<Diagnostic> {
19✔
82
        let source_code = Some(source_code);
19✔
83
        diagnostics
19✔
84
            .into_iter()
19✔
85
            .filter_map(|d| match d.severity {
19✔
NEW
86
                pomsky::diagnose::Severity::Warning if !warnings.is_enabled(d.kind) => None,
×
87
                _ => Some(Diagnostic::from(d, source_code, json)),
6✔
88
            })
19✔
89
            .collect()
19✔
90
    }
19✔
91

92
    pub(crate) fn output(self, json: bool, new_line: bool, in_test_suite: bool, source_code: &str) {
19✔
93
        let success = self.success;
19✔
94
        if json {
19✔
95
            match serde_json::to_string(&self) {
2✔
96
                Ok(string) => println!("{string}"),
2✔
NEW
97
                Err(e) => eprintln!("{e}"),
×
98
            }
99
        } else {
100
            if in_test_suite {
17✔
NEW
101
                if success {
×
NEW
102
                    efprintln!(G!"ok");
×
NEW
103
                } else {
×
NEW
104
                    efprintln!(R!"failed");
×
NEW
105
                }
×
106
            }
17✔
107
            self.output_human_readable(new_line, in_test_suite, Some(source_code));
17✔
108
        }
109
        if !success && !in_test_suite {
19✔
110
            std::process::exit(1);
3✔
111
        }
16✔
112
    }
16✔
113

114
    fn output_human_readable(
17✔
115
        mut self,
17✔
116
        new_line: bool,
17✔
117
        in_test_suite: bool,
17✔
118
        source_code: Option<&str>,
17✔
119
    ) {
17✔
120
        if self.output.is_none() {
17✔
121
            self.diagnostics.retain(|d| d.severity == Severity::Error);
5✔
122
        }
15✔
123
        let initial_len = self.diagnostics.len();
17✔
124
        let error_len = self.diagnostics.iter().filter(|d| d.severity == Severity::Error).count();
17✔
125
        let warning_len = self.diagnostics.len() - error_len;
17✔
126

17✔
127
        if self.diagnostics.len() > 8 {
17✔
NEW
128
            self.diagnostics.drain(8..);
×
129
        }
17✔
130

131
        for diag in &self.diagnostics {
22✔
132
            diag.print_human_readable(source_code);
5✔
133
        }
5✔
134

135
        if !self.diagnostics.is_empty() {
17✔
136
            if initial_len > self.diagnostics.len() {
2✔
NEW
137
                efprintln!(C!"note" ": " {&(initial_len - self.diagnostics.len()).to_string()} " diagnostic(s) were omitted");
×
138
            }
2✔
139
            if initial_len > 3 && error_len == 0 {
2✔
NEW
140
                let warning_len = warning_len.to_string();
×
NEW
141
                efprintln!(Y!"warning" ": pomsky generated " {&warning_len} " warnings");
×
142
            }
2✔
143
        }
15✔
144

145
        if let Some(compiled) = &self.output {
17✔
146
            if in_test_suite {
15✔
NEW
147
                // do nothing
×
148
            } else if new_line {
15✔
149
                println!("{compiled}");
12✔
150
            } else {
12✔
151
                use std::io::Write;
3✔
152

3✔
153
                print!("{compiled}");
3✔
154
                std::io::stdout().flush().unwrap();
3✔
155
            }
3✔
156
        }
2✔
157
    }
17✔
158
}
159

160
#[derive(Debug, PartialEq, Serialize, Deserialize)]
1✔
161
pub struct Diagnostic {
162
    /// "error" | "warning"
163
    pub severity: Severity,
164
    /// See [`DiagnosticKind`](pomsky::diagnose::DiagnosticKind)
165
    ///
166
    /// Currently "syntax" | "resolve" | "compat" | "unsupported" | "deprecated"
167
    /// | "limits" | "other"
168
    pub kind: Kind,
169
    /// See [`DiagnosticCode`](pomsky::diagnose::DiagnosticCode)
170
    #[serde(with = "serde_code", skip_serializing_if = "Option::is_none")]
171
    pub code: Option<DiagnosticCode>,
172
    /// List of locations that should be underlined
173
    ///
174
    /// Currently guaranteed to contain exactly 1 span
175
    pub spans: Vec<Span>,
176
    /// Error/warning message
177
    pub description: String,
178
    /// Help text
179
    ///
180
    /// Currently guaranteed to contain at most 1 string
181
    pub help: Vec<String>,
182
    /// Automatically applicable fixes
183
    ///
184
    /// Currently unused and guaranteed to be empty
185
    pub fixes: Vec<QuickFix>,
186
    /// Visual representation of the diagnostic as displayed in the CLI
187
    pub visual: String,
188
}
189

190
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
191
#[serde(rename_all = "lowercase")]
192
pub enum Severity {
193
    Error,
194
    Warning,
195
}
196

197
impl From<pomsky::diagnose::Severity> for Severity {
198
    fn from(value: pomsky::diagnose::Severity) -> Self {
6✔
199
        match value {
6✔
200
            pomsky::diagnose::Severity::Error => Severity::Error,
6✔
201
            pomsky::diagnose::Severity::Warning => Severity::Warning,
×
202
        }
203
    }
6✔
204
}
205

206
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
207
#[serde(rename_all = "lowercase")]
208
pub enum Kind {
209
    Syntax,
210
    Resolve,
211
    Compat,
212
    Unsupported,
213
    Deprecated,
214
    Limits,
215
    Test,
216
    Other,
217
}
218

219
impl Kind {
220
    pub fn as_str(&self) -> &'static str {
5✔
221
        match self {
5✔
222
            Kind::Syntax => "syntax",
1✔
NEW
223
            Kind::Resolve => "resolve",
×
NEW
224
            Kind::Compat => "compat",
×
NEW
225
            Kind::Unsupported => "unsupported",
×
NEW
226
            Kind::Deprecated => "deprecated",
×
NEW
227
            Kind::Limits => "limits",
×
228
            Kind::Test => "test",
4✔
NEW
229
            Kind::Other => "other",
×
230
        }
231
    }
5✔
232
}
233

234
impl From<DiagnosticKind> for Kind {
235
    fn from(value: DiagnosticKind) -> Self {
6✔
236
        match value {
6✔
237
            DiagnosticKind::Syntax => Kind::Syntax,
2✔
238
            DiagnosticKind::Resolve => Kind::Resolve,
×
239
            DiagnosticKind::Compat => Kind::Compat,
×
240
            DiagnosticKind::Unsupported => Kind::Unsupported,
×
241
            DiagnosticKind::Deprecated => Kind::Deprecated,
×
242
            DiagnosticKind::Limits => Kind::Limits,
×
243
            DiagnosticKind::Test => Kind::Test,
4✔
244
            DiagnosticKind::Other => Kind::Other,
×
245
            _ => panic!("unknown diagnostic kind"),
×
246
        }
247
    }
6✔
248
}
249

250
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
251
pub struct Timings {
252
    /// time of all performed compilation steps in microseconds
253
    pub all: u128,
254
    pub tests: u128,
255
}
256

257
impl Timings {
258
    pub fn from_micros(all: u128, tests: u128) -> Self {
19✔
259
        Timings { all, tests }
19✔
260
    }
19✔
261
}
262

263
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
264
pub struct Span {
265
    /// Start byte offset, counting from zero, assuming UTF-8 encoding.
266
    ///
267
    /// Guaranteed to be non-negative.
268
    pub start: usize,
269
    /// End byte offset, non-inclusive, counting from zero, assuming UTF-8
270
    /// encoding.
271
    ///
272
    /// Guaranteed to be at least `start`.
273
    pub end: usize,
274
    /// Additional details only relevant to this specific span
275
    ///
276
    /// Currently unused, guaranteed to be absent
277
    #[serde(skip_serializing_if = "Option::is_none")]
278
    pub label: Option<String>,
279
}
280

281
impl From<std::ops::Range<usize>> for Span {
282
    fn from(value: std::ops::Range<usize>) -> Self {
6✔
283
        Span { start: value.start, end: value.end, label: None }
6✔
284
    }
6✔
285
}
286

287
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
288
pub struct QuickFix {
289
    /// Short description what this quick fix does
290
    pub description: String,
291
    /// List of changes to fix this diagnostic.
292
    ///
293
    /// Guaranteed to be in source order and non-overlapping (e.g. `1-4`,
294
    /// `7-12`, `14-15`, `16-16`)
295
    pub replacements: Vec<Replacement>,
296
}
297

298
#[derive(Debug, PartialEq, Serialize, Deserialize)]
×
299
pub struct Replacement {
300
    /// Start byte offset, counting from zero, assuming UTF-8 encoding.
301
    ///
302
    /// Guaranteed to be non-negative.
303
    pub start: usize,
304
    /// End byte offset, non-inclusive, counting from zero, assuming UTF-8
305
    /// encoding
306
    ///
307
    /// Guaranteed to be at least `start`.
308
    pub end: usize,
309
    /// Text to replace this part of code with
310
    pub insert: String,
311
}
312

313
impl Diagnostic {
314
    pub(crate) fn from(
6✔
315
        value: pomsky::diagnose::Diagnostic,
6✔
316
        source_code: Option<&str>,
6✔
317
        json: bool,
6✔
318
    ) -> Self {
6✔
319
        let kind = value.kind.to_string();
6✔
320
        let severity: &str = value.severity.into();
6✔
321

322
        let visual = if json {
6✔
323
            let display = value.display_ascii(source_code);
1✔
324
            let visual = match value.code {
1✔
325
                Some(code) => format!("{severity} {code}{kind}:{display}"),
1✔
NEW
326
                None => format!("{severity}{kind}:{display}"),
×
327
            };
328
            drop(display);
1✔
329
            visual
1✔
330
        } else {
331
            // unused
332
            String::new()
5✔
333
        };
334

335
        Diagnostic {
6✔
336
            severity: value.severity.into(),
6✔
337
            kind: value.kind.into(),
6✔
338
            code: value.code,
6✔
339
            spans: value.span.range().into_iter().map(From::from).collect(),
6✔
340
            description: value.msg,
6✔
341
            help: value.help.into_iter().collect(),
6✔
342
            fixes: vec![],
6✔
343
            visual,
6✔
344
        }
6✔
345
    }
6✔
346

347
    fn print_human_readable(&self, source_code: Option<&str>) {
5✔
348
        let kind = self.kind.as_str();
5✔
349
        let display = self.miette_display(source_code).to_string();
5✔
350
        if let Some(code) = self.code {
5✔
351
            let code = code.to_string();
5✔
352
            match self.severity {
5✔
353
                Severity::Error => efprint!(R!"error " R!{&code} "(" {&kind} "):" {&display}),
5✔
354
                Severity::Warning => {
NEW
355
                    efprint!(Y!"warning " Y!{&code} "(" {&kind} "):" {&display})
×
356
                }
357
            }
358
        } else {
NEW
359
            match self.severity {
×
NEW
360
                Severity::Error => efprint!(R!"error" "(" {&kind} "):" {&display}),
×
NEW
361
                Severity::Warning => efprint!(Y!"warning" "(" {&kind} "):" {&display}),
×
362
            }
363
        }
364
    }
5✔
365

366
    fn miette_display<'a>(&'a self, source_code: Option<&'a str>) -> impl std::fmt::Display + 'a {
5✔
367
        use miette::ReportHandler;
368
        use std::fmt;
369

370
        #[derive(Debug)]
371
        struct MietteDiagnostic<'a> {
372
            diagnostic: &'a Diagnostic,
373
            source_code: Option<&'a str>,
374
        }
375

376
        impl fmt::Display for MietteDiagnostic<'_> {
377
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5✔
378
                self.diagnostic.description.fmt(f)
5✔
379
            }
5✔
380
        }
381

382
        impl std::error::Error for MietteDiagnostic<'_> {}
383

384
        impl miette::Diagnostic for MietteDiagnostic<'_> {
385
            fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
5✔
386
                if self.diagnostic.help.is_empty() {
5✔
387
                    None
3✔
388
                } else {
389
                    let help = self.diagnostic.help.join("\n");
2✔
390
                    Some(Box::new(help))
2✔
391
                }
392
            }
5✔
393

394
            fn source_code(&self) -> Option<&dyn miette::SourceCode> {
15✔
395
                self.source_code.as_ref().map(|s| s as &dyn miette::SourceCode)
15✔
396
            }
15✔
397

398
            fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
5✔
399
                if let Some(Span { start, end, label }) = self.diagnostic.spans.first() {
5✔
400
                    let label = label.as_deref().unwrap_or(match self.diagnostic.severity {
5✔
401
                        Severity::Error => "error occurred here",
5✔
NEW
402
                        Severity::Warning => "warning originated here",
×
403
                    });
404
                    Some(Box::new(std::iter::once(miette::LabeledSpan::new(
5✔
405
                        Some(label.into()),
5✔
406
                        *start,
5✔
407
                        end - start,
5✔
408
                    ))))
5✔
409
                } else {
NEW
410
                    None
×
411
                }
412
            }
5✔
413

414
            fn severity(&self) -> Option<miette::Severity> {
10✔
415
                Some(match self.diagnostic.severity {
10✔
416
                    Severity::Error => miette::Severity::Error,
10✔
NEW
417
                    Severity::Warning => miette::Severity::Warning,
×
418
                })
419
            }
10✔
420
        }
421

422
        struct Handler<'a>(MietteDiagnostic<'a>);
423

424
        impl fmt::Display for Handler<'_> {
425
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
5✔
426
                miette::MietteHandler::default().debug(&self.0, f)
5✔
427
            }
5✔
428
        }
429

430
        Handler(MietteDiagnostic { diagnostic: self, source_code })
5✔
431
    }
5✔
432
}
433

434
impl fmt::Display for CompilationResult {
435
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
436
        write!(f, "{self:?}")
×
437
    }
×
438
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc