clang_format/lib.rs
1// SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3// SPDX-FileContributor: Gerhard de Clercq <gerhard.declercq@kdab.com>
4//
5// SPDX-License-Identifier: MIT OR Apache-2.0
6
7#![deny(missing_docs)]
8
9//! A basic clang-format Rust wrapper.
10//!
11//! This allows for formatting a given input using `clang-format` from the system.
12
13use std::env;
14use std::io::Write;
15use std::process::{Command, Stdio};
16use thiserror::Error;
17
18/// Describes the style to pass to clang-format
19///
20/// This list is created from
21/// <https://clang.llvm.org/docs/ClangFormatStyleOptions.html#basedonstyle>
22#[derive(Debug, PartialEq)]
23#[non_exhaustive]
24pub enum ClangFormatStyle {
25 /// A style complying with [Chromium’s style guide](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/styleguide/styleguide.md)
26 Chromium,
27 /// Use the default clang-format style
28 Default,
29 /// clang-format will try to find the .clang-format file located in the closest parent directory of the current directory.
30 File,
31 /// A style complying with the [GNU coding standards](https://www.gnu.org/prep/standards/standards.html)
32 ///
33 /// Since clang-format 11
34 GNU,
35 /// A style complying with [Google’s C++ style guide](https://google.github.io/styleguide/cppguide.html)
36 Google,
37 /// A style complying with the [LLVM coding standards](https://llvm.org/docs/CodingStandards.html)
38 Llvm,
39 /// A style complying with [Microsoft’s style guide](https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference)
40 ///
41 /// Since clang-format 9
42 Microsoft,
43 /// A style complying with [Mozilla’s style guide](https://firefox-source-docs.mozilla.org/code-quality/coding-style/index.html)
44 Mozilla,
45 /// A style complying with [WebKit’s style guide](https://www.webkit.org/coding/coding-style.html)
46 WebKit,
47 /// Specify a custom input to the `--style` argument of clang-format
48 ///
49 /// # Example
50 ///
51 /// ```
52 /// # use clang_format::{clang_format_with_style, ClangFormatStyle};
53 /// # fn main() {
54 /// # let input = r#"
55 /// # struct Test {
56 /// # bool field;
57 /// # };
58 /// # "#;
59 /// let style = ClangFormatStyle::Custom("{ BasedOnStyle: Mozilla, IndentWidth: 8 }".to_string());
60 /// # let output = clang_format_with_style(input, &style);
61 /// # assert!(output.is_ok());
62 /// # assert_eq!(output.unwrap(), "\nstruct Test\n{\n bool field;\n};\n");
63 /// # }
64 /// ```
65 Custom(String),
66}
67
68impl ClangFormatStyle {
69 /// Converts the enum ClangFormatStyle to a string that clang-format expects
70 fn as_str(&self) -> &str {
71 match self {
72 Self::Chromium => "Chromium",
73 // Will use clang-format default options
74 Self::Default => "{}",
75 // Will look in parent directories for a .clang-format file
76 Self::File => "file",
77 Self::GNU => "GNU",
78 Self::Google => "Google",
79 Self::Llvm => "LLVM",
80 Self::Microsoft => "Microsoft",
81 Self::Mozilla => "Mozilla",
82 Self::WebKit => "WebKit",
83 // Custom style arguments
84 Self::Custom(custom) => custom.as_str(),
85 }
86 }
87}
88
89/// Describes which error spawning clang-format failed with
90#[derive(Error, Debug)]
91enum ClangFormatError {
92 #[error(transparent)]
93 Io(#[from] std::io::Error),
94 #[error(transparent)]
95 FromUtf8Error(#[from] std::string::FromUtf8Error),
96 // TODO: use ExitStatusError once it is a stable feature
97 // https://doc.rust-lang.org/stable/std/process/struct.ExitStatusError.html
98 // https://github.com/rust-lang/rust/issues/84908
99 #[error("Clang format process exited with a non-zero status")]
100 NonZeroExitStatus,
101}
102
103/// Execute clang-format with the given input, using the given style, and collect the output
104///
105/// # Example
106///
107/// ```
108/// # use clang_format::{clang_format_with_style, ClangFormatStyle};
109/// # fn main() {
110/// let input = r#"
111/// struct Test {
112///
113/// };
114/// "#;
115/// let output = clang_format_with_style(input, &ClangFormatStyle::Mozilla);
116/// assert!(output.is_ok());
117/// assert_eq!(output.unwrap(), "\nstruct Test\n{};\n");
118/// # }
119/// ```
120pub fn clang_format_with_style(
121 input: &str,
122 style: &ClangFormatStyle,
123) -> Result<String, impl std::error::Error> {
124 // Create and try to spawn the command with the specified style
125 let clang_binary = env::var("CLANG_FORMAT_BINARY").unwrap_or("clang-format".to_string());
126 let mut child = Command::new(clang_binary.as_str())
127 .arg(format!("--style={}", style.as_str()))
128 .stdin(Stdio::piped())
129 .stdout(Stdio::piped())
130 .spawn()?;
131
132 // Write the input to stdin
133 //
134 // Note we place inside a scope to ensure that stdin is closed
135 {
136 let mut stdin = child.stdin.take().expect("no stdin handle");
137 write!(stdin, "{}", input)?;
138 }
139
140 // Wait for the output and parse it
141 let output = child.wait_with_output()?;
142 // TODO: use exit_ok() once it is a stable feature
143 // https://doc.rust-lang.org/stable/std/process/struct.ExitStatus.html#method.exit_ok
144 // https://github.com/rust-lang/rust/issues/84908
145 if output.status.success() {
146 Ok(String::from_utf8(output.stdout)?)
147 } else {
148 Err(ClangFormatError::NonZeroExitStatus)
149 }
150}
151
152/// Execute clang-format with the given input and collect the output
153///
154/// Note that this uses `ClangFormatStyle::Default` as the style.
155///
156/// # Example
157///
158/// ```
159/// # use clang_format::clang_format;
160/// # fn main() {
161/// let input = r#"
162/// struct Test {
163///
164/// };
165/// "#;
166/// let output = clang_format(input);
167/// assert!(output.is_ok());
168/// assert_eq!(output.unwrap(), "\nstruct Test {};\n");
169/// # }
170/// ```
171pub fn clang_format(input: &str) -> Result<String, impl std::error::Error> {
172 clang_format_with_style(input, &ClangFormatStyle::Default)
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn format_default() {
181 let input = r#"
182 struct Test {
183
184 };
185 "#;
186 let output = clang_format_with_style(input, &ClangFormatStyle::Default);
187 assert!(output.is_ok());
188 assert_eq!(output.unwrap(), "\nstruct Test {};\n");
189 }
190
191 #[test]
192 fn format_mozilla() {
193 let input = r#"
194 struct Test {
195
196 };
197 "#;
198 let output = clang_format_with_style(input, &ClangFormatStyle::Mozilla);
199 assert!(output.is_ok());
200 assert_eq!(output.unwrap(), "\nstruct Test\n{};\n");
201 }
202
203 #[test]
204 fn format_custom() {
205 let input = r#"
206 struct Test {
207 bool field;
208 };
209 "#;
210
211 // Test multiple lines and single quotes
212 {
213 let output = clang_format_with_style(
214 input,
215 &ClangFormatStyle::Custom(
216 "{BasedOnStyle: 'Mozilla',
217 IndentWidth: 8}"
218 .to_string(),
219 ),
220 );
221 assert!(output.is_ok());
222 assert_eq!(
223 output.unwrap(),
224 "\nstruct Test\n{\n bool field;\n};\n"
225 );
226 }
227
228 // Test single line and double quotes
229 {
230 let output = clang_format_with_style(
231 input,
232 &ClangFormatStyle::Custom(
233 "{ BasedOnStyle: \"Mozilla\", IndentWidth: 4 }".to_string(),
234 ),
235 );
236 assert!(output.is_ok());
237 assert_eq!(output.unwrap(), "\nstruct Test\n{\n bool field;\n};\n");
238 }
239 }
240}