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-02-15 07:47:32 -06:00
|
|
|
#![feature(custom_attribute)]
|
2018-01-29 06:59:15 -06:00
|
|
|
#![feature(decl_macro)]
|
2018-01-03 19:15:13 -06:00
|
|
|
#![feature(match_default_bindings)]
|
2018-01-01 00:40:51 -06:00
|
|
|
#![feature(type_ascription)]
|
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;
|
2015-03-07 16:46:35 -06:00
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2017-08-08 10:16:35 -05:00
|
|
|
extern crate regex;
|
|
|
|
extern crate rustc_errors as errors;
|
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
|
|
|
|
2017-07-13 04:42:14 -05:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::fmt;
|
2018-01-20 14:23:25 -06:00
|
|
|
use std::io::{self, stdout, BufRead, Write};
|
2017-08-15 02:54:07 -05:00
|
|
|
use std::iter::repeat;
|
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 errors::{DiagnosticBuilder, Handler};
|
|
|
|
use errors::emitter::{ColorConfig, EmitterWriter};
|
|
|
|
use syntax::ast;
|
2017-09-17 01:32:00 -05:00
|
|
|
use syntax::codemap::{CodeMap, FilePathMapping};
|
2017-12-08 07:16:47 -06:00
|
|
|
pub use syntax::codemap::FileName;
|
2017-07-13 04:42:14 -05:00
|
|
|
use syntax::parse::{self, ParseSess};
|
|
|
|
|
|
|
|
use checkstyle::{output_footer, output_header};
|
2017-12-09 05:24:14 -06:00
|
|
|
use comment::{CharClasses, FullCodeCharKind};
|
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-02-07 08:15:00 -06:00
|
|
|
pub use config::Config;
|
|
|
|
pub use config::summary::Summary;
|
2015-03-07 16:46:35 -06: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;
|
2016-01-12 23:22:30 -06:00
|
|
|
mod checkstyle;
|
2017-11-12 20:26:33 -06:00
|
|
|
mod closures;
|
2018-01-29 07:00:07 -06:00
|
|
|
pub mod codemap;
|
|
|
|
mod comment;
|
2018-03-01 19:28:34 -06:00
|
|
|
pub mod config;
|
2015-04-21 04:01:19 -05:00
|
|
|
mod expr;
|
2018-01-29 07:00:07 -06:00
|
|
|
pub 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-01-29 07:00:07 -06:00
|
|
|
mod missed_spans;
|
|
|
|
pub mod modules;
|
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;
|
|
|
|
pub mod rustfmt_diff;
|
|
|
|
mod shape;
|
|
|
|
mod spanned;
|
|
|
|
mod string;
|
|
|
|
mod types;
|
2017-07-03 04:54:41 -05:00
|
|
|
mod vertical;
|
2018-01-29 07:00:07 -06:00
|
|
|
pub 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
|
|
|
|
pub type FileMap = Vec<FileRecord>;
|
|
|
|
|
|
|
|
pub type FileRecord = (FileName, String);
|
|
|
|
|
2017-12-09 05:24:14 -06:00
|
|
|
#[derive(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)
|
|
|
|
LineOverflow(usize, usize),
|
2015-06-08 18:42:29 -05:00
|
|
|
// Line ends in whitespace
|
|
|
|
TrailingWhitespace,
|
|
|
|
// TO-DO or FIX-ME item without an issue number
|
|
|
|
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
|
|
|
|
LicenseCheck,
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for ErrorKind {
|
|
|
|
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
|
|
match *self {
|
2017-07-11 07:53:10 -05:00
|
|
|
ErrorKind::LineOverflow(found, maximum) => write!(
|
|
|
|
fmt,
|
2017-08-15 02:54:07 -05:00
|
|
|
"line exceeded maximum width (maximum: {}, found: {})",
|
2017-11-30 22:30:21 -06:00
|
|
|
maximum, found
|
2017-07-11 07:53:10 -05:00
|
|
|
),
|
2015-11-14 14:57:31 -06:00
|
|
|
ErrorKind::TrailingWhitespace => write!(fmt, "left behind trailing whitespace"),
|
|
|
|
ErrorKind::BadIssue(issue) => write!(fmt, "found {}", 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
|
|
|
ErrorKind::LicenseCheck => write!(fmt, "license check failed"),
|
2015-06-08 18:42:29 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-17 13:21:06 -05:00
|
|
|
// Formatting errors that are identified *after* rustfmt has run.
|
|
|
|
pub 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 {
|
|
|
|
fn msg_prefix(&self) -> &str {
|
|
|
|
match self.kind {
|
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
|
|
|
ErrorKind::LineOverflow(..)
|
|
|
|
| ErrorKind::TrailingWhitespace
|
2018-02-19 10:26:29 -06:00
|
|
|
| ErrorKind::LicenseCheck => "error:",
|
2015-07-15 21:17:07 -05:00
|
|
|
ErrorKind::BadIssue(_) => "WARNING:",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
pub fn format_len(&self) -> (usize, usize) {
|
|
|
|
match self.kind {
|
|
|
|
ErrorKind::LineOverflow(found, max) => (max, found - max),
|
|
|
|
ErrorKind::TrailingWhitespace => {
|
|
|
|
let trailing_ws_len = self.line_buffer
|
|
|
|
.chars()
|
|
|
|
.rev()
|
|
|
|
.take_while(|c| c.is_whitespace())
|
|
|
|
.count();
|
|
|
|
(self.line_buffer.len() - trailing_ws_len, trailing_ws_len)
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2015-09-17 13:21:06 -05:00
|
|
|
pub struct FormatReport {
|
|
|
|
// Maps stringified file paths to their associated formatting errors.
|
2017-12-08 07:16:47 -06:00
|
|
|
file_error_map: 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 {
|
|
|
|
file_error_map: HashMap::new(),
|
|
|
|
}
|
2016-04-14 18:51:50 -05:00
|
|
|
}
|
|
|
|
|
2015-09-17 13:21:06 -05:00
|
|
|
pub fn warning_count(&self) -> usize {
|
2017-03-06 14:40:08 -06:00
|
|
|
self.file_error_map
|
|
|
|
.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
|
|
|
|
|
|
|
pub fn has_warnings(&self) -> bool {
|
|
|
|
self.warning_count() > 0
|
|
|
|
}
|
2017-08-15 02:54:18 -05:00
|
|
|
|
|
|
|
pub fn print_warnings_fancy(
|
|
|
|
&self,
|
|
|
|
mut t: Box<term::Terminal<Output = io::Stderr>>,
|
|
|
|
) -> Result<(), term::Error> {
|
|
|
|
for (file, errors) in &self.file_error_map {
|
|
|
|
for error in errors {
|
|
|
|
let prefix_space_len = error.line.to_string().len();
|
|
|
|
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
|
|
|
|
|
|
|
|
// 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)?;
|
|
|
|
write!(t, "{}\n", error.kind)?;
|
|
|
|
|
|
|
|
// Second line: file info
|
|
|
|
write!(t, "{}--> ", &prefix_spaces[1..])?;
|
|
|
|
t.reset()?;
|
|
|
|
write!(t, "{}:{}\n", file, error.line)?;
|
|
|
|
|
|
|
|
// 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()?;
|
|
|
|
write!(t, "{}\n", error.line_buffer)?;
|
|
|
|
t.attr(term::Attr::Bold)?;
|
|
|
|
write!(t, "{}| ", prefix_spaces)?;
|
|
|
|
t.fg(term::color::RED)?;
|
|
|
|
write!(t, "{}\n", target_str(space_len, target_len))?;
|
|
|
|
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()?;
|
|
|
|
write!(t, "{}\n", error.msg_suffix())?;
|
|
|
|
} else {
|
|
|
|
write!(t, "\n")?;
|
|
|
|
}
|
|
|
|
t.reset()?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !self.file_error_map.is_empty() {
|
|
|
|
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 {
|
|
|
|
let empty_line: String = repeat(" ").take(space_len).collect();
|
|
|
|
let overflowed: String = repeat("^").take(target_len).collect();
|
|
|
|
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> {
|
2015-09-04 16:39:33 -05:00
|
|
|
for (file, errors) in &self.file_error_map {
|
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();
|
|
|
|
let prefix_spaces: String = repeat(" ").take(1 + prefix_space_len).collect();
|
|
|
|
|
|
|
|
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)
|
|
|
|
};
|
|
|
|
|
2017-06-11 22:58:58 -05:00
|
|
|
write!(
|
|
|
|
fmt,
|
2017-08-15 02:54:07 -05:00
|
|
|
"{}\n{}\n{}\n{}{}\n",
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
2017-08-15 02:54:07 -05:00
|
|
|
if !self.file_error_map.is_empty() {
|
|
|
|
write!(
|
|
|
|
fmt,
|
|
|
|
"warning: rustfmt may have failed to format. See previous {} errors.\n",
|
|
|
|
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(),
|
|
|
|
{
|
|
|
|
if config.verbose() && path.to_string() != STDIN {
|
|
|
|
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,
|
|
|
|
mut after_file: F,
|
|
|
|
) -> Result<(FileMap, bool), io::Error>
|
|
|
|
where
|
2017-12-08 07:16:47 -06:00
|
|
|
F: FnMut(&FileName, &mut String, &[(usize, usize)]) -> 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
|
|
|
|
2016-04-28 00:38:04 -05:00
|
|
|
// We always skip children for the "Plain" write mode, since there is
|
|
|
|
// nothing to distinguish the nested module contents.
|
2017-05-16 03:47:09 -05:00
|
|
|
let skip_children = config.skip_children() || config.write_mode() == config::WriteMode::Plain;
|
2017-11-06 06:45:54 -06:00
|
|
|
for (path, module) in modules::list_files(krate, parse_session.codemap())? {
|
2017-12-08 07:16:47 -06:00
|
|
|
if skip_children && path != *main_file {
|
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);
|
|
|
|
let mut visitor = FmtVisitor::from_codemap(parse_session, config, &snippet_provider);
|
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,
|
|
|
|
::utils::count_newlines(&format!("{}", visitor.buffer))
|
|
|
|
);
|
|
|
|
|
2017-12-08 07:16:47 -06:00
|
|
|
let filename = path.clone();
|
|
|
|
has_diff |= match after_file(&filename, &mut visitor.buffer, &visitor.skipped_range) {
|
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
|
|
|
|
2017-12-08 07:16:47 -06:00
|
|
|
result.push((filename, 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
|
|
|
}
|
|
|
|
|
2017-12-08 02:46:43 -06:00
|
|
|
/// Returns true if the line with the given line number was skipped by `#[rustfmt_skip]`.
|
|
|
|
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,
|
|
|
|
report: &mut FormatReport,
|
|
|
|
) {
|
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());
|
2017-12-10 18:39:06 -06: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
|
|
|
|
2017-12-08 07:16:47 -06:00
|
|
|
report.file_error_map.insert(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,
|
|
|
|
) -> Result<ast::Crate, Option<DiagnosticBuilder<'sess>>> {
|
2016-04-27 14:08:44 -05:00
|
|
|
let result = match input {
|
2017-01-17 22:22:01 -06:00
|
|
|
Input::File(file) => {
|
|
|
|
let mut parser = parse::new_parser_from_file(parse_session, &file);
|
|
|
|
parser.cfg_mods = false;
|
2018-02-15 07:48:06 -06:00
|
|
|
if config.skip_children() {
|
|
|
|
parser.recurse_into_file_modules = false;
|
|
|
|
}
|
2017-01-17 22:22:01 -06:00
|
|
|
parser.parse_crate_mod()
|
|
|
|
}
|
2016-04-11 11:49:56 -05:00
|
|
|
Input::Text(text) => {
|
2017-12-08 07:16:47 -06:00
|
|
|
let mut parser = parse::new_parser_from_source_str(
|
|
|
|
parse_session,
|
|
|
|
FileName::Custom("stdin".to_owned()),
|
|
|
|
text,
|
|
|
|
);
|
2017-01-17 22:22:01 -06:00
|
|
|
parser.cfg_mods = false;
|
2018-02-15 07:48:06 -06:00
|
|
|
if config.skip_children() {
|
|
|
|
parser.recurse_into_file_modules = false;
|
|
|
|
}
|
2017-01-17 22:22:01 -06:00
|
|
|
parser.parse_crate_mod()
|
2016-04-11 11:49:56 -05:00
|
|
|
}
|
2016-04-27 14:08:44 -05:00
|
|
|
};
|
|
|
|
|
2016-05-17 16:58:51 -05:00
|
|
|
match result {
|
|
|
|
Ok(c) => {
|
|
|
|
if parse_session.span_diagnostic.has_errors() {
|
|
|
|
// Bail out if the parser recovered from an error.
|
|
|
|
Err(None)
|
|
|
|
} else {
|
|
|
|
Ok(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(e) => Err(Some(e)),
|
2016-04-14 18:51:50 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
config.set().write_mode(config::WriteMode::Plain);
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
let fn_main_prefix = "fn main() {\n";
|
|
|
|
let snippet = fn_main_prefix.to_owned() + code_snippet + "\n}";
|
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"
|
|
|
|
let block_len = formatted.len().checked_sub(2).unwrap_or(0);
|
|
|
|
for line in formatted[fn_main_prefix.len()..block_len].lines() {
|
|
|
|
if !is_first {
|
|
|
|
result.push('\n');
|
|
|
|
} else {
|
|
|
|
is_first = false;
|
|
|
|
}
|
|
|
|
let trimmed_line = if line.len() > config.max_width() {
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
Some(result)
|
2017-12-24 08:40:53 -06:00
|
|
|
}
|
|
|
|
|
2017-06-11 22:58:58 -05:00
|
|
|
pub fn format_input<T: Write>(
|
|
|
|
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,
|
2016-04-27 14:08:44 -05:00
|
|
|
Err(diagnostic) => {
|
|
|
|
if let Some(mut diagnostic) = diagnostic {
|
|
|
|
diagnostic.emit();
|
|
|
|
}
|
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();
|
|
|
|
|
2016-04-14 18:51:50 -05:00
|
|
|
if parse_session.span_diagnostic.has_errors() {
|
|
|
|
summary.add_parsing_error();
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2016-05-15 04:41:05 -05:00
|
|
|
let mut report = FormatReport::new();
|
|
|
|
|
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,
|
2017-12-08 02:46:43 -06:00
|
|
|
|file_name, file, skipped_range| {
|
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);
|
|
|
|
|
2017-12-08 02:46:43 -06:00
|
|
|
format_lines(file, file_name, skipped_range, config, &mut 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)]
|
|
|
|
pub 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)]
|
|
|
|
pub struct ModifiedLines {
|
|
|
|
/// 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-01-20 14:45:06 -06:00
|
|
|
pub struct ModifiedLinesResult {
|
|
|
|
/// 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.
|
|
|
|
pub fn get_modified_lines(
|
|
|
|
input: Input,
|
|
|
|
config: &Config,
|
2018-01-20 14:45:06 -06:00
|
|
|
) -> Result<ModifiedLinesResult, (io::Error, Summary)> {
|
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);
|
|
|
|
let (summary, filemap, formatreport) = format_input(input, &config, Some(&mut data))?;
|
|
|
|
|
|
|
|
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 {
|
|
|
|
summary: summary,
|
|
|
|
filemap: filemap,
|
|
|
|
report: formatreport,
|
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
|
|
|
|
2016-04-14 18:51:50 -05:00
|
|
|
pub fn run(input: Input, config: &Config) -> Summary {
|
2017-08-11 03:54:38 -05:00
|
|
|
let out = &mut stdout();
|
2017-05-16 03:47:09 -05:00
|
|
|
output_header(out, config.write_mode()).ok();
|
2016-05-15 04:41:05 -05:00
|
|
|
match format_input(input, config, Some(out)) {
|
|
|
|
Ok((summary, _, report)) => {
|
2017-05-16 03:47:09 -05:00
|
|
|
output_footer(out, config.write_mode()).ok();
|
2016-05-15 04:41:05 -05:00
|
|
|
|
|
|
|
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)
|
2017-11-13 03:10:46 -06: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),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => msg!("{}", report),
|
|
|
|
}
|
2016-05-15 04:41:05 -05:00
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
|
2016-05-15 04:41:05 -05:00
|
|
|
summary
|
|
|
|
}
|
|
|
|
Err((msg, mut summary)) => {
|
|
|
|
msg!("Error writing files: {}", msg);
|
|
|
|
summary.add_operational_error();
|
|
|
|
summary
|
|
|
|
}
|
2015-11-02 13:45:45 -06:00
|
|
|
}
|
|
|
|
}
|
2017-12-24 08:40:53 -06:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
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-02-15 07:47:32 -06: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));
|
|
|
|
}
|
|
|
|
}
|