Skip to content

Commit 1756dd5

Browse files
committed
lsp: Report the number of errors encountered during format
... in the return code. So 0 is "all is well", 1 - x is the number of slint errors in al files processed. 255 is a special value that gets reported for IO errors in the lsp code itself. Fixes: #8229
1 parent dbf8c9e commit 1756dd5

File tree

4 files changed

+155
-76
lines changed

4 files changed

+155
-76
lines changed

tools/lsp/fmt/fmt.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ fn format_member_access(
12971297
#[cfg(test)]
12981298
mod tests {
12991299
use super::*;
1300-
use crate::fmt::writer::FileWriter;
1300+
use crate::fmt::writer::StringWriter;
13011301
use i_slint_compiler::diagnostics::BuildDiagnostics;
13021302

13031303
// FIXME more descriptive errors when an assertion fails
@@ -1311,9 +1311,9 @@ mod tests {
13111311
);
13121312
// Turn the syntax node into a document
13131313
let doc = syntax_nodes::Document::new(syntax_node).unwrap();
1314-
let mut file = Vec::new();
1315-
format_document(doc, &mut FileWriter { file: &mut file }).unwrap();
1316-
assert_eq!(String::from_utf8(file).unwrap(), formatted);
1314+
let mut writer = StringWriter { content: String::new() };
1315+
format_document(doc, &mut writer).unwrap();
1316+
assert_eq!(writer.content, formatted);
13171317
}
13181318

13191319
#[test]

tools/lsp/fmt/tool.rs

Lines changed: 127 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -22,111 +22,180 @@ use std::path::Path;
2222

2323
use super::{fmt, writer};
2424

25-
pub fn run(files: Vec<std::path::PathBuf>, inplace: bool) -> std::io::Result<()> {
25+
pub fn run(files: Vec<std::path::PathBuf>, inplace: bool) -> std::io::Result<usize> {
26+
let mut changed_or_error_files = 0;
27+
2628
for path in files {
27-
let source = std::fs::read_to_string(&path)?;
29+
let source = std::fs::read(&path)?;
30+
31+
match process_file(source.clone(), &path) {
32+
Ok(result) if result == source => {}
33+
Ok(result) => {
34+
changed_or_error_files += 1;
2835

29-
if inplace {
30-
let file = BufWriter::new(std::fs::File::create(&path)?);
31-
process_file(source, path, file)?
32-
} else {
33-
process_file(source, path, std::io::stdout())?
36+
if inplace {
37+
let mut file = BufWriter::new(std::fs::File::create(&path)?);
38+
file.write_all(&result)?;
39+
} else {
40+
std::io::stdout().write_all(&result)?;
41+
}
42+
}
43+
Err(e) => {
44+
println!("Error in {path:?}: {e}");
45+
changed_or_error_files += 1;
46+
}
3447
}
3548
}
36-
Ok(())
49+
Ok(changed_or_error_files)
3750
}
3851

3952
/// FIXME! this is duplicated with the updater
40-
fn process_rust_file(source: String, mut file: impl Write) -> std::io::Result<()> {
53+
fn process_rust_file(source: Vec<u8>) -> std::io::Result<Vec<u8>> {
54+
let mut result = String::new();
55+
let source =
56+
String::from_utf8(source).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
4157
let mut last = 0;
4258
for range in i_slint_compiler::lexer::locate_slint_macro(&source) {
43-
file.write_all(source[last..=range.start].as_bytes())?;
59+
result.push_str(&source[last..=range.start]);
4460
last = range.end;
4561
let code = &source[range];
4662

4763
let mut diag = BuildDiagnostics::default();
4864
let syntax_node = i_slint_compiler::parser::parse(code.to_owned(), None, &mut diag);
4965
let len = syntax_node.text_range().end().into();
50-
visit_node(syntax_node, &mut file)?;
66+
result.push_str(&visit_node(syntax_node)?);
67+
5168
if diag.has_errors() {
52-
file.write_all(&code.as_bytes()[len..])?;
69+
result.push_str(&code[len..]);
5370
diag.print();
71+
return Err(std::io::Error::new(
72+
std::io::ErrorKind::Other,
73+
String::from("Failed to parse file"),
74+
));
75+
}
76+
}
77+
result.push_str(&source[last..]);
78+
Ok(result.as_bytes().to_vec())
79+
}
80+
81+
fn find_slint_code_in_markdown(content: &[u8]) -> Option<(usize, usize)> {
82+
const CODE_FENCE_START: &[u8] = b"```slint\n";
83+
const CODE_FENCE_END: &[u8] = b"```\n";
84+
85+
let mut it = content.iter().enumerate();
86+
87+
let mut fence_offset = 0;
88+
89+
let mut code_start = usize::MAX;
90+
let mut code_end = usize::MAX;
91+
92+
#[allow(clippy::while_let_on_iterator)]
93+
while let Some((idx, b)) = it.next() {
94+
if *b == CODE_FENCE_START[fence_offset] {
95+
fence_offset += 1;
96+
}
97+
98+
if fence_offset == CODE_FENCE_START.len() {
99+
code_start = idx + 1;
100+
break;
101+
}
102+
}
103+
104+
fence_offset = 0;
105+
let mut possible_end_offset = 0;
106+
107+
#[allow(clippy::while_let_on_iterator)]
108+
while let Some((idx, b)) = it.next() {
109+
if *b == CODE_FENCE_END[fence_offset] {
110+
if fence_offset == 0 {
111+
possible_end_offset = idx - 1;
112+
}
113+
fence_offset += 1;
114+
}
115+
116+
if fence_offset == CODE_FENCE_END.len() {
117+
code_end = possible_end_offset;
118+
break;
54119
}
55120
}
56-
file.write_all(source[last..].as_bytes())
121+
122+
if code_end != usize::MAX {
123+
Some((code_start, code_end))
124+
} else {
125+
None
126+
}
57127
}
58128

59129
/// FIXME! this is duplicated with the updater
60-
fn process_markdown_file(source: String, mut file: impl Write) -> std::io::Result<()> {
130+
fn process_markdown_file(source: Vec<u8>) -> std::io::Result<Vec<u8>> {
131+
let mut result = Vec::new();
132+
61133
let mut source_slice = &source[..];
62-
const CODE_FENCE_START: &str = "```slint\n";
63-
const CODE_FENCE_END: &str = "```\n";
64-
'l: while let Some(code_start) =
65-
source_slice.find(CODE_FENCE_START).map(|idx| idx + CODE_FENCE_START.len())
66-
{
67-
let code_end = if let Some(code_end) = source_slice[code_start..].find(CODE_FENCE_END) {
68-
code_end
69-
} else {
70-
break 'l;
71-
};
72-
file.write_all(source_slice[..=code_start - 1].as_bytes())?;
73-
source_slice = &source_slice[code_start..];
74-
let code = &source_slice[..code_end];
134+
while let Some((code_start, code_end)) = find_slint_code_in_markdown(source_slice) {
135+
result.extend(&source_slice[..code_start]);
136+
let code = Vec::from(&source_slice[code_start..code_end]);
137+
let code = String::from_utf8(code)
138+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
75139
source_slice = &source_slice[code_end..];
76140

77141
let mut diag = BuildDiagnostics::default();
78-
let syntax_node = i_slint_compiler::parser::parse(code.to_owned(), None, &mut diag);
79-
let len = syntax_node.text_range().end().into();
80-
visit_node(syntax_node, &mut file)?;
142+
let syntax_node = i_slint_compiler::parser::parse(code, None, &mut diag);
143+
144+
result.extend(visit_node(syntax_node)?.as_bytes());
145+
81146
if diag.has_errors() {
82-
file.write_all(&code.as_bytes()[len..])?;
83147
diag.print();
148+
return Err(std::io::Error::new(
149+
std::io::ErrorKind::Other,
150+
String::from("Failed to parse file"),
151+
));
84152
}
85153
}
86-
file.write_all(source_slice.as_bytes())
154+
result.extend(source_slice);
155+
Ok(result)
87156
}
88157

89-
fn process_slint_file(
90-
source: String,
91-
path: std::path::PathBuf,
92-
mut file: impl Write,
93-
) -> std::io::Result<()> {
158+
fn process_slint_file(source: Vec<u8>, path: &std::path::Path) -> std::io::Result<Vec<u8>> {
94159
let mut diag = BuildDiagnostics::default();
95-
let syntax_node = i_slint_compiler::parser::parse(source.clone(), Some(&path), &mut diag);
96-
let len = syntax_node.node.text_range().end().into();
97-
visit_node(syntax_node, &mut file)?;
160+
let source =
161+
String::from_utf8(source).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
162+
163+
let syntax_node = i_slint_compiler::parser::parse(source, Some(path), &mut diag);
164+
165+
let result = visit_node(syntax_node)?.as_bytes().to_vec();
166+
98167
if diag.has_errors() {
99-
file.write_all(&source.as_bytes()[len..])?;
100168
diag.print();
169+
return Err(std::io::Error::new(
170+
std::io::ErrorKind::Other,
171+
String::from("Failed to parse file"),
172+
));
101173
}
102-
Ok(())
174+
Ok(result)
103175
}
104176

105-
fn process_file(
106-
source: String,
107-
path: std::path::PathBuf,
108-
mut file: impl Write,
109-
) -> std::io::Result<()> {
177+
fn process_file(source: Vec<u8>, path: &std::path::Path) -> std::io::Result<Vec<u8>> {
110178
match path.extension() {
111-
Some(ext) if ext == "rs" => process_rust_file(source, file),
112-
Some(ext) if ext == "md" => process_markdown_file(source, file),
179+
Some(ext) if ext == "rs" => process_rust_file(source),
180+
Some(ext) if ext == "md" => process_markdown_file(source),
113181
// Formatting .60 files because of backwards compatibility (project was recently renamed)
114-
Some(ext) if ext == "slint" || ext == ".60" => process_slint_file(source, path, file),
182+
Some(ext) if ext == "slint" || ext == ".60" => process_slint_file(source, path),
115183
_ => {
116-
// This allows usage like `cat x.slint | slint-lsp format /dev/stdin`
117-
if path.as_path() == Path::new("/dev/stdin") {
118-
return process_slint_file(source, path, file);
184+
// This allows usage like `cat x.slint | slint-lsp format -`
185+
if path == Path::new("/dev/stdin") || path == Path::new("-") {
186+
return process_slint_file(source, path);
119187
}
120-
// With other file types, we just output them in their original form.
121-
file.write_all(source.as_bytes())
188+
189+
Ok(source.to_vec())
122190
}
123191
}
124192
}
125193

126-
fn visit_node(node: SyntaxNode, file: &mut impl Write) -> std::io::Result<()> {
194+
fn visit_node(node: SyntaxNode) -> std::io::Result<String> {
127195
if let Some(doc) = syntax_nodes::Document::new(node) {
128-
let mut writer = writer::FileWriter { file };
129-
fmt::format_document(doc, &mut writer)
196+
let mut writer = writer::StringWriter::default();
197+
fmt::format_document(doc, &mut writer)?;
198+
Ok(writer.finalize())
130199
} else {
131200
Err(std::io::Error::new(std::io::ErrorKind::Other, "Not a Document"))
132201
}

tools/lsp/fmt/writer.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
33

44
use i_slint_compiler::parser::SyntaxToken;
5-
use std::io::Write;
65

76
/// The idea is that each token need to go through this, either with no changes,
87
/// or with a new content.
@@ -18,21 +17,31 @@ pub trait TokenWriter {
1817
}
1918

2019
/// Just write the token stream to a file
21-
pub struct FileWriter<'a, W> {
22-
pub file: &'a mut W,
20+
#[derive(Default)]
21+
pub struct StringWriter {
22+
content: String,
2323
}
2424

25-
impl<W: Write> TokenWriter for FileWriter<'_, W> {
25+
impl StringWriter {
26+
pub fn finalize(self) -> String {
27+
self.content
28+
}
29+
}
30+
31+
impl TokenWriter for StringWriter {
2632
fn no_change(&mut self, token: SyntaxToken) -> std::io::Result<()> {
27-
self.file.write_all(token.text().as_bytes())
33+
self.content.push_str(token.text());
34+
Ok(())
2835
}
2936

3037
fn with_new_content(&mut self, _token: SyntaxToken, contents: &str) -> std::io::Result<()> {
31-
self.file.write_all(contents.as_bytes())
38+
self.content.push_str(contents);
39+
Ok(())
3240
}
3341

3442
fn insert_before(&mut self, token: SyntaxToken, contents: &str) -> std::io::Result<()> {
35-
self.file.write_all(contents.as_bytes())?;
36-
self.file.write_all(token.text().as_bytes())
43+
self.content.push_str(contents);
44+
self.content.push_str(token.text());
45+
Ok(())
3746
}
3847
}

tools/lsp/main.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ enum Commands {
9696
struct Format {
9797
#[arg(name = "path to .slint file(s)", action)]
9898
paths: Vec<std::path::PathBuf>,
99-
10099
/// modify the file inline instead of printing to stdout
101100
#[arg(short, long, action)]
102101
inline: bool,
@@ -234,11 +233,13 @@ fn main() {
234233
}
235234

236235
if let Some(Commands::Format(args)) = args.command {
237-
let _ = fmt::tool::run(args.paths, args.inline).map_err(|e| {
238-
eprintln!("{e}");
239-
std::process::exit(1);
240-
});
241-
std::process::exit(0);
236+
match fmt::tool::run(args.paths, args.inline) {
237+
Ok(error_count) => std::process::exit(error_count.try_into().unwrap_or(254)),
238+
Err(e) => {
239+
eprintln!("{e}");
240+
std::process::exit(255);
241+
}
242+
}
242243
}
243244

244245
if let Ok(panic_log_file) = std::env::var("SLINT_LSP_PANIC_LOG") {

0 commit comments

Comments
 (0)