taplo_cli/
printing.rs

1use crate::Taplo;
2use codespan_reporting::{
3    diagnostic::{Diagnostic, Label},
4    files::SimpleFile,
5    term::{
6        self,
7        termcolor::{Ansi, NoColor},
8    },
9};
10use itertools::Itertools;
11use std::ops::Range;
12use taplo::{dom, parser, rowan::TextRange};
13use taplo_common::{environment::Environment, schema::NodeValidationError};
14use tokio::io::AsyncWriteExt;
15
16impl<E: Environment> Taplo<E> {
17    pub(crate) async fn print_parse_errors(
18        &self,
19        file: &SimpleFile<&str, &str>,
20        errors: &[parser::Error],
21    ) -> Result<(), anyhow::Error> {
22        let mut out_diag = Vec::<u8>::new();
23
24        let config = codespan_reporting::term::Config::default();
25
26        for error in errors.iter().unique_by(|e| e.range) {
27            let diag = Diagnostic::error()
28                .with_message("invalid TOML")
29                .with_labels(Vec::from([
30                    Label::primary((), std_range(error.range)).with_message(&error.message)
31                ]));
32
33            if self.colors {
34                term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
35            } else {
36                term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
37            }
38        }
39
40        let mut stderr = self.env.stderr();
41
42        stderr.write_all(&out_diag).await?;
43        stderr.flush().await?;
44
45        Ok(())
46    }
47
48    pub(crate) async fn print_semantic_errors(
49        &self,
50        file: &SimpleFile<&str, &str>,
51        errors: impl Iterator<Item = dom::Error>,
52    ) -> Result<(), anyhow::Error> {
53        let mut out_diag = Vec::<u8>::new();
54
55        let config = codespan_reporting::term::Config::default();
56
57        for error in errors {
58            let diag = match &error {
59                dom::Error::ConflictingKeys { key, other } => Diagnostic::error()
60                    .with_message(error.to_string())
61                    .with_labels(Vec::from([
62                        Label::primary((), std_range(key.text_ranges().next().unwrap()))
63                            .with_message("duplicate key"),
64                        Label::secondary((), std_range(other.text_ranges().next().unwrap()))
65                            .with_message("duplicate found here"),
66                    ])),
67                dom::Error::ExpectedArrayOfTables {
68                    not_array_of_tables,
69                    required_by,
70                } => Diagnostic::error()
71                    .with_message(error.to_string())
72                    .with_labels(Vec::from([
73                        Label::primary(
74                            (),
75                            std_range(not_array_of_tables.text_ranges().next().unwrap()),
76                        )
77                        .with_message("expected array of tables"),
78                        Label::secondary((), std_range(required_by.text_ranges().next().unwrap()))
79                            .with_message("required by this key"),
80                    ])),
81                dom::Error::ExpectedTable {
82                    not_table,
83                    required_by,
84                } => Diagnostic::error()
85                    .with_message(error.to_string())
86                    .with_labels(Vec::from([
87                        Label::primary((), std_range(not_table.text_ranges().next().unwrap()))
88                            .with_message("expected table"),
89                        Label::secondary((), std_range(required_by.text_ranges().next().unwrap()))
90                            .with_message("required by this key"),
91                    ])),
92                dom::Error::InvalidEscapeSequence { string } => Diagnostic::error()
93                    .with_message(error.to_string())
94                    .with_labels(Vec::from([Label::primary(
95                        (),
96                        std_range(string.text_range()),
97                    )
98                    .with_message("the string contains invalid escape sequences")])),
99                _ => {
100                    unreachable!("this is a bug")
101                }
102            };
103
104            if self.colors {
105                term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
106            } else {
107                term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
108            }
109        }
110        let mut stderr = self.env.stderr();
111        stderr.write_all(&out_diag).await?;
112        stderr.flush().await?;
113        Ok(())
114    }
115
116    pub(crate) async fn print_schema_errors(
117        &self,
118        file: &SimpleFile<&str, &str>,
119        errors: &[NodeValidationError],
120    ) -> Result<(), anyhow::Error> {
121        let config = codespan_reporting::term::Config::default();
122
123        let mut out_diag = Vec::<u8>::new();
124        for err in errors {
125            let msg = err.error.to_string();
126            for text_range in err.node.text_ranges() {
127                let diag = Diagnostic::error()
128                    .with_message(err.error.to_string())
129                    .with_labels(Vec::from([
130                        Label::primary((), std_range(text_range)).with_message(&msg)
131                    ]));
132
133                if self.colors {
134                    term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
135                } else {
136                    term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
137                };
138            }
139        }
140        let mut stderr = self.env.stderr();
141        stderr.write_all(&out_diag).await?;
142        stderr.flush().await?;
143
144        Ok(())
145    }
146}
147
148fn std_range(range: TextRange) -> Range<usize> {
149    let start: usize = u32::from(range.start()) as _;
150    let end: usize = u32::from(range.end()) as _;
151    start..end
152}