2018-03-01 19:28:34 -06:00
|
|
|
// Copyright 2015-2018 The Rust Project Developers. See the COPYRIGHT
|
2015-03-07 16:46:35 -06:00
|
|
|
// file at the top-level directory of this distribution and at
|
|
|
|
// http://rust-lang.org/COPYRIGHT.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2018-05-13 23:25:10 -05:00
|
|
|
#![feature(tool_attributes)]
|
2018-01-29 06:59:15 -06:00
|
|
|
#![feature(decl_macro)]
|
2018-05-01 17:18:14 -05:00
|
|
|
#![allow(unused_attributes)]
|
2018-01-01 00:40:51 -06:00
|
|
|
#![feature(type_ascription)]
|
2018-04-19 18:14:11 -05:00
|
|
|
#![feature(unicode_internals)]
|
2015-04-13 20:00:46 -05:00
|
|
|
|
2017-11-06 06:44:59 -06:00
|
|
|
#[macro_use]
|
|
|
|
extern crate derive_new;
|
2017-08-08 10:16:35 -05:00
|
|
|
extern crate diff;
|
2018-04-25 22:48:48 -05:00
|
|
|
#[macro_use]
|
2018-04-24 03:10:09 -05:00
|
|
|
extern crate failure;
|
2018-04-20 04:08:20 -05:00
|
|
|
extern crate getopts;
|
2018-03-09 10:19:38 -06:00
|
|
|
extern crate itertools;
|
2018-04-20 04:08:20 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
2015-03-07 16:46:35 -06:00
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2017-08-08 10:16:35 -05:00
|
|
|
extern crate regex;
|
2018-04-28 02:08:58 -05:00
|
|
|
extern crate rustc_target;
|
2018-03-01 19:28:34 -06:00
|
|
|
extern crate serde;
|
|
|
|
#[macro_use]
|
|
|
|
extern crate serde_derive;
|
|
|
|
extern crate serde_json;
|
2017-08-08 10:16:35 -05:00
|
|
|
extern crate syntax;
|
2015-09-10 17:27:22 -05:00
|
|
|
extern crate term;
|
2018-03-01 19:28:34 -06:00
|
|
|
extern crate toml;
|
2017-08-08 10:16:35 -05:00
|
|
|
extern crate unicode_segmentation;
|
2015-09-03 22:38:12 -05:00
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
use std::cell::RefCell;
|
2017-07-13 04:42:14 -05:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::fmt;
|
2018-04-20 05:10:48 -05:00
|
|
|
use std::io::{self, stdout, Write};
|
2018-03-08 19:51:27 -06:00
|
|
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
2017-12-08 07:16:47 -06:00
|
|
|
use std::path::PathBuf;
|
2016-03-01 16:27:19 -06:00
|
|
|
use std::rc::Rc;
|
2017-11-17 07:21:46 -06:00
|
|
|
use std::time::Duration;
|
2015-03-07 16:46:35 -06:00
|
|
|
|
2017-07-13 04:42:14 -05:00
|
|
|
use syntax::ast;
|
2017-12-08 07:16:47 -06:00
|
|
|
pub use syntax::codemap::FileName;
|
2018-05-14 01:01:53 -05:00
|
|
|
use syntax::codemap::{CodeMap, FilePathMapping, Span};
|
2018-03-14 02:43:01 -05:00
|
|
|
use syntax::errors::emitter::{ColorConfig, EmitterWriter};
|
2018-03-16 02:57:35 -05:00
|
|
|
use syntax::errors::{DiagnosticBuilder, Handler};
|
2017-07-13 04:42:14 -05:00
|
|
|
use syntax::parse::{self, ParseSess};
|
|
|
|
|
2018-04-02 09:10:37 -05:00
|
|
|
use comment::{CharClasses, FullCodeCharKind, LineClasses};
|
2018-04-24 03:10:09 -05:00
|
|
|
use failure::Fail;
|
2017-07-13 04:42:14 -05:00
|
|
|
use issues::{BadIssueSeeker, Issue};
|
2017-12-24 08:40:53 -06:00
|
|
|
use shape::Indent;
|
2017-11-13 03:10:46 -06:00
|
|
|
use utils::use_colored_tty;
|
2017-12-06 22:51:30 -06:00
|
|
|
use visitor::{FmtVisitor, SnippetProvider};
|
2016-04-14 18:51:50 -05:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
pub use config::options::CliOptions;
|
2018-02-07 08:15:00 -06:00
|
|
|
pub use config::summary::Summary;
|
2018-05-10 20:50:30 -05:00
|
|
|
pub use config::{file_lines, load_config, Config, Verbosity, WriteMode};
|
2018-04-20 04:08:20 -05:00
|
|
|
|
2018-04-25 22:48:48 -05:00
|
|
|
pub type FmtResult<T> = std::result::Result<T, failure::Error>;
|
2018-04-20 04:08:20 -05:00
|
|
|
|
2015-06-08 13:23:24 -05:00
|
|
|
#[macro_use]
|
|
|
|
mod utils;
|
2018-02-17 10:06:54 -06:00
|
|
|
|
2018-02-23 07:30:05 -06:00
|
|
|
mod attr;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod chains;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod checkstyle;
|
2017-11-12 20:26:33 -06:00
|
|
|
mod closures;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod codemap;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod comment;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod config;
|
2015-04-21 04:01:19 -05:00
|
|
|
mod expr;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod filemap;
|
2015-04-21 04:01:19 -05:00
|
|
|
mod imports;
|
2015-06-08 18:42:29 -05:00
|
|
|
mod issues;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod items;
|
|
|
|
mod lists;
|
2015-09-14 15:53:30 -05:00
|
|
|
mod macros;
|
2018-03-21 08:02:18 -05:00
|
|
|
mod matches;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod missed_spans;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod modules;
|
2018-03-07 00:40:52 -06:00
|
|
|
mod overflow;
|
2015-10-17 08:56:53 -05:00
|
|
|
mod patterns;
|
2018-02-17 10:06:54 -06:00
|
|
|
mod reorder;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod rewrite;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod rustfmt_diff;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod shape;
|
|
|
|
mod spanned;
|
|
|
|
mod string;
|
2018-04-20 04:08:20 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
mod test;
|
2018-01-29 07:00:07 -06:00
|
|
|
mod types;
|
2017-07-03 04:54:41 -05:00
|
|
|
mod vertical;
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) mod visitor;
|
2015-03-07 16:46:35 -06:00
|
|
|
|
2018-02-22 18:07:35 -06:00
|
|
|
const STDIN: &str = "<stdin>";
|
2018-02-12 05:58:38 -06:00
|
|
|
|
2018-02-07 07:48:05 -06:00
|
|
|
// A map of the files of a crate, with their new content
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) type FileMap = Vec<FileRecord>;
|
2018-02-07 07:48:05 -06:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
pub(crate) type FileRecord = (FileName, String);
|
2018-02-07 07:48:05 -06:00
|
|
|
|
2018-04-24 03:10:09 -05:00
|
|
|
#[derive(Fail, Debug, Clone, Copy)]
|
2015-09-17 13:21:06 -05:00
|
|
|
pub enum ErrorKind {
|
2017-01-08 20:57:11 -06:00
|
|
|
// Line has exceeded character limit (found, maximum)
|
2018-04-24 03:10:09 -05:00
|
|
|
#[fail(
|
2018-05-06 02:17:09 -05:00
|
|
|
display = "line formatted, but exceeded maximum width \
|
|
|
|
(maximum: {} (see `max_width` option), found: {})",
|
2018-04-24 03:10:09 -05:00
|
|
|
_0,
|
|
|
|
_1
|
|
|
|
)]
|
2017-01-08 20:57:11 -06:00
|
|
|
LineOverflow(usize, usize),
|
2015-06-08 18:42:29 -05:00
|
|
|
// Line ends in whitespace
|
2018-04-24 03:10:09 -05:00
|
|
|
#[fail(display = "left behind trailing whitespace")]
|
2015-06-08 18:42:29 -05:00
|
|
|
TrailingWhitespace,
|
2018-04-08 23:47:55 -05:00
|
|
|
// TODO or FIXME item without an issue number
|
2018-04-24 03:10:09 -05:00
|
|
|
#[fail(display = "found {}", _0)]
|
2015-06-08 18:42:29 -05:00
|
|
|
BadIssue(Issue),
|
Attempt at checking for license (#209)
I'm not quite sure how best to handle loading the license template from
a path -- I mean obviously I know *how* to do it, but I'm not sure where
to fit it in the codebase :) So this first attempt puts the license
template directly into the config file.
These are my misgivings about the license template config option as a
path to a file (I'd love feedback if some of these are wrong or can be
easily circumvented!):
1. I thought the obvious choice for the type of `license_template` in
`create_config!` should be `PathBuf`, but `PathBuf` doesn't implement
`FromStr` (yet? see https://github.com/rust-lang/rust/issues/44431), so
it would have to be wrapped in a tuple struct, and I went down that road
for a little while but then it seemed like too much ceremony for too
little gain.
2. So a plain `String` then (which, mind you, also means the same
`doc_hint()`, i.e. `<string>`, not `<path>` or something like that). The
fact that it's a valid path will be checked once we try to read the
file.
3. But where in the code should the license template be read? The
obvious choice for me would be somewhere in `Config::from_toml()`, but
since `Config` is defined via the `create_config!` macro, that would
mean tight coupling between the macro invocation (which defines the
configuration option `license_template`) and its definition (which would
rely on the existence of that option to run the template loading code).
4. `license_template` could also be made a special option which is
hardwired into the macro. This gets rid of the tight coupling, but
special-casing one of the config options would make the code harder to
navigate.
5. Instead, the macro could maybe be rewritten to allow for config
options that load additional resources from files when the config is
being parsed, but that's beyond my skill level I'm afraid (and probably
overengineering the problem if it's only ever going to be used for this
one option).
6. Finally, the file can be loaded at some later point in time, e.g. in
`format_lines()`, right before `check_license()` is called. But to
face a potential *IO* error at so late a stage, when the source files
have already been parsed... I don't know, it doesn't feel right.
BTW I don't like that I'm actually parsing the license template as late
as inside `check_license()` either, but for much the same reasons, I
don't know where else to put it. If the `Config` were hand-rolled
instead of a macro, I'd just define a custom `license_template` option
and load and parse the template in the `Config`'s init. But the way
things are, I'm a bit at a loss.
However, if someone more familiar with the project would kindly provide
a few hints as to how the path approach can be done in a way that is as
clean as possible in the context of the codebase, I'll be more than
happy to implement it! :)
2018-02-16 16:21:57 -06:00
|
|
|
// License check has failed
|
2018-04-24 03:10:09 -05:00
|
|
|
#[fail(display = "license check failed")]
|
Attempt at checking for license (#209)
I'm not quite sure how best to handle loading the license template from
a path -- I mean obviously I know *how* to do it, but I'm not sure where
to fit it in the codebase :) So this first attempt puts the license
template directly into the config file.
These are my misgivings about the license template config option as a
path to a file (I'd love feedback if some of these are wrong or can be
easily circumvented!):
1. I thought the obvious choice for the type of `license_template` in
`create_config!` should be `PathBuf`, but `PathBuf` doesn't implement
`FromStr` (yet? see https://github.com/rust-lang/rust/issues/44431), so
it would have to be wrapped in a tuple struct, and I went down that road
for a little while but then it seemed like too much ceremony for too
little gain.
2. So a plain `String` then (which, mind you, also means the same
`doc_hint()`, i.e. `<string>`, not `<path>` or something like that). The
fact that it's a valid path will be checked once we try to read the
file.
3. But where in the code should the license template be read? The
obvious choice for me would be somewhere in `Config::from_toml()`, but
since `Config` is defined via the `create_config!` macro, that would
mean tight coupling between the macro invocation (which defines the
configuration option `license_template`) and its definition (which would
rely on the existence of that option to run the template loading code).
4. `license_template` could also be made a special option which is
hardwired into the macro. This gets rid of the tight coupling, but
special-casing one of the config options would make the code harder to
navigate.
5. Instead, the macro could maybe be rewritten to allow for config
options that load additional resources from files when the config is
being parsed, but that's beyond my skill level I'm afraid (and probably
overengineering the problem if it's only ever going to be used for this
one option).
6. Finally, the file can be loaded at some later point in time, e.g. in
`format_lines()`, right before `check_license()` is called. But to
face a potential *IO* error at so late a stage, when the source files
have already been parsed... I don't know, it doesn't feel right.
BTW I don't like that I'm actually parsing the license template as late
as inside `check_license()` either, but for much the same reasons, I
don't know where else to put it. If the `Config` were hand-rolled
instead of a macro, I'd just define a custom `license_template` option
and load and parse the template in the `Config`'s init. But the way
things are, I'm a bit at a loss.
However, if someone more familiar with the project would kindly provide
a few hints as to how the path approach can be done in a way that is as
clean as possible in the context of the codebase, I'll be more than
happy to implement it! :)
2018-02-16 16:21:57 -06:00
|
|
|
LicenseCheck,
|
2018-05-14 01:01:53 -05:00
|
|
|
// Used deprecated skip attribute
|
|
|
|
#[fail(display = "`rustfmt_skip` is deprecated; use `rustfmt::skip`")]
|
|
|
|
DeprecatedAttr,
|
|
|
|
// Used a rustfmt:: attribute other than skip
|
|
|
|
#[fail(display = "invalid attribute")]
|
|
|
|
BadAttr,
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
struct FormattingError {
|
2017-08-15 02:52:11 -05:00
|
|
|
line: usize,
|
2015-06-08 18:42:29 -05:00
|
|
|
kind: ErrorKind,
|
2017-08-15 02:52:11 -05:00
|
|
|
is_comment: bool,
|
2017-12-09 05:24:14 -06:00
|
|
|
is_string: bool,
|
2017-08-15 02:52:11 -05:00
|
|
|
line_buffer: String,
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
|
2015-07-15 21:17:07 -05:00
|
|
|
impl FormattingError {
|
2018-05-14 01:01:53 -05:00
|
|
|
fn from_span(span: &Span, codemap: &CodeMap, kind: ErrorKind) -> FormattingError {
|
|
|
|
FormattingError {
|
|
|
|
line: codemap.lookup_char_pos(span.lo()).line,
|
|
|
|
kind,
|
|
|
|
is_comment: false,
|
|
|
|
is_string: false,
|
|
|
|
line_buffer: codemap
|
|
|
|
.span_to_lines(*span)
|
|
|
|
.ok()
|
|
|
|
.and_then(|fl| {
|
|
|
|
fl.file
|
|
|
|
.get_line(fl.lines[0].line_index)
|
|
|
|
.map(|l| l.into_owned())
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| String::new()),
|
|
|
|
}
|
|
|
|
}
|
2015-07-15 21:17:07 -05:00
|
|
|
fn msg_prefix(&self) -> &str {
|
|
|
|
match self.kind {
|
2018-04-08 23:47:55 -05:00
|
|
|
ErrorKind::LineOverflow(..) | ErrorKind::TrailingWhitespace => "internal error:",
|
2018-05-14 01:01:53 -05:00
|
|
|
ErrorKind::LicenseCheck | ErrorKind::BadAttr => "error:",
|
|
|
|
ErrorKind::BadIssue(_) | ErrorKind::DeprecatedAttr => "warning:",
|
2015-07-15 21:17:07 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-15 04:11:24 -05:00
|
|
|
fn msg_suffix(&self) -> &str {
|
2017-12-10 18:39:06 -06:00
|
|
|
if self.is_comment || self.is_string {
|
2017-12-10 20:46:04 -06:00
|
|
|
"set `error_on_unformatted = false` to suppress \
|
2017-12-10 18:39:06 -06:00
|
|
|
the warning against comments or string literals\n"
|
2017-12-09 05:24:14 -06:00
|
|
|
} else {
|
|
|
|
""
|
2015-07-15 21:17:07 -05:00
|
|
|
}
|
|
|
|
}
|
2017-08-15 02:54:07 -05:00
|
|
|
|
|
|
|
// (space, target)
|
2018-04-20 04:08:20 -05:00
|
|
|
fn format_len(&self) -> (usize, usize) {
|
2017-08-15 02:54:07 -05:00
|
|
|
match self.kind {
|
|
|
|
ErrorKind::LineOverflow(found, max) => (max, found - max),
|
2018-05-14 01:01:53 -05:00
|
|
|
ErrorKind::TrailingWhitespace | ErrorKind::DeprecatedAttr | ErrorKind::BadAttr => {
|
2018-05-07 16:25:48 -05:00
|
|
|
let trailing_ws_start = self
|
|
|
|
.line_buffer
|
2018-03-21 18:32:42 -05:00
|
|
|
.rfind(|c: char| !c.is_whitespace())
|
|
|
|
.map(|pos| pos + 1)
|
|
|
|
.unwrap_or(0);
|
|
|
|
(
|
|
|
|
trailing_ws_start,
|
|
|
|
self.line_buffer.len() - trailing_ws_start,
|
|
|
|
)
|
2017-08-15 02:54:07 -05:00
|
|
|
}
|
2017-10-20 01:58:24 -05:00
|
|
|
_ => unreachable!(),
|
2017-08-15 02:54:07 -05:00
|
|
|
}
|
|
|
|
}
|
2015-07-15 21:17:07 -05:00
|
|
|
}
|
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
#[derive(Clone)]
|
2015-09-17 13:21:06 -05:00
|
|
|
pub struct FormatReport {
|
|
|
|
// Maps stringified file paths to their associated formatting errors.
|
2018-05-14 01:01:53 -05:00
|
|
|
file_error_map: Rc<RefCell<HashMap<FileName, Vec<FormattingError>>>>,
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
|
2015-09-17 13:21:06 -05:00
|
|
|
impl FormatReport {
|
2016-04-14 18:51:50 -05:00
|
|
|
fn new() -> FormatReport {
|
2017-07-03 04:54:26 -05:00
|
|
|
FormatReport {
|
2018-05-14 01:01:53 -05:00
|
|
|
file_error_map: Rc::new(RefCell::new(HashMap::new())),
|
2017-07-03 04:54:26 -05:00
|
|
|
}
|
2016-04-14 18:51:50 -05:00
|
|
|
}
|
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
fn append(&self, f: FileName, mut v: Vec<FormattingError>) {
|
|
|
|
self.file_error_map
|
|
|
|
.borrow_mut()
|
|
|
|
.entry(f)
|
|
|
|
.and_modify(|fe| fe.append(&mut v))
|
|
|
|
.or_insert(v);
|
|
|
|
}
|
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
fn warning_count(&self) -> usize {
|
2017-03-06 14:40:08 -06:00
|
|
|
self.file_error_map
|
2018-05-14 01:01:53 -05:00
|
|
|
.borrow()
|
2017-03-06 14:40:08 -06:00
|
|
|
.iter()
|
|
|
|
.map(|(_, errors)| errors.len())
|
2018-01-25 23:53:28 -06:00
|
|
|
.sum()
|
2015-09-17 13:21:06 -05:00
|
|
|
}
|
2016-04-14 18:51:50 -05:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
fn has_warnings(&self) -> bool {
|
2016-04-14 18:51:50 -05:00
|
|
|
self.warning_count() > 0
|
|
|
|
}
|
2017-08-15 02:54:18 -05:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
fn print_warnings_fancy(
|
2017-08-15 02:54:18 -05:00
|
|
|
&self,
|
|
|
|
mut t: Box<term::Terminal<Output = io::Stderr>>,
|
|
|
|
) -> Result<(), term::Error> {
|
2018-05-14 01:01:53 -05:00
|
|
|
for (file, errors) in &*self.file_error_map.borrow() {
|
2017-08-15 02:54:18 -05:00
|
|
|
for error in errors {
|
|
|
|
let prefix_space_len = error.line.to_string().len();
|
2018-04-01 02:20:46 -05:00
|
|
|
let prefix_spaces = " ".repeat(1 + prefix_space_len);
|
2017-08-15 02:54:18 -05:00
|
|
|
|
|
|
|
// First line: the overview of error
|
|
|
|
t.fg(term::color::RED)?;
|
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "{} ", error.msg_prefix())?;
|
|
|
|
t.reset()?;
|
|
|
|
t.attr(term::Attr::Bold)?;
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t, "{}", error.kind)?;
|
2017-08-15 02:54:18 -05:00
|
|
|
|
|
|
|
// Second line: file info
|
|
|
|
write!(t, "{}--> ", &prefix_spaces[1..])?;
|
|
|
|
t.reset()?;
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t, "{}:{}", file, error.line)?;
|
2017-08-15 02:54:18 -05:00
|
|
|
|
|
|
|
// Third to fifth lines: show the line which triggered error, if available.
|
|
|
|
if !error.line_buffer.is_empty() {
|
|
|
|
let (space_len, target_len) = error.format_len();
|
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "{}|\n{} | ", prefix_spaces, error.line)?;
|
|
|
|
t.reset()?;
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t, "{}", error.line_buffer)?;
|
2017-08-15 02:54:18 -05:00
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "{}| ", prefix_spaces)?;
|
|
|
|
t.fg(term::color::RED)?;
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t, "{}", target_str(space_len, target_len))?;
|
2017-08-15 02:54:18 -05:00
|
|
|
t.reset()?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The last line: show note if available.
|
|
|
|
let msg_suffix = error.msg_suffix();
|
|
|
|
if !msg_suffix.is_empty() {
|
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "{}= note: ", prefix_spaces)?;
|
|
|
|
t.reset()?;
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t, "{}", error.msg_suffix())?;
|
2017-08-15 02:54:18 -05:00
|
|
|
} else {
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(t)?;
|
2017-08-15 02:54:18 -05:00
|
|
|
}
|
|
|
|
t.reset()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
if !self.file_error_map.borrow().is_empty() {
|
2017-08-15 02:54:18 -05:00
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "warning: ")?;
|
|
|
|
t.reset()?;
|
|
|
|
write!(
|
|
|
|
t,
|
|
|
|
"rustfmt may have failed to format. See previous {} errors.\n\n",
|
|
|
|
self.warning_count(),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn target_str(space_len: usize, target_len: usize) -> String {
|
2018-04-01 02:20:46 -05:00
|
|
|
let empty_line = " ".repeat(space_len);
|
|
|
|
let overflowed = "^".repeat(target_len);
|
2017-08-15 02:54:18 -05:00
|
|
|
empty_line + &overflowed
|
2015-09-17 13:21:06 -05:00
|
|
|
}
|
|
|
|
|
2015-06-08 18:42:29 -05:00
|
|
|
impl fmt::Display for FormatReport {
|
|
|
|
// Prints all the formatting errors.
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
2018-05-14 01:01:53 -05:00
|
|
|
for (file, errors) in &*self.file_error_map.borrow() {
|
2015-06-08 18:42:29 -05:00
|
|
|
for error in errors {
|
2017-08-15 02:54:07 -05:00
|
|
|
let prefix_space_len = error.line.to_string().len();
|
2018-04-01 02:20:46 -05:00
|
|
|
let prefix_spaces = " ".repeat(1 + prefix_space_len);
|
2017-08-15 02:54:07 -05:00
|
|
|
|
|
|
|
let error_line_buffer = if error.line_buffer.is_empty() {
|
|
|
|
String::from(" ")
|
|
|
|
} else {
|
|
|
|
let (space_len, target_len) = error.format_len();
|
|
|
|
format!(
|
|
|
|
"{}|\n{} | {}\n{}| {}",
|
|
|
|
prefix_spaces,
|
|
|
|
error.line,
|
|
|
|
error.line_buffer,
|
|
|
|
prefix_spaces,
|
|
|
|
target_str(space_len, target_len)
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
let error_info = format!("{} {}", error.msg_prefix(), error.kind);
|
|
|
|
let file_info = format!("{}--> {}:{}", &prefix_spaces[1..], file, error.line);
|
|
|
|
let msg_suffix = error.msg_suffix();
|
|
|
|
let note = if msg_suffix.is_empty() {
|
|
|
|
String::new()
|
|
|
|
} else {
|
|
|
|
format!("{}note= ", prefix_spaces)
|
|
|
|
};
|
|
|
|
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(
|
2017-06-11 22:58:58 -05:00
|
|
|
fmt,
|
2018-04-24 17:21:23 -05:00
|
|
|
"{}\n{}\n{}\n{}{}",
|
2017-08-15 02:54:07 -05:00
|
|
|
error_info,
|
|
|
|
file_info,
|
|
|
|
error_line_buffer,
|
|
|
|
note,
|
2017-06-11 22:58:58 -05:00
|
|
|
error.msg_suffix()
|
|
|
|
)?;
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
}
|
2018-05-14 01:01:53 -05:00
|
|
|
if !self.file_error_map.borrow().is_empty() {
|
2018-04-24 17:21:23 -05:00
|
|
|
writeln!(
|
2017-08-15 02:54:07 -05:00
|
|
|
fmt,
|
2018-04-24 17:21:23 -05:00
|
|
|
"warning: rustfmt may have failed to format. See previous {} errors.",
|
2017-08-15 02:54:07 -05:00
|
|
|
self.warning_count(),
|
|
|
|
)?;
|
|
|
|
}
|
2015-06-08 18:42:29 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-12 05:58:38 -06:00
|
|
|
fn should_emit_verbose<F>(path: &FileName, config: &Config, f: F)
|
|
|
|
where
|
|
|
|
F: Fn(),
|
|
|
|
{
|
2018-05-10 20:50:30 -05:00
|
|
|
if config.verbose() == Verbosity::Verbose && path.to_string() != STDIN {
|
2018-02-12 05:58:38 -06:00
|
|
|
f();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-07 16:46:35 -06:00
|
|
|
// Formatting which depends on the AST.
|
2017-06-11 22:58:58 -05:00
|
|
|
fn format_ast<F>(
|
|
|
|
krate: &ast::Crate,
|
2017-08-11 03:54:38 -05:00
|
|
|
parse_session: &mut ParseSess,
|
2017-12-08 07:16:47 -06:00
|
|
|
main_file: &FileName,
|
2017-06-11 22:58:58 -05:00
|
|
|
config: &Config,
|
2018-05-14 01:01:53 -05:00
|
|
|
report: FormatReport,
|
2017-06-11 22:58:58 -05:00
|
|
|
mut after_file: F,
|
|
|
|
) -> Result<(FileMap, bool), io::Error>
|
|
|
|
where
|
2018-05-14 01:01:53 -05:00
|
|
|
F: FnMut(&FileName, &mut String, &[(usize, usize)], &FormatReport) -> Result<bool, io::Error>,
|
2016-05-15 04:41:05 -05:00
|
|
|
{
|
|
|
|
let mut result = FileMap::new();
|
2016-06-20 13:42:29 -05:00
|
|
|
// diff mode: check if any files are differing
|
|
|
|
let mut has_diff = false;
|
2016-05-15 04:41:05 -05:00
|
|
|
|
2018-05-10 20:50:30 -05:00
|
|
|
let skip_children = config.skip_children();
|
2017-11-06 06:45:54 -06:00
|
|
|
for (path, module) in modules::list_files(krate, parse_session.codemap())? {
|
2018-03-08 22:27:43 -06:00
|
|
|
if (skip_children && path != *main_file) || config.ignore().skip_file(&path) {
|
2015-12-23 08:25:49 -06:00
|
|
|
continue;
|
|
|
|
}
|
2018-02-12 05:58:38 -06:00
|
|
|
should_emit_verbose(&path, config, || println!("Formatting {}", path));
|
2017-12-06 22:51:30 -06:00
|
|
|
let filemap = parse_session
|
|
|
|
.codemap()
|
|
|
|
.lookup_char_pos(module.inner.lo())
|
|
|
|
.file;
|
|
|
|
let big_snippet = filemap.src.as_ref().unwrap();
|
|
|
|
let snippet_provider = SnippetProvider::new(filemap.start_pos, big_snippet);
|
2018-05-14 01:01:53 -05:00
|
|
|
let mut visitor =
|
|
|
|
FmtVisitor::from_codemap(parse_session, config, &snippet_provider, report.clone());
|
2017-08-29 22:00:10 -05:00
|
|
|
// Format inner attributes if available.
|
2017-12-08 07:16:47 -06:00
|
|
|
if !krate.attrs.is_empty() && path == *main_file {
|
2017-11-10 02:09:31 -06:00
|
|
|
visitor.skip_empty_lines(filemap.end_pos);
|
2017-10-20 01:58:24 -05:00
|
|
|
if visitor.visit_attrs(&krate.attrs, ast::AttrStyle::Inner) {
|
|
|
|
visitor.push_rewrite(module.inner, None);
|
|
|
|
} else {
|
|
|
|
visitor.format_separate_mod(module, &*filemap);
|
|
|
|
}
|
2017-08-29 22:00:10 -05:00
|
|
|
} else {
|
|
|
|
visitor.last_pos = filemap.start_pos;
|
2017-11-10 02:09:31 -06:00
|
|
|
visitor.skip_empty_lines(filemap.end_pos);
|
2017-10-20 01:58:24 -05:00
|
|
|
visitor.format_separate_mod(module, &*filemap);
|
|
|
|
};
|
2016-05-15 04:41:05 -05:00
|
|
|
|
2018-02-18 21:50:39 -06:00
|
|
|
debug_assert_eq!(
|
2017-12-08 01:59:04 -06:00
|
|
|
visitor.line_number,
|
2018-04-24 17:21:23 -05:00
|
|
|
::utils::count_newlines(&visitor.buffer)
|
2017-12-08 01:59:04 -06:00
|
|
|
);
|
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
has_diff |= match after_file(&path, &mut visitor.buffer, &visitor.skipped_range, &report) {
|
2017-10-19 04:52:12 -05:00
|
|
|
Ok(result) => result,
|
|
|
|
Err(e) => {
|
|
|
|
// Create a new error with path_str to help users see which files failed
|
2017-12-08 07:16:47 -06:00
|
|
|
let err_msg = format!("{}: {}", path, e);
|
2017-10-19 04:52:12 -05:00
|
|
|
return Err(io::Error::new(e.kind(), err_msg));
|
|
|
|
}
|
|
|
|
};
|
2016-05-15 04:41:05 -05:00
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
result.push((path.clone(), visitor.buffer));
|
2015-07-26 05:55:25 -05:00
|
|
|
}
|
2016-05-15 04:41:05 -05:00
|
|
|
|
2016-06-20 13:42:29 -05:00
|
|
|
Ok((result, has_diff))
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
|
|
|
|
2018-05-13 23:25:10 -05:00
|
|
|
/// Returns true if the line with the given line number was skipped by `#[rustfmt::skip]`.
|
2017-12-08 02:46:43 -06:00
|
|
|
fn is_skipped_line(line_number: usize, skipped_range: &[(usize, usize)]) -> bool {
|
|
|
|
skipped_range
|
|
|
|
.iter()
|
|
|
|
.any(|&(lo, hi)| lo <= line_number && line_number <= hi)
|
|
|
|
}
|
|
|
|
|
2017-12-09 05:24:14 -06:00
|
|
|
fn should_report_error(
|
|
|
|
config: &Config,
|
|
|
|
char_kind: FullCodeCharKind,
|
|
|
|
is_string: bool,
|
|
|
|
error_kind: ErrorKind,
|
|
|
|
) -> bool {
|
2017-12-10 18:39:06 -06:00
|
|
|
let allow_error_report = if char_kind.is_comment() || is_string {
|
2017-12-10 20:46:04 -06:00
|
|
|
config.error_on_unformatted()
|
2017-12-09 05:24:14 -06:00
|
|
|
} else {
|
|
|
|
true
|
|
|
|
};
|
|
|
|
|
|
|
|
match error_kind {
|
|
|
|
ErrorKind::LineOverflow(..) => config.error_on_line_overflow() && allow_error_report,
|
|
|
|
ErrorKind::TrailingWhitespace => allow_error_report,
|
|
|
|
_ => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-21 05:50:43 -05:00
|
|
|
// Formatting done on a char by char or line by line basis.
|
2016-05-15 04:41:05 -05:00
|
|
|
// FIXME(#20) other stuff for parity with make tidy
|
2017-12-08 02:46:43 -06:00
|
|
|
fn format_lines(
|
2017-12-10 02:40:51 -06:00
|
|
|
text: &mut String,
|
2017-12-08 07:16:47 -06:00
|
|
|
name: &FileName,
|
2017-12-08 02:46:43 -06:00
|
|
|
skipped_range: &[(usize, usize)],
|
|
|
|
config: &Config,
|
2018-05-14 01:01:53 -05:00
|
|
|
report: &FormatReport,
|
2017-12-08 02:46:43 -06:00
|
|
|
) {
|
2016-05-15 04:41:05 -05:00
|
|
|
let mut trims = vec![];
|
|
|
|
let mut last_wspace: Option<usize> = None;
|
|
|
|
let mut line_len = 0;
|
|
|
|
let mut cur_line = 1;
|
|
|
|
let mut newline_count = 0;
|
|
|
|
let mut errors = vec![];
|
2017-05-16 03:47:09 -05:00
|
|
|
let mut issue_seeker = BadIssueSeeker::new(config.report_todo(), config.report_fixme());
|
2017-08-15 02:52:11 -05:00
|
|
|
let mut line_buffer = String::with_capacity(config.max_width() * 2);
|
2017-12-09 05:24:14 -06:00
|
|
|
let mut is_string = false; // true if the current line contains a string literal.
|
|
|
|
let mut format_line = config.file_lines().contains_line(name, cur_line);
|
2018-02-18 21:50:39 -06:00
|
|
|
let allow_issue_seek = !issue_seeker.is_disabled();
|
2016-05-15 04:41:05 -05:00
|
|
|
|
Attempt at checking for license (#209)
I'm not quite sure how best to handle loading the license template from
a path -- I mean obviously I know *how* to do it, but I'm not sure where
to fit it in the codebase :) So this first attempt puts the license
template directly into the config file.
These are my misgivings about the license template config option as a
path to a file (I'd love feedback if some of these are wrong or can be
easily circumvented!):
1. I thought the obvious choice for the type of `license_template` in
`create_config!` should be `PathBuf`, but `PathBuf` doesn't implement
`FromStr` (yet? see https://github.com/rust-lang/rust/issues/44431), so
it would have to be wrapped in a tuple struct, and I went down that road
for a little while but then it seemed like too much ceremony for too
little gain.
2. So a plain `String` then (which, mind you, also means the same
`doc_hint()`, i.e. `<string>`, not `<path>` or something like that). The
fact that it's a valid path will be checked once we try to read the
file.
3. But where in the code should the license template be read? The
obvious choice for me would be somewhere in `Config::from_toml()`, but
since `Config` is defined via the `create_config!` macro, that would
mean tight coupling between the macro invocation (which defines the
configuration option `license_template`) and its definition (which would
rely on the existence of that option to run the template loading code).
4. `license_template` could also be made a special option which is
hardwired into the macro. This gets rid of the tight coupling, but
special-casing one of the config options would make the code harder to
navigate.
5. Instead, the macro could maybe be rewritten to allow for config
options that load additional resources from files when the config is
being parsed, but that's beyond my skill level I'm afraid (and probably
overengineering the problem if it's only ever going to be used for this
one option).
6. Finally, the file can be loaded at some later point in time, e.g. in
`format_lines()`, right before `check_license()` is called. But to
face a potential *IO* error at so late a stage, when the source files
have already been parsed... I don't know, it doesn't feel right.
BTW I don't like that I'm actually parsing the license template as late
as inside `check_license()` either, but for much the same reasons, I
don't know where else to put it. If the `Config` were hand-rolled
instead of a macro, I'd just define a custom `license_template` option
and load and parse the template in the `Config`'s init. But the way
things are, I'm a bit at a loss.
However, if someone more familiar with the project would kindly provide
a few hints as to how the path approach can be done in a way that is as
clean as possible in the context of the codebase, I'll be more than
happy to implement it! :)
2018-02-16 16:21:57 -06:00
|
|
|
// Check license.
|
2018-02-19 10:26:29 -06:00
|
|
|
if let Some(ref license_template) = config.license_template {
|
|
|
|
if !license_template.is_match(text) {
|
|
|
|
errors.push(FormattingError {
|
|
|
|
line: cur_line,
|
|
|
|
kind: ErrorKind::LicenseCheck,
|
|
|
|
is_comment: false,
|
|
|
|
is_string: false,
|
|
|
|
line_buffer: String::new(),
|
|
|
|
});
|
Attempt at checking for license (#209)
I'm not quite sure how best to handle loading the license template from
a path -- I mean obviously I know *how* to do it, but I'm not sure where
to fit it in the codebase :) So this first attempt puts the license
template directly into the config file.
These are my misgivings about the license template config option as a
path to a file (I'd love feedback if some of these are wrong or can be
easily circumvented!):
1. I thought the obvious choice for the type of `license_template` in
`create_config!` should be `PathBuf`, but `PathBuf` doesn't implement
`FromStr` (yet? see https://github.com/rust-lang/rust/issues/44431), so
it would have to be wrapped in a tuple struct, and I went down that road
for a little while but then it seemed like too much ceremony for too
little gain.
2. So a plain `String` then (which, mind you, also means the same
`doc_hint()`, i.e. `<string>`, not `<path>` or something like that). The
fact that it's a valid path will be checked once we try to read the
file.
3. But where in the code should the license template be read? The
obvious choice for me would be somewhere in `Config::from_toml()`, but
since `Config` is defined via the `create_config!` macro, that would
mean tight coupling between the macro invocation (which defines the
configuration option `license_template`) and its definition (which would
rely on the existence of that option to run the template loading code).
4. `license_template` could also be made a special option which is
hardwired into the macro. This gets rid of the tight coupling, but
special-casing one of the config options would make the code harder to
navigate.
5. Instead, the macro could maybe be rewritten to allow for config
options that load additional resources from files when the config is
being parsed, but that's beyond my skill level I'm afraid (and probably
overengineering the problem if it's only ever going to be used for this
one option).
6. Finally, the file can be loaded at some later point in time, e.g. in
`format_lines()`, right before `check_license()` is called. But to
face a potential *IO* error at so late a stage, when the source files
have already been parsed... I don't know, it doesn't feel right.
BTW I don't like that I'm actually parsing the license template as late
as inside `check_license()` either, but for much the same reasons, I
don't know where else to put it. If the `Config` were hand-rolled
instead of a macro, I'd just define a custom `license_template` option
and load and parse the template in the `Config`'s init. But the way
things are, I'm a bit at a loss.
However, if someone more familiar with the project would kindly provide
a few hints as to how the path approach can be done in a way that is as
clean as possible in the context of the codebase, I'll be more than
happy to implement it! :)
2018-02-16 16:21:57 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Iterate over the chars in the file map.
|
2017-12-10 20:50:11 -06:00
|
|
|
for (kind, (b, c)) in CharClasses::new(text.chars().enumerate()) {
|
2016-05-15 04:41:05 -05:00
|
|
|
if c == '\r' {
|
|
|
|
continue;
|
|
|
|
}
|
2015-06-08 18:42:29 -05:00
|
|
|
|
2018-02-18 21:50:39 -06:00
|
|
|
if allow_issue_seek && format_line {
|
2017-05-06 14:14:44 -05:00
|
|
|
// Add warnings for bad todos/ fixmes
|
|
|
|
if let Some(issue) = issue_seeker.inspect(c) {
|
2015-09-26 01:29:48 -05:00
|
|
|
errors.push(FormattingError {
|
2017-06-11 22:58:58 -05:00
|
|
|
line: cur_line,
|
|
|
|
kind: ErrorKind::BadIssue(issue),
|
2017-08-15 02:52:11 -05:00
|
|
|
is_comment: false,
|
2017-12-09 05:24:14 -06:00
|
|
|
is_string: false,
|
2017-08-15 02:52:11 -05:00
|
|
|
line_buffer: String::new(),
|
2017-06-11 22:58:58 -05:00
|
|
|
});
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
2017-05-06 14:14:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if c == '\n' {
|
|
|
|
if format_line {
|
|
|
|
// Check for (and record) trailing whitespace.
|
2017-12-09 05:24:14 -06:00
|
|
|
if let Some(..) = last_wspace {
|
|
|
|
if should_report_error(config, kind, is_string, ErrorKind::TrailingWhitespace) {
|
|
|
|
trims.push((cur_line, kind, line_buffer.clone()));
|
|
|
|
}
|
2017-05-06 14:14:44 -05:00
|
|
|
line_len -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for any line width errors we couldn't correct.
|
2017-12-09 05:24:14 -06:00
|
|
|
let error_kind = ErrorKind::LineOverflow(line_len, config.max_width());
|
2018-05-06 01:22:29 -05:00
|
|
|
if line_len > config.max_width()
|
|
|
|
&& !is_skipped_line(cur_line, skipped_range)
|
2017-12-09 05:24:14 -06:00
|
|
|
&& should_report_error(config, kind, is_string, error_kind)
|
|
|
|
{
|
2017-05-06 14:14:44 -05:00
|
|
|
errors.push(FormattingError {
|
2017-06-11 22:58:58 -05:00
|
|
|
line: cur_line,
|
2017-12-09 05:24:14 -06:00
|
|
|
kind: error_kind,
|
|
|
|
is_comment: kind.is_comment(),
|
2018-01-21 22:05:18 -06:00
|
|
|
is_string,
|
2017-08-15 02:52:11 -05:00
|
|
|
line_buffer: line_buffer.clone(),
|
2017-06-11 22:58:58 -05:00
|
|
|
});
|
2017-05-06 14:14:44 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-15 04:41:05 -05:00
|
|
|
line_len = 0;
|
|
|
|
cur_line += 1;
|
2017-12-09 05:24:14 -06:00
|
|
|
format_line = config.file_lines().contains_line(name, cur_line);
|
2016-05-15 04:41:05 -05:00
|
|
|
newline_count += 1;
|
|
|
|
last_wspace = None;
|
2017-08-15 02:52:11 -05:00
|
|
|
line_buffer.clear();
|
2017-12-09 05:24:14 -06:00
|
|
|
is_string = false;
|
2016-05-15 04:41:05 -05:00
|
|
|
} else {
|
|
|
|
newline_count = 0;
|
2018-01-19 11:18:25 -06:00
|
|
|
line_len += if c == '\t' { config.tab_spaces() } else { 1 };
|
2016-05-15 04:41:05 -05:00
|
|
|
if c.is_whitespace() {
|
|
|
|
if last_wspace.is_none() {
|
|
|
|
last_wspace = Some(b);
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
|
|
|
} else {
|
2016-05-15 04:41:05 -05:00
|
|
|
last_wspace = None;
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
2017-08-15 02:52:11 -05:00
|
|
|
line_buffer.push(c);
|
2017-12-09 05:24:14 -06:00
|
|
|
if kind.is_string() {
|
|
|
|
is_string = true;
|
|
|
|
}
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
2016-05-15 04:41:05 -05:00
|
|
|
}
|
2015-03-07 16:46:35 -06:00
|
|
|
|
2016-05-15 04:41:05 -05:00
|
|
|
if newline_count > 1 {
|
2017-12-10 02:40:51 -06:00
|
|
|
debug!("track truncate: {} {}", text.len(), newline_count);
|
|
|
|
let line = text.len() - newline_count + 1;
|
2016-05-15 04:41:05 -05:00
|
|
|
text.truncate(line);
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
2015-04-23 01:02:55 -05:00
|
|
|
|
2017-12-09 05:24:14 -06:00
|
|
|
for &(l, kind, ref b) in &trims {
|
2017-12-08 02:46:43 -06:00
|
|
|
if !is_skipped_line(l, skipped_range) {
|
|
|
|
errors.push(FormattingError {
|
|
|
|
line: l,
|
|
|
|
kind: ErrorKind::TrailingWhitespace,
|
2017-12-09 05:24:14 -06:00
|
|
|
is_comment: kind.is_comment(),
|
|
|
|
is_string: kind.is_string(),
|
2017-12-08 02:46:43 -06:00
|
|
|
line_buffer: b.clone(),
|
|
|
|
});
|
|
|
|
}
|
2015-04-23 01:02:55 -05:00
|
|
|
}
|
2015-06-08 18:42:29 -05:00
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
report.append(name.clone(), errors);
|
2015-03-07 16:46:35 -06:00
|
|
|
}
|
|
|
|
|
2018-02-15 07:48:06 -06:00
|
|
|
fn parse_input<'sess>(
|
2017-06-11 22:58:58 -05:00
|
|
|
input: Input,
|
2018-02-15 07:48:06 -06:00
|
|
|
parse_session: &'sess ParseSess,
|
|
|
|
config: &Config,
|
2018-03-08 19:51:27 -06:00
|
|
|
) -> Result<ast::Crate, ParseError<'sess>> {
|
|
|
|
let mut parser = match input {
|
|
|
|
Input::File(file) => parse::new_parser_from_file(parse_session, &file),
|
|
|
|
Input::Text(text) => parse::new_parser_from_source_str(
|
|
|
|
parse_session,
|
|
|
|
FileName::Custom("stdin".to_owned()),
|
|
|
|
text,
|
|
|
|
),
|
2016-04-27 14:08:44 -05:00
|
|
|
};
|
|
|
|
|
2018-03-08 19:51:27 -06:00
|
|
|
parser.cfg_mods = false;
|
|
|
|
if config.skip_children() {
|
|
|
|
parser.recurse_into_file_modules = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut parser = AssertUnwindSafe(parser);
|
|
|
|
let result = catch_unwind(move || parser.0.parse_crate_mod());
|
|
|
|
|
2016-05-17 16:58:51 -05:00
|
|
|
match result {
|
2018-03-08 19:51:27 -06:00
|
|
|
Ok(Ok(c)) => {
|
2016-05-17 16:58:51 -05:00
|
|
|
if parse_session.span_diagnostic.has_errors() {
|
|
|
|
// Bail out if the parser recovered from an error.
|
2018-03-08 19:51:27 -06:00
|
|
|
Err(ParseError::Recovered)
|
2016-05-17 16:58:51 -05:00
|
|
|
} else {
|
|
|
|
Ok(c)
|
|
|
|
}
|
|
|
|
}
|
2018-03-08 19:51:27 -06:00
|
|
|
Ok(Err(e)) => Err(ParseError::Error(e)),
|
|
|
|
Err(_) => Err(ParseError::Panic),
|
2016-04-14 18:51:50 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
}
|
|
|
|
|
2018-03-08 19:51:27 -06:00
|
|
|
/// All the ways that parsing can fail.
|
|
|
|
enum ParseError<'sess> {
|
|
|
|
/// There was an error, but the parser recovered.
|
|
|
|
Recovered,
|
|
|
|
/// There was an error (supplied) and parsing failed.
|
|
|
|
Error(DiagnosticBuilder<'sess>),
|
|
|
|
/// The parser panicked.
|
|
|
|
Panic,
|
|
|
|
}
|
|
|
|
|
2017-12-24 08:40:53 -06:00
|
|
|
/// Format the given snippet. The snippet is expected to be *complete* code.
|
|
|
|
/// When we cannot parse the given snippet, this function returns `None`.
|
|
|
|
pub fn format_snippet(snippet: &str, config: &Config) -> Option<String> {
|
|
|
|
let mut out: Vec<u8> = Vec::with_capacity(snippet.len() * 2);
|
|
|
|
let input = Input::Text(snippet.into());
|
|
|
|
let mut config = config.clone();
|
2018-05-10 20:50:30 -05:00
|
|
|
config.set().write_mode(config::WriteMode::Display);
|
|
|
|
config.set().verbose(Verbosity::Quiet);
|
2018-01-11 00:19:23 -06:00
|
|
|
config.set().hide_parse_errors(true);
|
2017-12-24 08:40:53 -06:00
|
|
|
match format_input(input, &config, Some(&mut out)) {
|
|
|
|
// `format_input()` returns an empty string on parsing error.
|
|
|
|
Ok(..) if out.is_empty() && !snippet.is_empty() => None,
|
|
|
|
Ok(..) => String::from_utf8(out).ok(),
|
|
|
|
Err(..) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-09 02:10:20 -06:00
|
|
|
const FN_MAIN_PREFIX: &str = "fn main() {\n";
|
|
|
|
|
|
|
|
fn enclose_in_main_block(s: &str, config: &Config) -> String {
|
|
|
|
let indent = Indent::from_width(config, config.tab_spaces());
|
2018-04-02 09:10:37 -05:00
|
|
|
let mut result = String::with_capacity(s.len() * 2);
|
|
|
|
result.push_str(FN_MAIN_PREFIX);
|
|
|
|
let mut need_indent = true;
|
|
|
|
for (kind, line) in LineClasses::new(s) {
|
|
|
|
if need_indent {
|
|
|
|
result.push_str(&indent.to_string(config));
|
|
|
|
}
|
|
|
|
result.push_str(&line);
|
|
|
|
result.push('\n');
|
2018-04-24 17:21:23 -05:00
|
|
|
need_indent = !kind.is_string() || line.ends_with('\\');
|
2018-04-02 09:10:37 -05:00
|
|
|
}
|
|
|
|
result.push('}');
|
|
|
|
result
|
2018-03-09 02:10:20 -06:00
|
|
|
}
|
|
|
|
|
2017-12-24 08:40:53 -06:00
|
|
|
/// Format the given code block. Mainly targeted for code block in comment.
|
|
|
|
/// The code block may be incomplete (i.e. parser may be unable to parse it).
|
|
|
|
/// To avoid panic in parser, we wrap the code block with a dummy function.
|
|
|
|
/// The returned code block does *not* end with newline.
|
|
|
|
pub fn format_code_block(code_snippet: &str, config: &Config) -> Option<String> {
|
|
|
|
// Wrap the given code block with `fn main()` if it does not have one.
|
2018-03-09 02:10:20 -06:00
|
|
|
let snippet = enclose_in_main_block(code_snippet, config);
|
2018-02-13 14:10:43 -06:00
|
|
|
let mut result = String::with_capacity(snippet.len());
|
|
|
|
let mut is_first = true;
|
2017-12-24 08:40:53 -06:00
|
|
|
|
|
|
|
// Trim "fn main() {" on the first line and "}" on the last line,
|
|
|
|
// then unindent the whole code block.
|
2018-02-13 14:10:43 -06:00
|
|
|
let formatted = format_snippet(&snippet, config)?;
|
|
|
|
// 2 = "}\n"
|
2018-05-08 13:31:33 -05:00
|
|
|
let block_len = formatted.rfind('}').unwrap_or(formatted.len());
|
2018-04-02 09:10:37 -05:00
|
|
|
let mut is_indented = true;
|
|
|
|
for (kind, ref line) in LineClasses::new(&formatted[FN_MAIN_PREFIX.len()..block_len]) {
|
2018-02-13 14:10:43 -06:00
|
|
|
if !is_first {
|
|
|
|
result.push('\n');
|
|
|
|
} else {
|
|
|
|
is_first = false;
|
|
|
|
}
|
2018-04-02 09:10:37 -05:00
|
|
|
let trimmed_line = if !is_indented {
|
|
|
|
line
|
|
|
|
} else if line.len() > config.max_width() {
|
2018-02-13 14:10:43 -06:00
|
|
|
// If there are lines that are larger than max width, we cannot tell
|
|
|
|
// whether we have succeeded but have some comments or strings that
|
|
|
|
// are too long, or we have failed to format code block. We will be
|
|
|
|
// conservative and just return `None` in this case.
|
|
|
|
return None;
|
|
|
|
} else if line.len() > config.tab_spaces() {
|
|
|
|
// Make sure that the line has leading whitespaces.
|
|
|
|
let indent_str = Indent::from_width(config, config.tab_spaces()).to_string(config);
|
|
|
|
if line.starts_with(indent_str.as_ref()) {
|
|
|
|
let offset = if config.hard_tabs() {
|
|
|
|
1
|
2017-12-24 08:40:53 -06:00
|
|
|
} else {
|
2018-02-13 14:10:43 -06:00
|
|
|
config.tab_spaces()
|
|
|
|
};
|
|
|
|
&line[offset..]
|
|
|
|
} else {
|
|
|
|
line
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
line
|
|
|
|
};
|
|
|
|
result.push_str(trimmed_line);
|
2018-04-24 17:21:23 -05:00
|
|
|
is_indented = !kind.is_string() || line.ends_with('\\');
|
2018-02-13 14:10:43 -06:00
|
|
|
}
|
|
|
|
Some(result)
|
2017-12-24 08:40:53 -06:00
|
|
|
}
|
|
|
|
|
2017-06-11 22:58:58 -05:00
|
|
|
pub fn format_input<T: Write>(
|
2018-03-15 04:55:52 -05:00
|
|
|
input: Input,
|
|
|
|
config: &Config,
|
|
|
|
out: Option<&mut T>,
|
|
|
|
) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
|
|
|
|
syntax::with_globals(|| format_input_inner(input, config, out))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn format_input_inner<T: Write>(
|
2017-06-11 22:58:58 -05:00
|
|
|
input: Input,
|
|
|
|
config: &Config,
|
|
|
|
mut out: Option<&mut T>,
|
|
|
|
) -> Result<(Summary, FileMap, FormatReport), (io::Error, Summary)> {
|
2017-08-29 08:16:04 -05:00
|
|
|
let mut summary = Summary::default();
|
2017-05-16 03:47:09 -05:00
|
|
|
if config.disable_all_formatting() {
|
2017-09-17 08:14:28 -05:00
|
|
|
// When the input is from stdin, echo back the input.
|
|
|
|
if let Input::Text(ref buf) = input {
|
|
|
|
if let Err(e) = io::stdout().write_all(buf.as_bytes()) {
|
|
|
|
return Err((e, summary));
|
|
|
|
}
|
|
|
|
}
|
2017-02-06 22:11:47 -06:00
|
|
|
return Ok((summary, FileMap::new(), FormatReport::new()));
|
|
|
|
}
|
2017-06-05 23:54:22 -05:00
|
|
|
let codemap = Rc::new(CodeMap::new(FilePathMapping::empty()));
|
2016-03-01 16:27:19 -06:00
|
|
|
|
2018-01-01 00:40:51 -06:00
|
|
|
let tty_handler = if config.hide_parse_errors() {
|
|
|
|
let silent_emitter = Box::new(EmitterWriter::new(
|
|
|
|
Box::new(Vec::new()),
|
|
|
|
Some(codemap.clone()),
|
|
|
|
false,
|
2018-02-01 19:18:30 -06:00
|
|
|
false,
|
2018-01-01 00:40:51 -06:00
|
|
|
));
|
|
|
|
Handler::with_emitter(true, false, silent_emitter)
|
|
|
|
} else {
|
2018-02-05 18:29:00 -06:00
|
|
|
let supports_color = term::stderr().map_or(false, |term| term.supports_color());
|
|
|
|
let color_cfg = if supports_color {
|
|
|
|
ColorConfig::Auto
|
|
|
|
} else {
|
|
|
|
ColorConfig::Never
|
|
|
|
};
|
|
|
|
Handler::with_tty_emitter(color_cfg, true, false, Some(codemap.clone()))
|
2018-01-01 00:40:51 -06:00
|
|
|
};
|
2016-03-01 16:27:19 -06:00
|
|
|
let mut parse_session = ParseSess::with_span_handler(tty_handler, codemap.clone());
|
|
|
|
|
2016-04-11 11:49:56 -05:00
|
|
|
let main_file = match input {
|
2017-12-08 07:16:47 -06:00
|
|
|
Input::File(ref file) => FileName::Real(file.clone()),
|
|
|
|
Input::Text(..) => FileName::Custom("stdin".to_owned()),
|
2016-04-11 11:49:56 -05:00
|
|
|
};
|
|
|
|
|
2018-02-15 07:48:06 -06:00
|
|
|
let krate = match parse_input(input, &parse_session, config) {
|
2016-04-11 11:49:56 -05:00
|
|
|
Ok(krate) => krate,
|
2018-03-08 19:51:27 -06:00
|
|
|
Err(err) => {
|
|
|
|
match err {
|
|
|
|
ParseError::Error(mut diagnostic) => diagnostic.emit(),
|
|
|
|
ParseError::Panic => {
|
|
|
|
// Note that if you see this message and want more information,
|
|
|
|
// then go to `parse_input` and run the parse function without
|
|
|
|
// `catch_unwind` so rustfmt panics and you can get a backtrace.
|
|
|
|
should_emit_verbose(&main_file, config, || {
|
|
|
|
println!("The Rust parser panicked")
|
|
|
|
});
|
|
|
|
}
|
|
|
|
ParseError::Recovered => {}
|
2016-04-27 14:08:44 -05:00
|
|
|
}
|
2016-04-14 18:51:50 -05:00
|
|
|
summary.add_parsing_error();
|
2016-05-15 04:41:05 -05:00
|
|
|
return Ok((summary, FileMap::new(), FormatReport::new()));
|
2016-04-11 11:49:56 -05:00
|
|
|
}
|
|
|
|
};
|
2015-10-28 01:41:32 -05:00
|
|
|
|
2017-11-14 07:42:48 -06:00
|
|
|
summary.mark_parse_time();
|
|
|
|
|
2015-10-28 01:41:32 -05:00
|
|
|
// Suppress error output after parsing.
|
2017-06-11 22:58:58 -05:00
|
|
|
let silent_emitter = Box::new(EmitterWriter::new(
|
|
|
|
Box::new(Vec::new()),
|
|
|
|
Some(codemap.clone()),
|
2017-10-20 08:53:06 -05:00
|
|
|
false,
|
2018-02-01 19:18:30 -06:00
|
|
|
false,
|
2017-06-11 22:58:58 -05:00
|
|
|
));
|
2016-03-01 16:27:19 -06:00
|
|
|
parse_session.span_diagnostic = Handler::with_emitter(true, false, silent_emitter);
|
2015-10-28 01:41:32 -05:00
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
let report = FormatReport::new();
|
2016-05-15 04:41:05 -05:00
|
|
|
|
2017-11-14 07:42:48 -06:00
|
|
|
let format_result = format_ast(
|
2017-05-31 22:08:09 -05:00
|
|
|
&krate,
|
2017-06-10 00:16:12 -05:00
|
|
|
&mut parse_session,
|
2017-05-31 22:08:09 -05:00
|
|
|
&main_file,
|
|
|
|
config,
|
2018-05-14 01:01:53 -05:00
|
|
|
report.clone(),
|
|
|
|
|file_name, file, skipped_range, report| {
|
2017-05-31 22:08:09 -05:00
|
|
|
// For some reason, the codemap does not include terminating
|
|
|
|
// newlines so we must add one on for each file. This is sad.
|
|
|
|
filemap::append_newline(file);
|
|
|
|
|
2018-05-14 01:01:53 -05:00
|
|
|
format_lines(file, file_name, skipped_range, config, report);
|
2017-05-31 22:08:09 -05:00
|
|
|
|
|
|
|
if let Some(ref mut out) = out {
|
|
|
|
return filemap::write_file(file, file_name, out, config);
|
|
|
|
}
|
|
|
|
Ok(false)
|
2017-06-03 08:50:44 -05:00
|
|
|
},
|
2017-11-14 07:42:48 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
summary.mark_format_time();
|
|
|
|
|
2018-02-12 05:58:38 -06:00
|
|
|
should_emit_verbose(&main_file, config, || {
|
2017-11-17 07:21:46 -06:00
|
|
|
fn duration_to_f32(d: Duration) -> f32 {
|
|
|
|
d.as_secs() as f32 + d.subsec_nanos() as f32 / 1_000_000_000f32
|
|
|
|
}
|
|
|
|
|
2017-11-14 07:42:48 -06:00
|
|
|
println!(
|
2017-11-17 07:21:46 -06:00
|
|
|
"Spent {0:.3} secs in the parsing phase, and {1:.3} secs in the formatting phase",
|
|
|
|
duration_to_f32(summary.get_parse_time().unwrap()),
|
|
|
|
duration_to_f32(summary.get_format_time().unwrap()),
|
2018-02-12 05:58:38 -06:00
|
|
|
)
|
|
|
|
});
|
2017-11-14 07:42:48 -06:00
|
|
|
|
|
|
|
match format_result {
|
2016-06-20 13:42:29 -05:00
|
|
|
Ok((file_map, has_diff)) => {
|
2016-05-15 04:41:05 -05:00
|
|
|
if report.has_warnings() {
|
|
|
|
summary.add_formatting_error();
|
|
|
|
}
|
2015-09-16 14:52:32 -05:00
|
|
|
|
2016-06-20 13:42:29 -05:00
|
|
|
if has_diff {
|
|
|
|
summary.add_diff();
|
|
|
|
}
|
|
|
|
|
2016-05-15 04:41:05 -05:00
|
|
|
Ok((summary, file_map, report))
|
|
|
|
}
|
|
|
|
Err(e) => Err((e, summary)),
|
2016-04-14 18:51:50 -05:00
|
|
|
}
|
2016-04-02 13:56:37 -05:00
|
|
|
}
|
2015-09-16 14:52:32 -05:00
|
|
|
|
2018-01-20 14:23:25 -06:00
|
|
|
/// A single span of changed lines, with 0 or more removed lines
|
|
|
|
/// and a vector of 0 or more inserted lines.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2018-04-20 05:10:48 -05:00
|
|
|
struct ModifiedChunk {
|
2018-02-12 17:07:46 -06:00
|
|
|
/// The first to be removed from the original text
|
|
|
|
pub line_number_orig: u32,
|
2018-01-20 14:23:25 -06:00
|
|
|
/// The number of lines which have been replaced
|
|
|
|
pub lines_removed: u32,
|
|
|
|
/// The new lines
|
|
|
|
pub lines: Vec<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set of changed sections of a file.
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2018-04-20 05:10:48 -05:00
|
|
|
struct ModifiedLines {
|
2018-01-20 14:23:25 -06:00
|
|
|
/// The set of changed chunks.
|
|
|
|
pub chunks: Vec<ModifiedChunk>,
|
|
|
|
}
|
|
|
|
|
2018-02-22 18:07:35 -06:00
|
|
|
/// The successful result of formatting via `get_modified_lines()`.
|
2018-04-20 05:10:48 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
struct ModifiedLinesResult {
|
2018-01-20 14:45:06 -06:00
|
|
|
/// The high level summary details
|
|
|
|
pub summary: Summary,
|
|
|
|
/// The result Filemap
|
|
|
|
pub filemap: FileMap,
|
|
|
|
/// Map of formatting errors
|
|
|
|
pub report: FormatReport,
|
|
|
|
/// The sets of updated lines.
|
|
|
|
pub modified_lines: ModifiedLines,
|
|
|
|
}
|
|
|
|
|
2018-01-20 14:23:25 -06:00
|
|
|
/// Format a file and return a `ModifiedLines` data structure describing
|
|
|
|
/// the changed ranges of lines.
|
2018-04-20 05:10:48 -05:00
|
|
|
#[cfg(test)]
|
|
|
|
fn get_modified_lines(
|
2018-01-20 14:23:25 -06:00
|
|
|
input: Input,
|
|
|
|
config: &Config,
|
2018-01-20 14:45:06 -06:00
|
|
|
) -> Result<ModifiedLinesResult, (io::Error, Summary)> {
|
2018-04-20 05:10:48 -05:00
|
|
|
use std::io::BufRead;
|
|
|
|
|
2018-01-20 14:23:25 -06:00
|
|
|
let mut data = Vec::new();
|
|
|
|
|
|
|
|
let mut config = config.clone();
|
|
|
|
config.set().write_mode(config::WriteMode::Modified);
|
2018-03-08 01:29:00 -06:00
|
|
|
let (summary, filemap, report) = format_input(input, &config, Some(&mut data))?;
|
2018-01-20 14:23:25 -06:00
|
|
|
|
|
|
|
let mut lines = data.lines();
|
|
|
|
let mut chunks = Vec::new();
|
|
|
|
while let Some(Ok(header)) = lines.next() {
|
|
|
|
// Parse the header line
|
|
|
|
let values: Vec<_> = header
|
|
|
|
.split(' ')
|
|
|
|
.map(|s| s.parse::<u32>().unwrap())
|
|
|
|
.collect();
|
|
|
|
assert_eq!(values.len(), 3);
|
2018-02-12 17:07:46 -06:00
|
|
|
let line_number_orig = values[0];
|
|
|
|
let lines_removed = values[1];
|
2018-01-20 14:23:25 -06:00
|
|
|
let num_added = values[2];
|
|
|
|
let mut added_lines = Vec::new();
|
|
|
|
for _ in 0..num_added {
|
|
|
|
added_lines.push(lines.next().unwrap().unwrap());
|
|
|
|
}
|
|
|
|
chunks.push(ModifiedChunk {
|
2018-02-12 17:07:46 -06:00
|
|
|
line_number_orig,
|
|
|
|
lines_removed,
|
2018-01-20 14:23:25 -06:00
|
|
|
lines: added_lines,
|
|
|
|
});
|
|
|
|
}
|
2018-01-20 14:45:06 -06:00
|
|
|
Ok(ModifiedLinesResult {
|
2018-03-08 01:29:00 -06:00
|
|
|
summary,
|
|
|
|
filemap,
|
|
|
|
report,
|
2018-02-12 17:07:46 -06:00
|
|
|
modified_lines: ModifiedLines { chunks },
|
2018-01-20 14:45:06 -06:00
|
|
|
})
|
2018-01-20 14:23:25 -06:00
|
|
|
}
|
|
|
|
|
2016-05-25 13:41:26 -05:00
|
|
|
#[derive(Debug)]
|
2016-04-02 13:56:37 -05:00
|
|
|
pub enum Input {
|
|
|
|
File(PathBuf),
|
|
|
|
Text(String),
|
2015-04-22 23:22:48 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
pub fn format_and_emit_report(input: Input, config: &Config) -> FmtResult<Summary> {
|
|
|
|
if !config.version_meets_requirement() {
|
2018-04-25 22:48:48 -05:00
|
|
|
return Err(format_err!("Version mismatch"));
|
2018-04-20 04:08:20 -05:00
|
|
|
}
|
2017-08-11 03:54:38 -05:00
|
|
|
let out = &mut stdout();
|
2016-05-15 04:41:05 -05:00
|
|
|
match format_input(input, config, Some(out)) {
|
|
|
|
Ok((summary, _, report)) => {
|
|
|
|
if report.has_warnings() {
|
2017-08-15 02:54:18 -05:00
|
|
|
match term::stderr() {
|
2017-11-01 07:45:35 -05:00
|
|
|
Some(ref t)
|
2018-05-06 01:22:29 -05:00
|
|
|
if use_colored_tty(config.color())
|
|
|
|
&& t.supports_color()
|
2017-11-09 14:23:12 -06:00
|
|
|
&& t.supports_attr(term::Attr::Bold) =>
|
2017-11-01 07:45:35 -05:00
|
|
|
{
|
2017-08-15 02:54:18 -05:00
|
|
|
match report.print_warnings_fancy(term::stderr().unwrap()) {
|
|
|
|
Ok(..) => (),
|
|
|
|
Err(..) => panic!("Unable to write to stderr: {}", report),
|
|
|
|
}
|
|
|
|
}
|
2018-04-20 04:08:20 -05:00
|
|
|
_ => eprintln!("{}", report),
|
2017-08-15 02:54:18 -05:00
|
|
|
}
|
2016-05-15 04:41:05 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
Ok(summary)
|
2016-05-15 04:41:05 -05:00
|
|
|
}
|
|
|
|
Err((msg, mut summary)) => {
|
2018-04-20 04:08:20 -05:00
|
|
|
eprintln!("Error writing files: {}", msg);
|
2016-05-15 04:41:05 -05:00
|
|
|
summary.add_operational_error();
|
2018-04-20 04:08:20 -05:00
|
|
|
Ok(summary)
|
2016-05-15 04:41:05 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
}
|
|
|
|
}
|
2017-12-24 08:40:53 -06:00
|
|
|
|
2018-04-20 04:08:20 -05:00
|
|
|
pub fn emit_pre_matter(config: &Config) -> FmtResult<()> {
|
|
|
|
if config.write_mode() == WriteMode::Checkstyle {
|
|
|
|
let mut out = &mut stdout();
|
|
|
|
checkstyle::output_header(&mut out)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn emit_post_matter(config: &Config) -> FmtResult<()> {
|
|
|
|
if config.write_mode() == WriteMode::Checkstyle {
|
|
|
|
let mut out = &mut stdout();
|
|
|
|
checkstyle::output_footer(&mut out)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-12-24 08:40:53 -06:00
|
|
|
#[cfg(test)]
|
2018-04-20 04:08:20 -05:00
|
|
|
mod unit_tests {
|
2018-02-19 10:26:29 -06:00
|
|
|
use super::{format_code_block, format_snippet, Config};
|
2017-12-24 08:40:53 -06:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_no_panic_on_format_snippet_and_format_code_block() {
|
|
|
|
// `format_snippet()` and `format_code_block()` should not panic
|
|
|
|
// even when we cannot parse the given snippet.
|
|
|
|
let snippet = "let";
|
|
|
|
assert!(format_snippet(snippet, &Config::default()).is_none());
|
|
|
|
assert!(format_code_block(snippet, &Config::default()).is_none());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn test_format_inner<F>(formatter: F, input: &str, expected: &str) -> bool
|
|
|
|
where
|
|
|
|
F: Fn(&str, &Config) -> Option<String>,
|
|
|
|
{
|
|
|
|
let output = formatter(input, &Config::default());
|
|
|
|
output.is_some() && output.unwrap() == expected
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_snippet() {
|
|
|
|
let snippet = "fn main() { println!(\"hello, world\"); }";
|
|
|
|
let expected = "fn main() {\n \
|
|
|
|
println!(\"hello, world\");\n\
|
|
|
|
}\n";
|
|
|
|
assert!(test_format_inner(format_snippet, snippet, expected));
|
|
|
|
}
|
|
|
|
|
2018-02-13 14:10:43 -06:00
|
|
|
#[test]
|
|
|
|
fn test_format_code_block_fail() {
|
2018-05-13 23:25:10 -05:00
|
|
|
#[rustfmt::skip]
|
2018-02-13 14:10:43 -06:00
|
|
|
let code_block = "this_line_is_100_characters_long_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(x, y, z);";
|
|
|
|
assert!(format_code_block(code_block, &Config::default()).is_none());
|
|
|
|
}
|
|
|
|
|
2017-12-24 08:40:53 -06:00
|
|
|
#[test]
|
|
|
|
fn test_format_code_block() {
|
|
|
|
// simple code block
|
|
|
|
let code_block = "let x=3;";
|
|
|
|
let expected = "let x = 3;";
|
|
|
|
assert!(test_format_inner(format_code_block, code_block, expected));
|
|
|
|
|
|
|
|
// more complex code block, taken from chains.rs.
|
|
|
|
let code_block =
|
|
|
|
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
|
|
|
|
(
|
|
|
|
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
|
|
|
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
|
|
|
|
)
|
|
|
|
} else if is_block_expr(context, &parent, &parent_rewrite) {
|
|
|
|
match context.config.indent_style() {
|
|
|
|
// Try to put the first child on the same line with parent's last line
|
|
|
|
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
|
|
|
|
// The parent is a block, so align the rest of the chain with the closing
|
|
|
|
// brace.
|
|
|
|
IndentStyle::Visual => (parent_shape, false),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
};
|
|
|
|
";
|
|
|
|
let expected =
|
|
|
|
"let (nested_shape, extend) = if !parent_rewrite_contains_newline && is_continuable(&parent) {
|
|
|
|
(
|
|
|
|
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
|
|
|
context.config.indent_style() == IndentStyle::Visual || is_small_parent,
|
|
|
|
)
|
|
|
|
} else if is_block_expr(context, &parent, &parent_rewrite) {
|
|
|
|
match context.config.indent_style() {
|
|
|
|
// Try to put the first child on the same line with parent's last line
|
|
|
|
IndentStyle::Block => (parent_shape.block_indent(context.config.tab_spaces()), true),
|
|
|
|
// The parent is a block, so align the rest of the chain with the closing
|
|
|
|
// brace.
|
|
|
|
IndentStyle::Visual => (parent_shape, false),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
chain_indent(context, shape.add_offset(parent_rewrite.len())),
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
};";
|
|
|
|
assert!(test_format_inner(format_code_block, code_block, expected));
|
|
|
|
}
|
|
|
|
}
|