From a6de57269c90ca89fb1e98b91ad9ab200295df0a Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Thu, 21 Apr 2016 01:24:18 -0400 Subject: [PATCH 01/11] Derive Debug for the Input enum --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index f63b2caaa94..161ce14e22b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -433,6 +433,7 @@ pub fn format_input(input: Input, config: &Config) -> (Summary, FileMap, FormatR (summary, file_map, report) } +#[derive(Debug)] pub enum Input { File(PathBuf), Text(String), From a07a6c10199808eae25a9bfe170bda9b16c5820d Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Mon, 7 Mar 2016 12:16:36 -0500 Subject: [PATCH 02/11] visitor: Add debug log for FmtVisitor::visit_stmt() --- src/visitor.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/visitor.rs b/src/visitor.rs index d52a167cb5e..af48406ec26 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -34,6 +34,10 @@ pub struct FmtVisitor<'a> { impl<'a> FmtVisitor<'a> { fn visit_stmt(&mut self, stmt: &ast::Stmt) { + debug!("visit_stmt: {:?} {:?}", + self.codemap.lookup_char_pos(stmt.span.lo), + self.codemap.lookup_char_pos(stmt.span.hi)); + match stmt.node { ast::StmtKind::Decl(ref decl, _) => { if let ast::DeclKind::Item(ref item) = decl.node { From 83db7ed70b0d735a714cc3614032818861275582 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Wed, 20 Apr 2016 23:42:36 -0400 Subject: [PATCH 03/11] config: Allow excluding config settings from --config-help output This is preparation for restricting formatting to specific line ranges. That option does not make sense for use in the config file, and so should not appear in config documentation. Refs #434 --- src/config.rs | 145 ++++++++++++++++++++++++++++---------------------- 1 file changed, 80 insertions(+), 65 deletions(-) diff --git a/src/config.rs b/src/config.rs index 5632e9a925c..cd9c25eae60 100644 --- a/src/config.rs +++ b/src/config.rs @@ -219,8 +219,18 @@ impl ConfigHelpItem { } } +/// Controls if a config field is printed in docs. +enum ConfigDoc { + /// Include in docs. + Doc, + /// Do not include in docs. + #[allow(dead_code)] + NoDoc, +} +use self::ConfigDoc::*; + macro_rules! create_config { - ($($i:ident: $ty:ty, $def:expr, $( $dstring:expr ),+ );+ $(;)*) => ( + ($($doc:ident $i:ident: $ty:ty, $def:expr, $( $dstring:expr ),+ );+ $(;)*) => ( #[derive(RustcDecodable, Clone)] pub struct Config { $(pub $i: $ty),+ @@ -286,21 +296,23 @@ macro_rules! create_config { } println!("Configuration Options:"); $( - let name_raw = stringify!($i); - let mut name_out = String::with_capacity(max); - for _ in name_raw.len()..max-1 { - name_out.push(' ') + if let ConfigDoc::Doc = $doc { + let name_raw = stringify!($i); + let mut name_out = String::with_capacity(max); + for _ in name_raw.len()..max-1 { + name_out.push(' ') + } + name_out.push_str(name_raw); + name_out.push(' '); + println!("{}{} Default: {:?}", + name_out, + <$ty>::doc_hint(), + $def); + $( + println!("{}{}", space_str, $dstring); + )+ + println!(""); } - name_out.push_str(name_raw); - name_out.push(' '); - println!("{}{} Default: {:?}", - name_out, - <$ty>::doc_hint(), - $def); - $( - println!("{}{}", space_str, $dstring); - )+ - println!(""); )+ } } @@ -319,71 +331,74 @@ macro_rules! create_config { } create_config! { - verbose: bool, false, "Use verbose output"; - skip_children: bool, false, "Don't reformat out of line modules"; - max_width: usize, 100, "Maximum width of each line"; - ideal_width: usize, 80, "Ideal width of each line"; - tab_spaces: usize, 4, "Number of spaces per tab"; - fn_call_width: usize, 60, + Doc verbose: bool, false, "Use verbose output"; + Doc skip_children: bool, false, "Don't reformat out of line modules"; + Doc max_width: usize, 100, "Maximum width of each line"; + Doc ideal_width: usize, 80, "Ideal width of each line"; + Doc tab_spaces: usize, 4, "Number of spaces per tab"; + Doc fn_call_width: usize, 60, "Maximum width of the args of a function call before falling back to vertical formatting"; - struct_lit_width: usize, 16, + Doc struct_lit_width: usize, 16, "Maximum width in the body of a struct lit before falling back to vertical formatting"; - force_explicit_abi: bool, true, "Always print the abi for extern items"; - newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings"; - fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions"; - item_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for structs and enums"; - else_if_brace_style: ElseIfBraceStyle, ElseIfBraceStyle::AlwaysSameLine, + Doc force_explicit_abi: bool, true, "Always print the abi for extern items"; + Doc newline_style: NewlineStyle, NewlineStyle::Unix, "Unix or Windows line endings"; + Doc fn_brace_style: BraceStyle, BraceStyle::SameLineWhere, "Brace style for functions"; + Doc item_brace_style: BraceStyle, BraceStyle::SameLineWhere, + "Brace style for structs and enums"; + Doc else_if_brace_style: ElseIfBraceStyle, ElseIfBraceStyle::AlwaysSameLine, "Brace style for if, else if, and else constructs"; - control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, + Doc control_brace_style: ControlBraceStyle, ControlBraceStyle::AlwaysSameLine, "Brace style for match, loop, for, and while constructs"; - impl_empty_single_line: bool, true, "Put empty-body implementations on a single line"; - fn_empty_single_line: bool, true, "Put empty-body functions on a single line"; - fn_single_line: bool, false, "Put single-expression functions on a single line"; - fn_return_indent: ReturnIndent, ReturnIndent::WithArgs, + Doc impl_empty_single_line: bool, true, "Put empty-body implementations on a single line"; + Doc fn_empty_single_line: bool, true, "Put empty-body functions on a single line"; + Doc fn_single_line: bool, false, "Put single-expression functions on a single line"; + Doc fn_return_indent: ReturnIndent, ReturnIndent::WithArgs, "Location of return type in function declaration"; - fn_args_paren_newline: bool, true, "If function argument parenthesis goes on a newline"; - fn_args_density: Density, Density::Tall, "Argument density in functions"; - fn_args_layout: FnArgLayoutStyle, FnArgLayoutStyle::Visual, "Layout of function arguments"; - fn_arg_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indent on function arguments"; - type_punctuation_density: TypeDensity, TypeDensity::Wide, + Doc fn_args_paren_newline: bool, true, "If function argument parenthesis goes on a newline"; + Doc fn_args_density: Density, Density::Tall, "Argument density in functions"; + Doc fn_args_layout: FnArgLayoutStyle, FnArgLayoutStyle::Visual, "Layout of function arguments"; + Doc fn_arg_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indent on function arguments"; + Doc type_punctuation_density: TypeDensity, TypeDensity::Wide, "Determines if '+' or '=' are wrapped in spaces in the punctuation of types"; // Should we at least try to put the where clause on the same line as the rest of the // function decl? - where_density: Density, Density::CompressedIfEmpty, "Density of a where clause"; + Doc where_density: Density, Density::CompressedIfEmpty, "Density of a where clause"; // Visual will be treated like Tabbed - where_indent: BlockIndentStyle, BlockIndentStyle::Tabbed, "Indentation of a where clause"; - where_layout: ListTactic, ListTactic::Vertical, "Element layout inside a where clause"; - where_pred_indent: BlockIndentStyle, BlockIndentStyle::Visual, + Doc where_indent: BlockIndentStyle, BlockIndentStyle::Tabbed, "Indentation of a where clause"; + Doc where_layout: ListTactic, ListTactic::Vertical, "Element layout inside a where clause"; + Doc where_pred_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indentation style of a where predicate"; - where_trailing_comma: bool, false, "Put a trailing comma on where clauses"; - generics_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indentation of generics"; - struct_trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, + Doc where_trailing_comma: bool, false, "Put a trailing comma on where clauses"; + Doc generics_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indentation of generics"; + Doc struct_trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, "If there is a trailing comma on structs"; - struct_lit_trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, + Doc struct_lit_trailing_comma: SeparatorTactic, SeparatorTactic::Vertical, "If there is a trailing comma on literal structs"; - struct_lit_style: StructLitStyle, StructLitStyle::Block, "Style of struct definition"; - struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle, + Doc struct_lit_style: StructLitStyle, StructLitStyle::Block, "Style of struct definition"; + Doc struct_lit_multiline_style: MultilineStyle, MultilineStyle::PreferSingle, "Multiline style on literal structs"; - enum_trailing_comma: bool, true, "Put a trailing comma on enum declarations"; - report_todo: ReportTactic, ReportTactic::Never, + Doc enum_trailing_comma: bool, true, "Put a trailing comma on enum declarations"; + Doc report_todo: ReportTactic, ReportTactic::Never, "Report all, none or unnumbered occurrences of TODO in source file comments"; - report_fixme: ReportTactic, ReportTactic::Never, + Doc report_fixme: ReportTactic, ReportTactic::Never, "Report all, none or unnumbered occurrences of FIXME in source file comments"; - chain_base_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indent on chain base"; - chain_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indentation of chain"; - reorder_imports: bool, false, "Reorder import statements alphabetically"; - single_line_if_else: bool, false, "Put else on same line as closing brace for if statements"; - format_strings: bool, true, "Format string literals where necessary"; - force_format_strings: bool, false, "Always format string literals"; - chains_overflow_last: bool, true, "Allow last call in method chain to break the line"; - take_source_hints: bool, true, "Retain some formatting characteristics from the source code"; - hard_tabs: bool, false, "Use tab characters for indentation, spaces for alignment"; - wrap_comments: bool, false, "Break comments to fit on the line"; - normalise_comments: bool, true, "Convert /* */ comments to // comments where possible"; - wrap_match_arms: bool, true, "Wrap multiline match arms in blocks"; - match_block_trailing_comma: bool, false, + Doc chain_base_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indent on chain base"; + Doc chain_indent: BlockIndentStyle, BlockIndentStyle::Visual, "Indentation of chain"; + Doc reorder_imports: bool, false, "Reorder import statements alphabetically"; + Doc single_line_if_else: bool, false, + "Put else on same line as closing brace for if statements"; + Doc format_strings: bool, true, "Format string literals where necessary"; + Doc force_format_strings: bool, false, "Always format string literals"; + Doc chains_overflow_last: bool, true, "Allow last call in method chain to break the line"; + Doc take_source_hints: bool, true, + "Retain some formatting characteristics from the source code"; + Doc hard_tabs: bool, false, "Use tab characters for indentation, spaces for alignment"; + Doc wrap_comments: bool, false, "Break comments to fit on the line"; + Doc normalise_comments: bool, true, "Convert /* */ comments to // comments where possible"; + Doc wrap_match_arms: bool, true, "Wrap multiline match arms in blocks"; + Doc match_block_trailing_comma: bool, false, "Put a trailing comma after a block based match arm (non-block arms are not affected)"; - match_wildcard_trailing_comma: bool, true, "Put a trailing comma after a wildcard arm"; - write_mode: WriteMode, WriteMode::Replace, + Doc match_wildcard_trailing_comma: bool, true, "Put a trailing comma after a wildcard arm"; + Doc write_mode: WriteMode, WriteMode::Replace, "What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage"; } From d45d82fcf61ca8bf5cf4b73fc7664ec995cb629e Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Sat, 16 Apr 2016 10:29:59 -0400 Subject: [PATCH 04/11] config: Move override_value() parsing method to ConfigType trait This commit moves `Config::override_value()` parsing from `FromStr` to our own trait. This allows more control over parsing, and in particular allows us to define parsing on types from `std` such as `Option`. This will be used to handle restricting formatting to specific line ranges. Refs #434 --- src/bin/rustfmt.rs | 5 ++--- src/config.rs | 25 ++++++++++++++++++++++++- src/utils.rs | 24 +++++++++++------------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 472098f59eb..6356d0372a7 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -18,13 +18,12 @@ extern crate env_logger; extern crate getopts; use rustfmt::{run, Input, Summary}; -use rustfmt::config::{Config, WriteMode}; +use rustfmt::config::{Config, ConfigType, WriteMode}; use std::{env, error}; use std::fs::{self, File}; use std::io::{self, ErrorKind, Read, Write}; use std::path::{Path, PathBuf}; -use std::str::FromStr; use getopts::{Matches, Options}; @@ -66,7 +65,7 @@ impl CliOptions { options.verbose = matches.opt_present("verbose"); if let Some(ref write_mode) = matches.opt_str("write-mode") { - if let Ok(write_mode) = WriteMode::from_str(write_mode) { + if let Ok(write_mode) = WriteMode::parse(write_mode) { options.write_mode = Some(write_mode); } else { return Err(FmtError::from(format!("Invalid write-mode: {}", write_mode))); diff --git a/src/config.rs b/src/config.rs index cd9c25eae60..6d4e307c204 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,7 @@ extern crate toml; +use std::str; use lists::{SeparatorTactic, ListTactic}; use std::io::Write; @@ -171,27 +172,49 @@ configuration_option_enum! { WriteMode: /// Trait for types that can be used in `Config`. pub trait ConfigType: Sized { + /// The error type for `parse()`. + type ParseErr; /// Returns hint text for use in `Config::print_docs()`. For enum types, this is a /// pipe-separated list of variants; for other types it returns "". fn doc_hint() -> String; + /// Parses a string for use as an override in `Config::override_value()`. + fn parse(s: &str) -> Result; } impl ConfigType for bool { + type ParseErr = ::Err; + fn doc_hint() -> String { String::from("") } + + fn parse(s: &str) -> Result { + s.parse() + } } impl ConfigType for usize { + type ParseErr = ::Err; + fn doc_hint() -> String { String::from("") } + + fn parse(s: &str) -> Result { + s.parse() + } } impl ConfigType for String { + type ParseErr = ::Err; + fn doc_hint() -> String { String::from("") } + + fn parse(s: &str) -> Result { + s.parse() + } } pub struct ConfigHelpItem { @@ -275,7 +298,7 @@ macro_rules! create_config { match key { $( stringify!($i) => { - self.$i = val.parse::<$ty>() + self.$i = <$ty>::parse(val) .expect(&format!("Failed to parse override for {} (\"{}\") as a {}", stringify!($i), val, diff --git a/src/utils.rs b/src/utils.rs index 667c9cd7efd..ea8270e9e9d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -207,10 +207,18 @@ macro_rules! impl_enum_decodable { } } - impl ::std::str::FromStr for $e { - type Err = &'static str; + impl ::config::ConfigType for $e { + type ParseErr = &'static str; + + fn doc_hint() -> String { + let mut variants = Vec::new(); + $( + variants.push(stringify!($x)); + )* + format!("[{}]", variants.join("|")) + } - fn from_str(s: &str) -> Result { + fn parse(s: &str) -> Result { use std::ascii::AsciiExt; $( if stringify!($x).eq_ignore_ascii_case(s) { @@ -220,16 +228,6 @@ macro_rules! impl_enum_decodable { Err("Bad variant") } } - - impl ::config::ConfigType for $e { - fn doc_hint() -> String { - let mut variants = Vec::new(); - $( - variants.push(stringify!($x)); - )* - format!("[{}]", variants.join("|")) - } - } }; } From 39a4ee4e2dab0e4356ef5cda0329ac123e52f908 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Wed, 20 Apr 2016 23:42:36 -0400 Subject: [PATCH 05/11] utils: Move codemap related utilities to a dedicated module This commit adds a `codemap` module, and moves the `CodemapSpanUtils` added in #857 to it. This is preparation for adding more `Codemap` specific utilities. Refs #434 --- src/codemap.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/expr.rs | 5 +++-- src/imports.rs | 2 +- src/items.rs | 5 +++-- src/lib.rs | 1 + src/macros.rs | 3 ++- src/patterns.rs | 3 ++- src/types.rs | 3 ++- src/utils.rs | 39 +-------------------------------------- src/visitor.rs | 3 ++- 10 files changed, 56 insertions(+), 47 deletions(-) create mode 100644 src/codemap.rs diff --git a/src/codemap.rs b/src/codemap.rs new file mode 100644 index 00000000000..c70decd8422 --- /dev/null +++ b/src/codemap.rs @@ -0,0 +1,39 @@ +use syntax::codemap::{BytePos, CodeMap, Span}; + +use comment::FindUncommented; + +pub trait SpanUtils { + fn span_after(&self, original: Span, needle: &str) -> BytePos; + fn span_after_last(&self, original: Span, needle: &str) -> BytePos; + fn span_before(&self, original: Span, needle: &str) -> BytePos; +} + +impl SpanUtils for CodeMap { + #[inline] + fn span_after(&self, original: Span, needle: &str) -> BytePos { + let snippet = self.span_to_snippet(original).unwrap(); + let offset = snippet.find_uncommented(needle).unwrap() + needle.len(); + + original.lo + BytePos(offset as u32) + } + + #[inline] + fn span_after_last(&self, original: Span, needle: &str) -> BytePos { + let snippet = self.span_to_snippet(original).unwrap(); + let mut offset = 0; + + while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) { + offset += additional_offset + needle.len(); + } + + original.lo + BytePos(offset as u32) + } + + #[inline] + fn span_before(&self, original: Span, needle: &str) -> BytePos { + let snippet = self.span_to_snippet(original).unwrap(); + let offset = snippet.find_uncommented(needle).unwrap(); + + original.lo + BytePos(offset as u32) + } +} diff --git a/src/expr.rs b/src/expr.rs index 248cc7e090a..3c8809c5faa 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -16,12 +16,13 @@ use std::iter::ExactSizeIterator; use std::fmt::Write; use {Indent, Spanned}; +use codemap::SpanUtils; use rewrite::{Rewrite, RewriteContext}; use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic, DefinitiveListTactic, definitive_tactic, ListItem, format_item_list}; use string::{StringFormat, rewrite_string}; -use utils::{CodeMapSpanUtils, extra_offset, last_line_width, wrap_str, binary_search, - first_line_width, semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr}; +use utils::{extra_offset, last_line_width, wrap_str, binary_search, first_line_width, + semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr}; use visitor::FmtVisitor; use config::{Config, StructLitStyle, MultilineStyle, ElseIfBraceStyle, ControlBraceStyle}; use comment::{FindUncommented, rewrite_comment, contains_comment, recover_comment_removed}; diff --git a/src/imports.rs b/src/imports.rs index c7cedc63a98..8fae8173446 100644 --- a/src/imports.rs +++ b/src/imports.rs @@ -9,9 +9,9 @@ // except according to those terms. use Indent; +use codemap::SpanUtils; use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, definitive_tactic}; use types::rewrite_path; -use utils::CodeMapSpanUtils; use rewrite::{Rewrite, RewriteContext}; use syntax::ast; diff --git a/src/items.rs b/src/items.rs index 2566d9a436b..03b4093cb78 100644 --- a/src/items.rs +++ b/src/items.rs @@ -11,8 +11,9 @@ // Formatting top-level items - functions, structs, enums, traits, impls. use Indent; -use utils::{CodeMapSpanUtils, format_mutability, format_visibility, contains_skip, end_typaram, - wrap_str, last_line_width, semicolon_for_expr, format_unsafety, trim_newlines}; +use codemap::SpanUtils; +use utils::{format_mutability, format_visibility, contains_skip, end_typaram, wrap_str, + last_line_width, semicolon_for_expr, format_unsafety, trim_newlines}; use lists::{write_list, itemize_list, ListItem, ListFormatting, SeparatorTactic, DefinitiveListTactic, definitive_tactic, format_item_list}; use expr::{is_empty_block, is_simple_block_stmt, rewrite_assign_rhs}; diff --git a/src/lib.rs b/src/lib.rs index 161ce14e22b..b6e39be8254 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,6 +48,7 @@ pub use self::summary::Summary; #[macro_use] mod utils; pub mod config; +pub mod codemap; pub mod filemap; mod visitor; mod checkstyle; diff --git a/src/macros.rs b/src/macros.rs index 2be271e140b..b38a18866f9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -25,10 +25,11 @@ use syntax::parse::tts_to_parser; use syntax::codemap::{mk_sp, BytePos}; use Indent; +use codemap::SpanUtils; use rewrite::RewriteContext; use expr::{rewrite_call, rewrite_array}; use comment::{FindUncommented, contains_comment}; -use utils::{CodeMapSpanUtils, wrap_str}; +use utils::wrap_str; const FORCED_BRACKET_MACROS: &'static [&'static str] = &["vec!"]; diff --git a/src/patterns.rs b/src/patterns.rs index 3cec98bb93a..0b7996e93a5 100644 --- a/src/patterns.rs +++ b/src/patterns.rs @@ -9,8 +9,9 @@ // except according to those terms. use Indent; +use codemap::SpanUtils; use rewrite::{Rewrite, RewriteContext}; -use utils::{CodeMapSpanUtils, wrap_str, format_mutability}; +use utils::{wrap_str, format_mutability}; use lists::{format_item_list, itemize_list}; use expr::{rewrite_unary_prefix, rewrite_pair, rewrite_tuple}; use types::rewrite_path; diff --git a/src/types.rs b/src/types.rs index 22b518d2463..1cbda2f8960 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,9 +17,10 @@ use syntax::codemap::{self, Span, BytePos}; use syntax::abi; use {Indent, Spanned}; +use codemap::SpanUtils; use lists::{format_item_list, itemize_list, format_fn_args}; use rewrite::{Rewrite, RewriteContext}; -use utils::{CodeMapSpanUtils, extra_offset, format_mutability, wrap_str}; +use utils::{extra_offset, format_mutability, wrap_str}; use expr::{rewrite_unary_prefix, rewrite_pair, rewrite_tuple}; use config::TypeDensity; diff --git a/src/utils.rs b/src/utils.rs index ea8270e9e9d..f9b639c0ae2 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,51 +11,14 @@ use std::cmp::Ordering; use syntax::ast::{self, Visibility, Attribute, MetaItem, MetaItemKind}; -use syntax::codemap::{CodeMap, Span, BytePos}; +use syntax::codemap::BytePos; use syntax::abi; use Indent; -use comment::FindUncommented; use rewrite::{Rewrite, RewriteContext}; use SKIP_ANNOTATION; -pub trait CodeMapSpanUtils { - fn span_after(&self, original: Span, needle: &str) -> BytePos; - fn span_after_last(&self, original: Span, needle: &str) -> BytePos; - fn span_before(&self, original: Span, needle: &str) -> BytePos; -} - -impl CodeMapSpanUtils for CodeMap { - #[inline] - fn span_after(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).unwrap(); - let offset = snippet.find_uncommented(needle).unwrap() + needle.len(); - - original.lo + BytePos(offset as u32) - } - - #[inline] - fn span_after_last(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).unwrap(); - let mut offset = 0; - - while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) { - offset += additional_offset + needle.len(); - } - - original.lo + BytePos(offset as u32) - } - - #[inline] - fn span_before(&self, original: Span, needle: &str) -> BytePos { - let snippet = self.span_to_snippet(original).unwrap(); - let offset = snippet.find_uncommented(needle).unwrap(); - - original.lo + BytePos(offset as u32) - } -} - // Computes the length of a string's last line, minus offset. #[inline] pub fn extra_offset(text: &str, offset: Indent) -> usize { diff --git a/src/visitor.rs b/src/visitor.rs index af48406ec26..9833621052b 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -15,7 +15,8 @@ use syntax::parse::ParseSess; use strings::string_buffer::StringBuffer; use Indent; -use utils::{self, CodeMapSpanUtils}; +use utils; +use codemap::SpanUtils; use config::Config; use rewrite::{Rewrite, RewriteContext}; use comment::rewrite_comment; From 5a6df6bce2195b669d021a33063e1bef6180ba62 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Wed, 20 Apr 2016 23:42:36 -0400 Subject: [PATCH 06/11] codemap: Add utilities for dealing with line ranges This commit adds `LineRange` and `LineSet` types, and assiciated utilities. They will be used to restrict formatting to specific lines. Refs #434 --- Cargo.lock | 6 ++ Cargo.toml | 1 + src/codemap.rs | 188 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 4 files changed, 196 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8c7661b832d..74dd78976fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,7 @@ dependencies = [ "diff 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.63 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -47,6 +48,11 @@ name = "getopts" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "itertools" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 6cc358ca199..22641ff0df7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ syntex_syntax = "0.30" log = "0.3" env_logger = "0.3" getopts = "0.2" +itertools = "0.4" diff --git a/src/codemap.rs b/src/codemap.rs index c70decd8422..4bd28496d3a 100644 --- a/src/codemap.rs +++ b/src/codemap.rs @@ -1,13 +1,135 @@ +use std::cmp; +use std::iter::FromIterator; + +use itertools::Itertools; + use syntax::codemap::{BytePos, CodeMap, Span}; use comment::FindUncommented; +/// A range of lines, inclusive of both ends. +#[derive(Clone, Copy, Debug, Eq, PartialEq, RustcDecodable)] +pub struct LineRange { + pub lo: usize, + pub hi: usize, +} + +impl LineRange { + #[inline] + fn is_valid(self) -> bool { + self.lo <= self.hi + } + + #[inline] + pub fn contains(self, other: LineRange) -> bool { + debug_assert!(self.is_valid()); + debug_assert!(other.is_valid()); + + self.lo <= other.lo && self.hi >= other.hi + } + + #[inline] + pub fn intersects(self, other: LineRange) -> bool { + debug_assert!(self.is_valid()); + debug_assert!(other.is_valid()); + + self.lo <= other.hi || other.lo <= self.hi + } + + #[inline] + /// Returns a new `LineRange` with lines from `self` and `other` if they were adjacent or + /// intersect; returns `None` otherwise. + pub fn merge(self, other: LineRange) -> Option { + debug_assert!(self.is_valid()); + debug_assert!(other.is_valid()); + + // We can't merge non-adjacent ranges. + if self.hi + 1 < other.lo || other.hi + 1 < self.lo { + None + } else { + Some(LineRange { + lo: cmp::min(self.lo, other.lo), + hi: cmp::max(self.hi, other.hi), + }) + } + } +} + +/// A set of lines. +/// +/// The set is represented as a list of disjoint, non-adjacent ranges sorted by lower endpoint. +/// This allows efficient querying for containment of a `LineRange`. +#[derive(Clone, Debug, Eq, PartialEq, RustcDecodable)] +pub struct LineSet(Vec); + +impl LineSet { + /// Creates an empty `LineSet`. + pub fn new() -> LineSet { + LineSet(Vec::new()) + } + + /// Returns `true` if the lines in `range` are all contained in `self`. + pub fn contains(&self, range: LineRange) -> bool { + // This coule be a binary search, but it's unlikely this is a performance bottleneck. + self.0.iter().any(|r| r.contains(range)) + } + + /// Normalizes the line ranges so they are sorted by `lo` and are disjoint: any adjacent + /// contiguous ranges are merged. + fn normalize(&mut self) { + let mut v = Vec::with_capacity(self.0.len()); + { + let ranges = &mut self.0; + ranges.sort_by_key(|x| x.lo); + let merged = ranges.drain(..).coalesce(|x, y| x.merge(y).ok_or((x, y))); + v.extend(merged); + } + v.shrink_to_fit(); + + self.0 = v; + } +} + +impl FromIterator for LineSet { + /// Produce a `LineSet` from `LineRange`s in `iter`. + fn from_iter>(iter: I) -> LineSet { + let mut ret = LineSet::new(); + ret.extend(iter); + + ret + } +} + +impl Extend for LineSet { + /// Add `LineRanges` from `iter` to `self`. + fn extend(&mut self, iter: T) + where T: IntoIterator + { + self.0.extend(iter); + self.normalize(); + } +} + +impl IntoIterator for LineSet { + type Item = LineRange; + type IntoIter = ::std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + pub trait SpanUtils { fn span_after(&self, original: Span, needle: &str) -> BytePos; fn span_after_last(&self, original: Span, needle: &str) -> BytePos; fn span_before(&self, original: Span, needle: &str) -> BytePos; } +pub trait LineRangeUtils { + /// Returns the `LineRange` that corresponds to `span` in `self`. + fn lookup_line_range(&self, span: Span) -> LineRange; +} + impl SpanUtils for CodeMap { #[inline] fn span_after(&self, original: Span, needle: &str) -> BytePos { @@ -37,3 +159,69 @@ impl SpanUtils for CodeMap { original.lo + BytePos(offset as u32) } } + +impl LineRangeUtils for CodeMap { + /// Returns the `LineRange` that corresponds to `span` in `self`. + /// + /// # Panics + /// + /// Panics if `span` crosses a file boundary, which shouldn't happen. + fn lookup_line_range(&self, span: Span) -> LineRange { + let lo = self.lookup_char_pos(span.lo); + let hi = self.lookup_char_pos(span.hi); + + assert!(lo.file.name == hi.file.name, + "span crossed file boundary: lo: {:?}, hi: {:?}", + lo, + hi); + + LineRange { + lo: lo.line, + hi: hi.line, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn mk_range(lo: usize, hi: usize) -> LineRange { + assert!(lo <= hi); + LineRange { lo: lo, hi: hi } + } + + #[test] + fn test_line_range_contains() { + assert_eq!(true, mk_range(1, 2).contains(mk_range(1, 1))); + assert_eq!(true, mk_range(1, 2).contains(mk_range(2, 2))); + assert_eq!(false, mk_range(1, 2).contains(mk_range(0, 0))); + assert_eq!(false, mk_range(1, 2).contains(mk_range(3, 10))); + } + + #[test] + fn test_line_range_merge() { + assert_eq!(None, mk_range(1, 3).merge(mk_range(5, 5))); + assert_eq!(None, mk_range(4, 7).merge(mk_range(0, 1))); + assert_eq!(Some(mk_range(3, 7)), mk_range(3, 5).merge(mk_range(4, 7))); + assert_eq!(Some(mk_range(3, 7)), mk_range(3, 5).merge(mk_range(5, 7))); + assert_eq!(Some(mk_range(3, 7)), mk_range(3, 5).merge(mk_range(6, 7))); + assert_eq!(Some(mk_range(3, 7)), mk_range(3, 7).merge(mk_range(4, 5))); + } + + #[test] + fn test_line_set_extend() { + let mut line_set = LineSet(vec![LineRange { lo: 3, hi: 4 }, + LineRange { lo: 7, hi: 8 }, + LineRange { lo: 10, hi: 13 }]); + + // Fill in the gaps, and add one disjoint range at the end. + line_set.extend(vec![LineRange { lo: 5, hi: 6 }, + LineRange { lo: 9, hi: 9 }, + LineRange { lo: 14, hi: 17 }, + LineRange { lo: 19, hi: 21 }]); + + assert_eq!(line_set.0, + vec![LineRange { lo: 3, hi: 17 }, LineRange { lo: 19, hi: 21 }]); + } +} diff --git a/src/lib.rs b/src/lib.rs index b6e39be8254..e6ccc996cd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,7 @@ extern crate unicode_segmentation; extern crate regex; extern crate diff; extern crate term; +extern crate itertools; use syntax::ast; use syntax::codemap::{mk_sp, CodeMap, Span}; From 365f7946c20285b5ac39fce8456d598ed2339c8f Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Sat, 16 Apr 2016 10:34:40 -0400 Subject: [PATCH 07/11] config: Add field for restricting formating to specific lines This commit adds a `file_lines_map` field to `Config`. This is an optional field that when present stores a map from file names to sets of lines to format. The code to use this field will come in a later commit. This includes code to parse a file name / line set specification that is used for `Config::override_value()` and will be used to parse the command line option. Adding `nom` as a dependency for parsing increases the incremental rustfmt build time by 0.96s on a machine where it takes ~23s to build rustfmt. Refs #434 --- Cargo.lock | 6 +++ Cargo.toml | 1 + src/config.rs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 2 + 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 74dd78976fe..631df72a9a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ dependencies = [ "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.63 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "strings 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -85,6 +86,11 @@ name = "mempool" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nom" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "regex" version = "0.1.63" diff --git a/Cargo.toml b/Cargo.toml index 22641ff0df7..2974b55410a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,4 @@ log = "0.3" env_logger = "0.3" getopts = "0.2" itertools = "0.4" +nom = "1.2.1" diff --git a/src/config.rs b/src/config.rs index 6d4e307c204..7205c474dbe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,7 +10,12 @@ extern crate toml; -use std::str; +use std::{iter, str}; +use std::collections::HashMap; + +use nom::IResult; + +use codemap::LineSet; use lists::{SeparatorTactic, ListTactic}; use std::io::Write; @@ -111,6 +116,112 @@ configuration_option_enum! { TypeDensity: Wide, } +// Newtype needed to have impls for `create_config` macro. +#[derive(Clone, Debug, Default, RustcDecodable)] +pub struct FileLinesMap(pub HashMap); + +impl ConfigType for Option { + type ParseErr = String; + + fn doc_hint() -> String { + unimplemented!(); + } + + fn parse(s: &str) -> Result { + let mut map = HashMap::new(); + let (file, lines) = try!(parse_file_lines_spec(s)); + map.insert(file, lines); + + Ok(Some(FileLinesMap(map))) + } +} + +pub fn parse_file_lines_spec(s: &str) -> Result<(String, LineSet), String> { + let err = || Err(format!("invalid experimental-file-lines argument: {}", s)); + + match file_lines_spec_parser::file_lines_spec(&s) { + IResult::Error(_) | + IResult::Incomplete(_) => return err(), + IResult::Done(remaining, _) if !remaining.is_empty() => return err(), + IResult::Done(_, (file, line_ranges)) => Ok((file, line_ranges)), + } +} + +impl iter::FromIterator<(String, LineSet)> for FileLinesMap { + fn from_iter>(iter: I) -> FileLinesMap { + let mut ret = FileLinesMap(HashMap::new()); + ret.extend(iter); + ret + } +} + +impl Extend<(String, LineSet)> for FileLinesMap { + fn extend(&mut self, iter: I) + where I: IntoIterator + { + let map = &mut self.0; + + for (file, line_set) in iter { + map.entry(file).or_insert(LineSet::new()).extend(line_set); + } + } +} + +mod file_lines_spec_parser { + use std::iter::FromIterator; + use std::str::FromStr; + + use nom::digit; + + use codemap::{LineRange, LineSet}; + + named!(pub file_lines_spec<&str, (String, LineSet)>, + chain!( + file: map!( + is_not_s!(":"), + String::from + ) ~ + tag_s!(":") ~ + line_set: line_set, + || (file, line_set) + ) + ); + + named!(usize_digit<&str, usize>, + map_res!( + digit, + usize::from_str + ) + ); + + named!(line_range<&str, LineRange>, + map_res!( + separated_pair!( + usize_digit, + tag_s!("-"), + usize_digit + ), + |pair| { + let (lo, hi) = pair; + if lo > hi { + return Err(format!("empty line range: {}-{}", lo, hi)); + } + Ok(LineRange { lo: lo, hi: hi }) + } + ) + ); + + named!(line_set<&str, LineSet>, + map!( + separated_nonempty_list!( + tag_s!(","), + line_range + ), + LineSet::from_iter + ) + ); +} + impl Density { pub fn to_list_tactic(self) -> ListTactic { match self { @@ -356,6 +467,8 @@ macro_rules! create_config { create_config! { Doc verbose: bool, false, "Use verbose output"; Doc skip_children: bool, false, "Don't reformat out of line modules"; + NoDoc file_lines_map: Option, None::, + "Lines to format for each file"; Doc max_width: usize, 100, "Maximum width of each line"; Doc ideal_width: usize, 80, "Ideal width of each line"; Doc tab_spaces: usize, 4, "Number of spaces per tab"; diff --git a/src/lib.rs b/src/lib.rs index e6ccc996cd5..748e9a83cd0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,8 @@ #[macro_use] extern crate log; +#[macro_use] +extern crate nom; extern crate syntex_syntax as syntax; extern crate rustc_serialize; From eca074e675945649916c421bfedbe3fc36434232 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Fri, 11 Mar 2016 19:19:16 -0500 Subject: [PATCH 08/11] visitor: Handle specified line ranges in visit_stmt This commit adds a very rough implementation of handling the specified line ranges in `config.file_lines_map` for statements. It reformats a statement if its span is fully contained in the set of lines specified for the file. The implementation here is intended as a proof of concept, and demonstration that the machinery added in the preceding commits is functional. A final implementation would likely hook in via the `Rewrite` trait. Refs #434 --- src/visitor.rs | 13 ++++++++++++- tests/source/file-lines-1.rs | 29 +++++++++++++++++++++++++++++ tests/source/file-lines-2.rs | 29 +++++++++++++++++++++++++++++ tests/source/file-lines-3.rs | 29 +++++++++++++++++++++++++++++ tests/target/file-lines-1.rs | 30 ++++++++++++++++++++++++++++++ tests/target/file-lines-2.rs | 29 +++++++++++++++++++++++++++++ tests/target/file-lines-3.rs | 30 ++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/source/file-lines-1.rs create mode 100644 tests/source/file-lines-2.rs create mode 100644 tests/source/file-lines-3.rs create mode 100644 tests/target/file-lines-1.rs create mode 100644 tests/target/file-lines-2.rs create mode 100644 tests/target/file-lines-3.rs diff --git a/src/visitor.rs b/src/visitor.rs index 9833621052b..af5a36a42b1 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -16,7 +16,7 @@ use strings::string_buffer::StringBuffer; use Indent; use utils; -use codemap::SpanUtils; +use codemap::{LineRangeUtils, SpanUtils}; use config::Config; use rewrite::{Rewrite, RewriteContext}; use comment::rewrite_comment; @@ -39,6 +39,17 @@ impl<'a> FmtVisitor<'a> { self.codemap.lookup_char_pos(stmt.span.lo), self.codemap.lookup_char_pos(stmt.span.hi)); + let line_range = self.codemap.lookup_line_range(stmt.span); + let file_name = self.codemap.span_to_filename(stmt.span); + + // TODO(#434): Move this check to somewhere more central, eg Rewrite. + if let Some(ref file_lines_map) = self.config.file_lines_map { + match file_lines_map.0.get(&*file_name) { + Some(ref line_set) if line_set.contains(line_range) => (), + _ => return, + } + } + match stmt.node { ast::StmtKind::Decl(ref decl, _) => { if let ast::DeclKind::Item(ref item) = decl.node { diff --git a/tests/source/file-lines-1.rs b/tests/source/file-lines-1.rs new file mode 100644 index 00000000000..2525b7ecbfd --- /dev/null +++ b/tests/source/file-lines-1.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines_map: tests/source/file-lines-1.rs:4-8 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/tests/source/file-lines-2.rs b/tests/source/file-lines-2.rs new file mode 100644 index 00000000000..1636466eed4 --- /dev/null +++ b/tests/source/file-lines-2.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines_map: tests/source/file-lines-2.rs:10-15 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/tests/source/file-lines-3.rs b/tests/source/file-lines-3.rs new file mode 100644 index 00000000000..4154a1c2dc4 --- /dev/null +++ b/tests/source/file-lines-3.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines_map: tests/source/file-lines-3.rs:4-8,10-15 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/tests/target/file-lines-1.rs b/tests/target/file-lines-1.rs new file mode 100644 index 00000000000..d7d867f86fe --- /dev/null +++ b/tests/target/file-lines-1.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines_map: tests/source/file-lines-1.rs:4-8 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call() + .method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/tests/target/file-lines-2.rs b/tests/target/file-lines-2.rs new file mode 100644 index 00000000000..1636466eed4 --- /dev/null +++ b/tests/target/file-lines-2.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines_map: tests/source/file-lines-2.rs:10-15 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call().method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} diff --git a/tests/target/file-lines-3.rs b/tests/target/file-lines-3.rs new file mode 100644 index 00000000000..cac32969e7a --- /dev/null +++ b/tests/target/file-lines-3.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines_map: tests/source/file-lines-3.rs:4-8,10-15 + +fn floaters() { + let x = Foo { + field1: val1, + field2: val2, + } + .method_call() + .method_call(); + + let y = if cond { + val1 + } else { + val2 + } + .method_call(); + + { + match x { + PushParam => { + // params are 1-indexed + stack.push(mparams[match cur.to_digit(10) { + Some(d) => d as usize - 1, + None => return Err("bad param number".to_owned()), + }] + .clone()); + } + } + } +} From 8efcb71ce2d57a928238a28f7bf0815efe2286a4 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Sun, 10 Apr 2016 13:03:54 -0400 Subject: [PATCH 09/11] rustfmt: Add option to specify line ranges for formatting This commit adds the `--experimental-file-lines` option to rustfmt. This allows specifying line ranges to format from the command line. We will remove the `experimental-` prefix once we have covered all AST elements, and are satisfied with the functionality. Refs #434 --- src/bin/rustfmt.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 6356d0372a7..0af72530484 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -18,7 +18,7 @@ extern crate env_logger; extern crate getopts; use rustfmt::{run, Input, Summary}; -use rustfmt::config::{Config, ConfigType, WriteMode}; +use rustfmt::config::{self, Config, ConfigType, FileLinesMap, WriteMode}; use std::{env, error}; use std::fs::{self, File}; @@ -56,6 +56,7 @@ struct CliOptions { skip_children: bool, verbose: bool, write_mode: Option, + file_lines_map: Option, } impl CliOptions { @@ -72,12 +73,25 @@ impl CliOptions { } } + if matches.opt_present("experimental-file-lines") { + let specs = matches.opt_strs("experimental-file-lines"); + let file_lines_map = try!(specs.iter() + .map(|ref s| { + config::parse_file_lines_spec(s) + .map_err(FmtError::from) + }) + .collect()); + + options.file_lines_map = Some(file_lines_map); + } + Ok(options) } - fn apply_to(&self, config: &mut Config) { + fn apply_to(self, config: &mut Config) { config.skip_children = self.skip_children; config.verbose = self.verbose; + config.file_lines_map = self.file_lines_map; if let Some(write_mode) = self.write_mode { config.write_mode = write_mode; } @@ -167,6 +181,11 @@ fn make_opts() -> Options { "Recursively searches the given path for the rustfmt.toml config file. If not \ found reverts to the input file path", "[Path for the configuration file]"); + opts.optmulti("", + "experimental-file-lines", + "Format specified line RANGEs in FILE. RANGEs are inclusive of both endpoints. \ + May be specified multiple times.", + "FILE:RANGE,RANGE,..."); opts } @@ -229,7 +248,7 @@ fn execute(opts: &Options) -> FmtResult { config = config_tmp; } - options.apply_to(&mut config); + options.clone().apply_to(&mut config); error_summary.add(run(Input::File(file), &config)); } Ok(error_summary) From 214d0cc9f2a0bccdd028dcf1aa2a966fe4672551 Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Tue, 26 Apr 2016 16:43:03 -0400 Subject: [PATCH 10/11] Change --experimental-file-lines option to --file-lines Quoth @nrc: > everything in Rustfmt is experimental for now! --- src/bin/rustfmt.rs | 6 +++--- src/config.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 0af72530484..6e49f845a32 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -73,8 +73,8 @@ impl CliOptions { } } - if matches.opt_present("experimental-file-lines") { - let specs = matches.opt_strs("experimental-file-lines"); + if matches.opt_present("file-lines") { + let specs = matches.opt_strs("file-lines"); let file_lines_map = try!(specs.iter() .map(|ref s| { config::parse_file_lines_spec(s) @@ -182,7 +182,7 @@ fn make_opts() -> Options { found reverts to the input file path", "[Path for the configuration file]"); opts.optmulti("", - "experimental-file-lines", + "file-lines", "Format specified line RANGEs in FILE. RANGEs are inclusive of both endpoints. \ May be specified multiple times.", "FILE:RANGE,RANGE,..."); diff --git a/src/config.rs b/src/config.rs index 7205c474dbe..35949f64849 100644 --- a/src/config.rs +++ b/src/config.rs @@ -137,7 +137,7 @@ impl ConfigType for Option { } pub fn parse_file_lines_spec(s: &str) -> Result<(String, LineSet), String> { - let err = || Err(format!("invalid experimental-file-lines argument: {}", s)); + let err = || Err(format!("invalid file-lines argument: {}", s)); match file_lines_spec_parser::file_lines_spec(&s) { IResult::Error(_) | From 5ec2ad68e2717d6a11e6e186d427163e0e3278fa Mon Sep 17 00:00:00 2001 From: Kamal Marhubi Date: Tue, 26 Apr 2016 17:02:35 -0400 Subject: [PATCH 11/11] readme: Document --file-lines option --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index d2dc9a22f85..8cdf997a067 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,19 @@ the command line. For example `rustfmt --write-mode=display src/filename.rs` `cargo fmt` uses `--write-mode=replace` by default. +If you want to restrict reformatting to specific sets of lines, you can use the +`--file-lines` option. It can be specified multiple times, and takes +`FILE:RANGE,RANGE,...` as an argument. `FILE` is a file name, and each `RANGE` +is an inclusive range of lines like `7-13`. For example, + +``` +rustfmt src/lib.rs --file-lines src/lib.rs:7-13,21-29 --file-lines src/foo.rs:10-11,15-15 +``` + +would format lines `7-13` and `21-29` for `src/lib.rs`, and lines `10-11`, and +`15` of `src/foo.rs`. No other files would be formatted, even if they are +included as out of line modules from `src/lib.rs`. + If `rustfmt` successfully reformatted the code it will exit with `0` exit status. Exit status `1` signals some unexpected error, like an unknown option or a failure to read a file. Exit status `2` is returned if there are syntax errors