rust_format/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(missing_docs)]
3
4//! A Rust source code formatting crate with a unified interface for string, file, and
5//! [TokenStream](proc_macro2::TokenStream) input. It currently supports
6//! [rustfmt](https://crates.io/crates/rustfmt-nightly) and
7//! [prettyplease](https://crates.io/crates/prettyplease).
8//!
9//! ```
10//! use rust_format::{Formatter, RustFmt};
11//!
12//! let source = r#"fn main() { println!("Hello World!"); }"#;
13//!
14//! let actual = RustFmt::default().format_str(source).unwrap();
15//! let expected = r#"fn main() {
16//!     println!("Hello World!");
17//! }
18//! "#;
19//!
20//! assert_eq!(expected, actual);
21//! ```
22
23#[cfg(feature = "post_process")]
24mod replace;
25
26#[cfg(not(feature = "post_process"))]
27mod replace {
28    use std::borrow::Cow;
29
30    use crate::Error;
31
32    #[inline]
33    pub(crate) fn replace_markers(s: &str, _replace_doc_blocks: bool) -> Result<Cow<str>, Error> {
34        Ok(Cow::Borrowed(s))
35    }
36}
37
38// Trick to test README samples (from: https://github.com/rust-lang/cargo/issues/383#issuecomment-720873790)
39#[cfg(feature = "post_process")]
40#[cfg(feature = "token_stream")]
41#[cfg(doctest)]
42mod test_readme {
43    macro_rules! external_doc_test {
44        ($x:expr) => {
45            #[doc = $x]
46            extern "C" {}
47        };
48    }
49
50    external_doc_test!(include_str!("../README.md"));
51}
52
53use std::borrow::Cow;
54use std::collections::HashMap;
55use std::default::Default;
56use std::ffi::{OsStr, OsString};
57use std::hash::Hash;
58use std::io::{Read, Write};
59use std::path::{Path, PathBuf};
60use std::process::{Command, Stdio};
61use std::{env, fmt, fs, io, string};
62
63const RUST_FMT: &str = "rustfmt";
64const RUST_FMT_KEY: &str = "RUSTFMT";
65
66// *** Marker macros ***
67
68/// A "marker" macro used to mark locations in the source code where blank lines should be inserted.
69/// If no parameter is given, one blank line is assumed, otherwise the integer literal specified
70/// gives the # of blank lines to insert.
71///
72/// It is important to understand this is NOT actually a macro that is executed. In fact, it is just
73/// here for documentation purposes. Instead, this works as a raw set of tokens in the source code
74/// that we match against verbatim. This means it cannot be renamed on import for example, and it MUST be
75/// invoked as `_blank_!(`, then an optional Rust integer literal, and then `);`.
76///
77/// Actually executing this macro has no effect and it is not meant to even be imported.
78#[cfg(feature = "post_process")]
79#[cfg_attr(docsrs, doc(cfg(feature = "post_process")))]
80#[macro_export]
81macro_rules! _blank_ {
82    () => {};
83    ($lit:literal) => {};
84}
85
86/// A "marker" macro used to mark locations in the source code where comments should be inserted.
87/// If no parameter is given, a single blank comment is assumed, otherwise the string literal
88/// specified is broken into lines and those comments will be inserted individually.
89///
90/// It is important to understand this is NOT actually a macro that is executed. In fact, it is just
91/// here for documentation purposes. Instead, this works as a raw set of tokens in the source code
92/// that we match against verbatim. This means it cannot be renamed on import for example, and it MUST be
93/// invoked as `_comment_!(`, then an optional Rust `str` literal (regular or raw, not byte string),
94/// and then `);`.
95///
96/// Actually executing this macro has no effect and it is not meant to even be imported.
97#[cfg(feature = "post_process")]
98#[cfg_attr(docsrs, doc(cfg(feature = "post_process")))]
99#[macro_export]
100macro_rules! _comment_ {
101    () => {};
102    ($lit:literal) => {};
103}
104
105// *** Error ***
106
107/// This error is returned when errors are triggered during the formatting process
108#[derive(Debug)]
109pub enum Error {
110    /// An I/O related error occurred
111    IOError(io::Error),
112    /// The response of formatting was not valid UTF8
113    UTFConversionError(string::FromUtf8Error),
114    /// The source code has bad syntax and could not be formatted
115    BadSourceCode(String),
116}
117
118impl fmt::Display for Error {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Error::IOError(err) => <io::Error as fmt::Display>::fmt(err, f),
122            Error::UTFConversionError(err) => <string::FromUtf8Error as fmt::Display>::fmt(err, f),
123            Error::BadSourceCode(cause) => {
124                f.write_str("An error occurred while formatting the source code: ")?;
125                f.write_str(cause)
126            }
127        }
128    }
129}
130
131impl std::error::Error for Error {}
132
133impl From<io::Error> for Error {
134    #[inline]
135    fn from(err: io::Error) -> Self {
136        Error::IOError(err)
137    }
138}
139
140impl From<string::FromUtf8Error> for Error {
141    #[inline]
142    fn from(err: string::FromUtf8Error) -> Self {
143        Error::UTFConversionError(err)
144    }
145}
146
147#[cfg(feature = "syn")]
148impl From<syn::Error> for Error {
149    #[inline]
150    fn from(err: syn::Error) -> Self {
151        Error::BadSourceCode(err.to_string())
152    }
153}
154
155// *** Edition ***
156
157/// The Rust edition the source code uses
158#[derive(Clone, Copy, Debug)]
159pub enum Edition {
160    /// Rust 2015 edition
161    Rust2015,
162    /// Rust 2018 edition
163    Rust2018,
164    /// Rust 2021 edition
165    Rust2021,
166}
167
168impl Edition {
169    #[inline]
170    fn as_os_str(self) -> &'static OsStr {
171        match self {
172            Edition::Rust2015 => "2015",
173            Edition::Rust2018 => "2018",
174            Edition::Rust2021 => "2021",
175        }
176        .as_ref()
177    }
178}
179
180impl Default for Edition {
181    #[inline]
182    fn default() -> Self {
183        Edition::Rust2021
184    }
185}
186
187// *** Post Processing ***
188
189/// Post format processing options - optionally replace comment/blank markers and doc blocks
190#[derive(Clone, Copy, Debug)]
191pub enum PostProcess {
192    /// No post processing after formatting (default)
193    None,
194
195    /// Replace [`_blank_!`] and [`_comment_!`] markers
196    #[cfg(feature = "post_process")]
197    #[cfg_attr(docsrs, doc(cfg(feature = "post_process")))]
198    ReplaceMarkers,
199
200    /// Replace [`_blank_!`] and [`_comment_!`] markers and  `#[doc = ""]` (with `///`)
201    #[cfg(feature = "post_process")]
202    #[cfg_attr(docsrs, doc(cfg(feature = "post_process")))]
203    ReplaceMarkersAndDocBlocks,
204}
205
206impl PostProcess {
207    /// Returns true if blank and comment markers should be replaced in the formatted source or
208    /// false if they should not be
209    #[inline]
210    pub fn replace_markers(self) -> bool {
211        !matches!(self, PostProcess::None)
212    }
213
214    /// Returns true if doc blocks should be replaced in the formatted source or false if they
215    /// should not be
216    #[cfg(feature = "post_process")]
217    #[inline]
218    pub fn replace_doc_blocks(self) -> bool {
219        matches!(self, PostProcess::ReplaceMarkersAndDocBlocks)
220    }
221
222    /// Returns true if doc blocks should be replaced in the formatted source or false if they
223    /// should not be
224    #[cfg(not(feature = "post_process"))]
225    #[inline]
226    pub fn replace_doc_blocks(self) -> bool {
227        false
228    }
229}
230
231impl Default for PostProcess {
232    #[inline]
233    fn default() -> Self {
234        PostProcess::None
235    }
236}
237
238// *** Config ***
239
240/// The configuration for the formatters. Most of the options are for `rustfmt` only (they are ignored
241/// by [PrettyPlease], but [PostProcess] options are used by both formatters).
242#[derive(Clone, Debug, Default)]
243pub struct Config<K, P, V>
244where
245    K: Eq + Hash + AsRef<OsStr>,
246    P: Into<PathBuf>,
247    V: AsRef<OsStr>,
248{
249    rust_fmt: Option<P>,
250    edition: Edition,
251    post_proc: PostProcess,
252    options: HashMap<K, V>,
253}
254
255impl<'a, 'b> Config<&'a str, &'b str, &'a str> {
256    /// Creates a new blank configuration with `&str` for all type params
257    /// (if you wish to use different types, use [new](Config::new) instead)
258    #[inline]
259    pub fn new_str() -> Self {
260        Self::new()
261    }
262
263    /// Creates a new configuration from the given [HashMap] of options using `&str` for all type params
264    /// (if you wish to use different types, use [from_hash_map](Config::from_hash_map) instead)
265    #[inline]
266    pub fn from_hash_map_str(options: HashMap<&'a str, &'a str>) -> Self {
267        Self::from_hash_map(options)
268    }
269}
270
271impl<K, P, V> Config<K, P, V>
272where
273    K: Eq + Hash + AsRef<OsStr>,
274    P: Into<PathBuf>,
275    V: AsRef<OsStr>,
276{
277    /// Creates a new blank configuration without type parameter assumptions
278    #[inline]
279    pub fn new() -> Self {
280        Self::from_hash_map(HashMap::default())
281    }
282
283    /// Creates a new configuration from the given [HashMap] of options with no type assumptions
284    #[inline]
285    pub fn from_hash_map(options: HashMap<K, V>) -> Self {
286        Self {
287            rust_fmt: None,
288            edition: Edition::Rust2021,
289            post_proc: PostProcess::None,
290            options,
291        }
292    }
293
294    /// Set the path to the `rustfmt` binary to use (`RustFmt` only, ignored by `PrettyPlease`).
295    /// This takes precedence over the `RUSTFMT` environment variable, if specified
296    #[inline]
297    pub fn rust_fmt_path(mut self, path: P) -> Self {
298        self.rust_fmt = Some(path);
299        self
300    }
301
302    /// Set the Rust edition of the source input (`RustFmt` only, ignored by `PrettyPlease`)
303    #[inline]
304    pub fn edition(mut self, edition: Edition) -> Self {
305        self.edition = edition;
306        self
307    }
308
309    /// Set the post processing option after formatting (used by both `RustFmt` and `PrettyPlease`)
310    #[inline]
311    pub fn post_proc(mut self, post_proc: PostProcess) -> Self {
312        self.post_proc = post_proc;
313        self
314    }
315
316    /// Set a key/value pair option (`RustFmt` only, ignored by `PrettyPlease`).
317    /// See [here](https://rust-lang.github.io/rustfmt/) for a list of possible options
318    #[inline]
319    pub fn option(mut self, key: K, value: V) -> Self {
320        self.options.insert(key, value);
321        self
322    }
323}
324
325// *** Misc. format related functions ***
326
327#[inline]
328fn post_process(post_proc: PostProcess, source: String) -> Result<String, Error> {
329    if post_proc.replace_markers() {
330        match replace::replace_markers(&source, post_proc.replace_doc_blocks())? {
331            // No change
332            Cow::Borrowed(_) => Ok(source),
333            // Changed
334            Cow::Owned(source) => Ok(source),
335        }
336    } else {
337        Ok(source)
338    }
339}
340
341#[inline]
342fn file_to_string(path: impl AsRef<Path>) -> Result<String, Error> {
343    // Read our file into a string
344    let mut file = fs::File::open(path.as_ref())?;
345    let len = file.metadata()?.len();
346    let mut source = String::with_capacity(len as usize);
347
348    file.read_to_string(&mut source)?;
349    Ok(source)
350}
351
352#[inline]
353fn string_to_file(path: impl AsRef<Path>, source: &str) -> Result<(), Error> {
354    let mut file = fs::File::create(path)?;
355    file.write_all(source.as_bytes())?;
356    Ok(())
357}
358
359// *** Formatter ***
360
361/// A unified interface to all formatters. It allows for formatting from string, file, or
362/// [TokenStream](proc_macro2::TokenStream)
363pub trait Formatter {
364    /// Format the given string and return the results in another `String`. An error is returned
365    /// if any issues occur during formatting
366    fn format_str(&self, source: impl AsRef<str>) -> Result<String, Error>;
367
368    /// Format the given file specified hte path and overwrite the file with the results. An error
369    /// is returned if any issues occur during formatting
370    fn format_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
371        let source = file_to_string(path.as_ref())?;
372        let result = self.format_str(source)?;
373        string_to_file(path, &result)
374    }
375
376    /// Format the given [TokenStream](proc_macro2::TokenStream) and return the results in a `String`.
377    /// An error is returned if any issues occur during formatting
378    #[cfg(feature = "token_stream")]
379    #[cfg_attr(docsrs, doc(cfg(feature = "token_stream")))]
380    #[inline]
381    fn format_tokens(&self, tokens: proc_macro2::TokenStream) -> Result<String, Error> {
382        self.format_str(tokens.to_string())
383    }
384}
385
386// *** Rust Fmt ***
387
388/// This formatter uses `rustfmt` for formatting source code
389///
390/// An example using a custom configuration:
391/// ```
392/// use rust_format::{Config, Edition, Formatter, RustFmt};
393///
394/// let source = r#"use std::marker; use std::io; mod test; mod impls;"#;
395///
396/// let mut config = Config::new_str()
397///     .edition(Edition::Rust2018)
398///     .option("reorder_imports", "false")
399///     .option("reorder_modules", "false");
400/// let rustfmt = RustFmt::from_config(config);
401///
402/// let actual = rustfmt.format_str(source).unwrap();
403/// let expected = r#"use std::marker;
404/// use std::io;
405/// mod test;
406/// mod impls;
407/// "#;
408///
409/// assert_eq!(expected, actual);
410/// ```
411#[derive(Clone)]
412pub struct RustFmt {
413    rust_fmt: PathBuf,
414    edition: Edition,
415    post_proc: PostProcess,
416    config_str: Option<OsString>,
417}
418
419impl RustFmt {
420    /// Creates a new instance of `RustFmt` using a default configuration
421    #[inline]
422    pub fn new() -> Self {
423        Self::build(None as Option<Config<&OsStr, &OsStr, &OsStr>>)
424    }
425
426    /// Creates a new instance of the formatter from the given configuration
427    #[inline]
428    pub fn from_config<K, P, V>(config: Config<K, P, V>) -> Self
429    where
430        K: Default + Eq + Hash + AsRef<OsStr>,
431        P: Default + Into<PathBuf>,
432        V: Default + AsRef<OsStr>,
433    {
434        Self::build(Some(config))
435    }
436
437    fn build<K, P, V>(config: Option<Config<K, P, V>>) -> Self
438    where
439        K: Default + Eq + Hash + AsRef<OsStr>,
440        P: Default + Into<PathBuf>,
441        V: Default + AsRef<OsStr>,
442    {
443        let config = config.unwrap_or_default();
444
445        // Use 'rustfmt' specified by the config first, and if not, environment var, if specified,
446        // else use the default
447        let rust_fmt = match config.rust_fmt {
448            Some(path) => path.into(),
449            None => env::var_os(RUST_FMT_KEY)
450                .unwrap_or_else(|| RUST_FMT.parse().unwrap())
451                .into(),
452        };
453
454        let edition = config.edition;
455        let config_str = Self::build_config_str(config.options);
456        Self {
457            rust_fmt,
458            edition,
459            post_proc: config.post_proc,
460            config_str,
461        }
462    }
463
464    fn build_config_str<K, V>(cfg_options: HashMap<K, V>) -> Option<OsString>
465    where
466        K: Default + AsRef<OsStr>,
467        V: Default + AsRef<OsStr>,
468    {
469        if !cfg_options.is_empty() {
470            // Random # that should hold a few options
471            let mut options = OsString::with_capacity(512);
472            let iter = cfg_options.iter();
473
474            for (idx, (k, v)) in iter.enumerate() {
475                // Build a comma separated list but only between items (no trailing comma)
476                if idx > 0 {
477                    options.push(",");
478                }
479                options.push(k);
480                options.push("=");
481                options.push(v);
482            }
483
484            Some(options)
485        } else {
486            None
487        }
488    }
489
490    fn build_args<'a, P>(&'a self, path: Option<&'a P>) -> Vec<&'a OsStr>
491    where
492        P: AsRef<Path> + ?Sized,
493    {
494        let mut args = match path {
495            Some(path) => {
496                let mut args = Vec::with_capacity(5);
497                args.push(path.as_ref().as_ref());
498                args
499            }
500            None => Vec::with_capacity(4),
501        };
502
503        args.push("--edition".as_ref());
504        args.push(self.edition.as_os_str());
505
506        if let Some(config_str) = &self.config_str {
507            args.push("--config".as_ref());
508            args.push(config_str);
509        }
510
511        args
512    }
513}
514
515impl Default for RustFmt {
516    #[inline]
517    fn default() -> Self {
518        Self::new()
519    }
520}
521
522impl Formatter for RustFmt {
523    fn format_str(&self, source: impl AsRef<str>) -> Result<String, Error> {
524        let args = self.build_args(None as Option<&Path>);
525
526        // Launch rustfmt
527        let mut proc = Command::new(&self.rust_fmt)
528            .stdin(Stdio::piped())
529            .stdout(Stdio::piped())
530            .stderr(Stdio::piped())
531            .args(args)
532            .spawn()?;
533
534        // Get stdin and send our source code to it to be formatted
535        // Safety: Can't panic - we captured stdin above
536        let mut stdin = proc.stdin.take().unwrap();
537        stdin.write_all(source.as_ref().as_bytes())?;
538        // Close stdin
539        drop(stdin);
540
541        // Parse the results and return stdout/stderr
542        let output = proc.wait_with_output()?;
543        let stderr = String::from_utf8(output.stderr)?;
544
545        if output.status.success() {
546            let stdout = String::from_utf8(output.stdout)?;
547            post_process(self.post_proc, stdout)
548        } else {
549            Err(Error::BadSourceCode(stderr))
550        }
551    }
552
553    fn format_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
554        // Just use regular string method if doing post processing so we don't write to file twice
555        if self.post_proc.replace_markers() {
556            let source = file_to_string(path.as_ref())?;
557            let result = self.format_str(source)?;
558            string_to_file(path, &result)
559        } else {
560            let args = self.build_args(Some(path.as_ref()));
561
562            // Launch rustfmt
563            let proc = Command::new(&self.rust_fmt)
564                .stderr(Stdio::piped())
565                .args(args)
566                .spawn()?;
567
568            // Parse the results and return stdout/stderr
569            let output = proc.wait_with_output()?;
570            let stderr = String::from_utf8(output.stderr)?;
571
572            if output.status.success() {
573                Ok(())
574            } else {
575                Err(Error::BadSourceCode(stderr))
576            }
577        }
578    }
579}
580
581// *** Pretty Please ***
582
583/// This formatter uses [prettyplease](https://crates.io/crates/prettyplease) for formatting source code
584///
585/// From string:
586/// ```
587/// use rust_format::{Formatter, PrettyPlease};
588///
589/// let source = r#"fn main() { println!("Hello World!"); }"#;
590///
591/// let actual = PrettyPlease::default().format_str(source).unwrap();
592/// let expected = r#"fn main() {
593///     println!("Hello World!");
594/// }
595/// "#;
596///
597/// assert_eq!(expected, actual);
598/// ```
599///
600/// From token stream:
601/// ```
602/// use quote::quote;
603/// use rust_format::{Formatter, PrettyPlease};
604///
605/// let source = quote! { fn main() { println!("Hello World!"); } };
606///
607/// let actual = PrettyPlease::default().format_tokens(source).unwrap();
608/// let expected = r#"fn main() {
609///     println!("Hello World!");
610/// }
611/// "#;
612///
613/// assert_eq!(expected, actual);
614/// ```
615#[cfg(feature = "pretty_please")]
616#[cfg_attr(docsrs, doc(cfg(feature = "pretty_please")))]
617#[derive(Clone, Default)]
618pub struct PrettyPlease {
619    post_proc: PostProcess,
620}
621
622#[cfg(feature = "pretty_please")]
623impl PrettyPlease {
624    /// Creates a new instance of `PrettyPlease` using a default configuration
625    #[inline]
626    pub fn new() -> Self {
627        Self::build(None as Option<Config<&OsStr, &OsStr, &OsStr>>)
628    }
629
630    /// Creates a new instance of `PrettyPlease` from the given configuration
631    #[inline]
632    pub fn from_config<K, P, V>(config: Config<K, P, V>) -> Self
633    where
634        K: Default + Eq + Hash + AsRef<OsStr>,
635        P: Default + Into<PathBuf>,
636        V: Default + AsRef<OsStr>,
637    {
638        Self::build(Some(config))
639    }
640
641    fn build<K, P, V>(config: Option<Config<K, P, V>>) -> Self
642    where
643        K: Default + Eq + Hash + AsRef<OsStr>,
644        P: Default + Into<PathBuf>,
645        V: Default + AsRef<OsStr>,
646    {
647        let config = config.unwrap_or_default();
648
649        Self {
650            post_proc: config.post_proc,
651        }
652    }
653
654    #[inline]
655    fn format(&self, f: &syn::File) -> Result<String, Error> {
656        let result = prettyplease::unparse(f);
657        post_process(self.post_proc, result)
658    }
659}
660
661#[cfg(feature = "pretty_please")]
662impl Formatter for PrettyPlease {
663    #[inline]
664    fn format_str(&self, source: impl AsRef<str>) -> Result<String, Error> {
665        let f = syn::parse_file(source.as_ref())?;
666        self.format(&f)
667    }
668
669    #[inline]
670    #[cfg(feature = "token_stream")]
671    #[cfg_attr(docsrs, doc(cfg(feature = "token_stream")))]
672    fn format_tokens(&self, tokens: proc_macro2::TokenStream) -> Result<String, Error> {
673        let f = syn::parse2::<syn::File>(tokens)?;
674        self.format(&f)
675    }
676}
677
678// *** Tests ***
679
680#[cfg(test)]
681mod tests {
682    use std::io::{Read, Seek, Write};
683
684    use pretty_assertions::assert_eq;
685
686    #[cfg(feature = "post_process")]
687    use crate::PostProcess;
688    #[cfg(feature = "pretty_please")]
689    use crate::PrettyPlease;
690    use crate::{Config, Error, Formatter, RustFmt, RUST_FMT, RUST_FMT_KEY};
691
692    const PLAIN_EXPECTED: &str = r#"#[doc = " This is main"]
693fn main() {
694    _comment_!("This prints hello world");
695    println!("Hello World!");
696    _blank_!();
697}
698"#;
699    #[cfg(feature = "pretty_please")]
700    const PLAIN_PP_EXPECTED: &str = r#"/// This is main
701fn main() {
702    _comment_!("This prints hello world");
703    println!("Hello World!");
704    _blank_!();
705}
706"#;
707    #[cfg(feature = "post_process")]
708    const REPLACE_EXPECTED: &str = r#"#[doc = " This is main"]
709fn main() {
710    // This prints hello world
711    println!("Hello World!");
712
713}
714"#;
715    #[cfg(feature = "post_process")]
716    const REPLACE_BLOCKS_EXPECTED: &str = r#"/// This is main
717fn main() {
718    // This prints hello world
719    println!("Hello World!");
720
721}
722"#;
723
724    #[test]
725    fn rustfmt_bad_env_path() {
726        temp_env::with_var(
727            RUST_FMT_KEY,
728            Some("this_is_never_going_to_be_a_valid_executable"),
729            || match RustFmt::new().format_str("bogus") {
730                Err(Error::IOError(_)) => {}
731                _ => panic!("'rustfmt' should have failed due to bad path"),
732            },
733        );
734    }
735
736    #[test]
737    fn rustfmt_bad_config_path() {
738        temp_env::with_var(RUST_FMT_KEY, Some(RUST_FMT), || {
739            let config =
740                Config::new_str().rust_fmt_path("this_is_never_going_to_be_a_valid_executable");
741            match RustFmt::from_config(config).format_str("bogus") {
742                Err(Error::IOError(_)) => {}
743                _ => panic!("'rustfmt' should have failed due to bad path"),
744            }
745        });
746    }
747
748    fn format_file(fmt: impl Formatter, expected: &str) {
749        // Write source code to file
750        let source = r#"#[doc = " This is main"] fn main() { _comment_!("This prints hello world");
751            println!("Hello World!"); _blank_!(); }"#;
752        let mut file = tempfile::NamedTempFile::new().unwrap();
753        file.write_all(source.as_bytes()).unwrap();
754
755        fmt.format_file(file.path()).unwrap();
756
757        // Now read back the formatted file
758        file.rewind().unwrap();
759        let mut actual = String::with_capacity(128);
760        file.read_to_string(&mut actual).unwrap();
761
762        assert_eq!(expected, actual);
763    }
764
765    #[test]
766    fn rustfmt_file() {
767        temp_env::with_var(RUST_FMT_KEY, Some(RUST_FMT), || {
768            format_file(RustFmt::new(), PLAIN_EXPECTED);
769        });
770    }
771
772    // prettyplease replaces doc blocks by default
773    #[cfg(feature = "pretty_please")]
774    #[test]
775    fn prettyplease_file() {
776        format_file(PrettyPlease::new(), PLAIN_PP_EXPECTED);
777    }
778
779    #[cfg(feature = "post_process")]
780    #[test]
781    fn rustfmt_file_replace_markers() {
782        temp_env::with_var(RUST_FMT_KEY, Some(RUST_FMT), || {
783            let config = Config::new_str().post_proc(PostProcess::ReplaceMarkers);
784            format_file(RustFmt::from_config(config), REPLACE_EXPECTED);
785        });
786    }
787
788    // prettyplease replaces doc blocks by default
789    #[cfg(feature = "post_process")]
790    #[cfg(feature = "pretty_please")]
791    #[test]
792    fn prettyplease_file_replace_markers() {
793        let config = Config::new_str().post_proc(PostProcess::ReplaceMarkers);
794        format_file(PrettyPlease::from_config(config), REPLACE_BLOCKS_EXPECTED);
795    }
796
797    #[cfg(feature = "post_process")]
798    #[test]
799    fn rustfmt_file_replace_markers_and_docs() {
800        temp_env::with_var(RUST_FMT_KEY, Some(RUST_FMT), || {
801            let config = Config::new_str().post_proc(PostProcess::ReplaceMarkersAndDocBlocks);
802            format_file(RustFmt::from_config(config), REPLACE_BLOCKS_EXPECTED);
803        });
804    }
805
806    #[cfg(feature = "post_process")]
807    #[cfg(feature = "pretty_please")]
808    #[test]
809    fn prettyplease_file_replace_markers_and_docs() {
810        let config = Config::new_str().post_proc(PostProcess::ReplaceMarkersAndDocBlocks);
811        format_file(PrettyPlease::from_config(config), REPLACE_BLOCKS_EXPECTED);
812    }
813
814    fn bad_format_file(fmt: impl Formatter) {
815        // Write source code to file
816        let source = r#"use"#;
817        let mut file = tempfile::NamedTempFile::new().unwrap();
818        file.write_all(source.as_bytes()).unwrap();
819
820        match fmt.format_file(file.path()) {
821            Err(Error::BadSourceCode(_)) => {}
822            _ => panic!("Expected bad source code"),
823        }
824    }
825
826    #[test]
827    fn rustfmt_bad_file() {
828        temp_env::with_var(RUST_FMT_KEY, Some(RUST_FMT), || {
829            bad_format_file(RustFmt::new());
830        });
831    }
832
833    #[cfg(feature = "pretty_please")]
834    #[test]
835    fn prettyplease_bad_file() {
836        bad_format_file(PrettyPlease::new());
837    }
838}