diff --git a/Cargo.lock b/Cargo.lock index 621659c737f5..fc8070979d6f 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.15 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "multimap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.71 (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)", @@ -80,6 +81,11 @@ dependencies = [ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multimap" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "regex" version = "0.1.71" diff --git a/Cargo.toml b/Cargo.toml index 756fcc600e9c..9d8ad68ed8ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,3 +26,4 @@ log = "0.3" env_logger = "0.3" getopts = "0.2" itertools = "0.4.15" +multimap = "0.3" diff --git a/README.md b/README.md index 60b299562183..5d9f262d659f 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,25 @@ 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. Its argument is a JSON array of objects +with `file` and `range` properties, where `file` is a file name, and +`range` is an array representing a range of lines like `[7,13]`. Ranges +are 1-based and inclusive of both end points. Specifying an empty array +will result in no files being formatted. For example, + +``` +rustfmt --file-lines '[ + {"file":"src/lib.rs","range":[7,13]}, + {"file":"src/lib.rs","range":[21,29]}, + {"file":"src/foo.rs","range":[10,11]}, + {"file":"src/foo.rs","range":[15,15]}]' +``` + +would format lines `7-13` and `21-29` of `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 diff --git a/src/bin/rustfmt.rs b/src/bin/rustfmt.rs index 2105a9629977..5aa7cc4e080e 100644 --- a/src/bin/rustfmt.rs +++ b/src/bin/rustfmt.rs @@ -18,6 +18,7 @@ extern crate getopts; use rustfmt::{run, Input, Summary}; +use rustfmt::file_lines::FileLines; use rustfmt::config::{Config, WriteMode}; use std::{env, error}; @@ -57,6 +58,7 @@ struct CliOptions { skip_children: bool, verbose: bool, write_mode: Option, + file_lines: FileLines, // Default is all lines in all files. } impl CliOptions { @@ -73,12 +75,17 @@ fn from_matches(matches: &Matches) -> FmtResult { } } + if let Some(ref file_lines) = matches.opt_str("file-lines") { + options.file_lines = try!(file_lines.parse()); + } + 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 = self.file_lines; if let Some(write_mode) = self.write_mode { config.write_mode = write_mode; } @@ -168,6 +175,10 @@ 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.optopt("", + "file-lines", + "Format specified line ranges. See README for more detail on the JSON format.", + "JSON"); opts } @@ -198,8 +209,12 @@ fn execute(opts: &Options) -> FmtResult { Ok(run(Input::Text(input), &config)) } - Operation::Format { files, config_path } => { + Operation::Format { mut files, config_path } => { let options = try!(CliOptions::from_matches(&matches)); + + // Add any additional files that were specified via `--file-lines`. + files.extend(options.file_lines.files().cloned().map(PathBuf::from)); + let mut config = Config::default(); let mut path = None; // Load the config path file if provided @@ -227,7 +242,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) @@ -306,8 +321,8 @@ fn determine_operation(matches: &Matches) -> FmtResult { Some(dir) }); - // if no file argument is supplied, read from stdin - if matches.free.is_empty() { + // if no file argument is supplied and `--file-lines` is not specified, read from stdin + if matches.free.is_empty() && !matches.opt_present("file-lines") { let mut buffer = String::new(); try!(io::stdin().read_to_string(&mut buffer)); @@ -318,6 +333,7 @@ fn determine_operation(matches: &Matches) -> FmtResult { }); } + // We append files from `--file-lines` later in `execute()`. let files: Vec<_> = matches.free.iter().map(PathBuf::from).collect(); Ok(Operation::Format { diff --git a/src/codemap.rs b/src/codemap.rs new file mode 100644 index 000000000000..9ec5c6630640 --- /dev/null +++ b/src/codemap.rs @@ -0,0 +1,94 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module contains utilities that work with the `CodeMap` from libsyntax / syntex_syntax. +//! This includes extension traits and methods for looking up spans and line ranges for AST nodes. + +use std::rc::Rc; + +use syntax::codemap::{BytePos, CodeMap, FileMap, Span}; + +use comment::FindUncommented; + +/// A range of lines in a file, inclusive of both ends. +pub struct LineRange { + pub file: Rc, + pub lo: usize, + pub hi: usize, +} + +impl LineRange { + pub fn file_name(&self) -> &str { + self.file.as_ref().name.as_str() + } +} + +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`. + /// + /// # Panics + /// + /// Panics if `span` crosses a file boundary, which shouldn't happen. + fn lookup_line_range(&self, span: Span) -> LineRange; +} + +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) + } +} + +impl LineRangeUtils for CodeMap { + 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 { + file: lo.file.clone(), + lo: lo.line, + hi: hi.line, + } + } +} diff --git a/src/config.rs b/src/config.rs index 8a51b3ecefae..51daa7a505a0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,7 @@ extern crate toml; +use file_lines::FileLines; use lists::{SeparatorTactic, ListTactic}; use std::io::Write; @@ -200,6 +201,12 @@ fn doc_hint() -> String { } } +impl ConfigType for FileLines { + fn doc_hint() -> String { + String::from("") + } +} + pub struct ConfigHelpItem { option_name: &'static str, doc_string: &'static str, @@ -327,6 +334,9 @@ fn default() -> Config { create_config! { verbose: bool, false, "Use verbose output"; skip_children: bool, false, "Don't reformat out of line modules"; + file_lines: FileLines, FileLines::all(), + "Lines to format; this is not supported in rustfmt.toml, and can only be specified \ + via the --file-lines option"; 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"; diff --git a/src/expr.rs b/src/expr.rs index fc7f9cc6ca3a..af0492b29135 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -16,12 +16,13 @@ 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/file_lines.rs b/src/file_lines.rs new file mode 100644 index 000000000000..28fedad4cfd4 --- /dev/null +++ b/src/file_lines.rs @@ -0,0 +1,232 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! This module contains types and functions to support formatting specific line ranges. +use std::{cmp, iter, str}; + +use itertools::Itertools; +use multimap::MultiMap; +use rustc_serialize::{self, json}; + +use codemap::LineRange; + +/// A range that is inclusive of both ends. +#[derive(Clone, Copy, Debug, Eq, PartialEq, RustcDecodable)] +struct Range { + pub lo: usize, + pub hi: usize, +} + +impl<'a> From<&'a LineRange> for Range { + fn from(range: &'a LineRange) -> Range { + Range::new(range.lo, range.hi) + } +} + +impl Range { + fn new(lo: usize, hi: usize) -> Range { + Range { lo: lo, hi: hi } + } + + fn is_empty(self) -> bool { + self.lo > self.hi + } + + fn contains(self, other: Range) -> bool { + if other.is_empty() { + true + } else { + !self.is_empty() && self.lo <= other.lo && self.hi >= other.hi + } + } + + fn intersects(self, other: Range) -> bool { + if self.is_empty() || other.is_empty() { + false + } else { + (self.lo <= other.hi && other.hi <= self.hi) || + (other.lo <= self.hi && self.hi <= other.hi) + } + } + + fn adjacent_to(self, other: Range) -> bool { + if self.is_empty() || other.is_empty() { + false + } else { + self.hi + 1 == other.lo || other.hi + 1 == self.lo + } + } + + /// Returns a new `Range` with lines from `self` and `other` if they were adjacent or + /// intersect; returns `None` otherwise. + fn merge(self, other: Range) -> Option { + if self.adjacent_to(other) || self.intersects(other) { + Some(Range::new(cmp::min(self.lo, other.lo), cmp::max(self.hi, other.hi))) + } else { + None + } + } +} + +/// A set of lines in files. +/// +/// It is represented as a multimap keyed on file names, with values a collection of +/// non-overlapping ranges sorted by their start point. An inner `None` is interpreted to mean all +/// lines in all files. +#[derive(Clone, Debug, Default)] +pub struct FileLines(Option>); + +/// Normalizes the ranges so that the invariants for `FileLines` hold: ranges are non-overlapping, +/// and ordered by their start point. +fn normalize_ranges(map: &mut MultiMap) { + for (_, ranges) in map.iter_all_mut() { + ranges.sort_by_key(|x| x.lo); + let merged = ranges.drain(..).coalesce(|x, y| x.merge(y).ok_or((x, y))).collect(); + *ranges = merged; + } +} + +impl FileLines { + /// Creates a `FileLines` that contains all lines in all files. + pub fn all() -> FileLines { + FileLines(None) + } + + /// Creates a `FileLines` from a `MultiMap`, ensuring that the invariants hold. + fn from_multimap(map: MultiMap) -> FileLines { + let mut map = map; + normalize_ranges(&mut map); + FileLines(Some(map)) + } + + /// Returns an iterator over the files contained in `self`. + pub fn files(&self) -> Files { + Files(self.0.as_ref().map(MultiMap::keys)) + } + + /// Returns true if `range` is fully contained in `self`. + pub fn contains(&self, range: &LineRange) -> bool { + let map = match self.0 { + // `None` means "all lines in all files". + None => return true, + Some(ref map) => map, + }; + + match map.get_vec(range.file_name()) { + None => false, + Some(ranges) => ranges.iter().any(|r| r.contains(Range::from(range))), + } + } + + /// Returns true if any lines in `range` are in `self`. + pub fn intersects(&self, range: &LineRange) -> bool { + let map = match self.0 { + // `None` means "all lines in all files". + None => return true, + Some(ref map) => map, + }; + + match map.get_vec(range.file_name()) { + None => false, + Some(ranges) => ranges.iter().any(|r| r.intersects(Range::from(range))), + } + } +} + +/// FileLines files iterator. +pub struct Files<'a>(Option<::std::collections::hash_map::Keys<'a, String, Vec>>); + +impl<'a> iter::Iterator for Files<'a> { + type Item = &'a String; + + fn next(&mut self) -> Option<&'a String> { + self.0.as_mut().and_then(Iterator::next) + } +} + +// This impl is needed for `Config::override_value` to work for use in tests. +impl str::FromStr for FileLines { + type Err = String; + + fn from_str(s: &str) -> Result { + let v: Vec = try!(json::decode(s).map_err(|e| e.to_string())); + let m = v.into_iter().map(JsonSpan::into_tuple).collect(); + Ok(FileLines::from_multimap(m)) + } +} + +// For JSON decoding. +#[derive(Clone, Debug, RustcDecodable)] +struct JsonSpan { + file: String, + range: (usize, usize), +} + +impl JsonSpan { + // To allow `collect()`ing into a `MultiMap`. + fn into_tuple(self) -> (String, Range) { + let (lo, hi) = self.range; + (self.file, Range::new(lo, hi)) + } +} + +// This impl is needed for inclusion in the `Config` struct. We don't have a toml representation +// for `FileLines`, so it will just panic instead. +impl rustc_serialize::Decodable for FileLines { + fn decode(_: &mut D) -> Result { + panic!("FileLines cannot be deserialized from a project rustfmt.toml file: please \ + specify it via the `--file-lines` option instead"); + } +} + +#[cfg(test)] +mod test { + use super::Range; + + #[test] + fn test_range_intersects() { + assert!(Range::new(1, 2).intersects(Range::new(1, 1))); + assert!(Range::new(1, 2).intersects(Range::new(2, 2))); + assert!(!Range::new(1, 2).intersects(Range::new(0, 0))); + assert!(!Range::new(1, 2).intersects(Range::new(3, 10))); + assert!(!Range::new(1, 3).intersects(Range::new(5, 5))); + } + + #[test] + fn test_range_adjacent_to() { + assert!(!Range::new(1, 2).adjacent_to(Range::new(1, 1))); + assert!(!Range::new(1, 2).adjacent_to(Range::new(2, 2))); + assert!(Range::new(1, 2).adjacent_to(Range::new(0, 0))); + assert!(Range::new(1, 2).adjacent_to(Range::new(3, 10))); + assert!(!Range::new(1, 3).adjacent_to(Range::new(5, 5))); + } + + #[test] + fn test_range_contains() { + assert!(Range::new(1, 2).contains(Range::new(1, 1))); + assert!(Range::new(1, 2).contains(Range::new(2, 2))); + assert!(!Range::new(1, 2).contains(Range::new(0, 0))); + assert!(!Range::new(1, 2).contains(Range::new(3, 10))); + } + + #[test] + fn test_range_merge() { + assert_eq!(None, Range::new(1, 3).merge(Range::new(5, 5))); + assert_eq!(None, Range::new(4, 7).merge(Range::new(0, 1))); + assert_eq!(Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(4, 7))); + assert_eq!(Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(5, 7))); + assert_eq!(Some(Range::new(3, 7)), + Range::new(3, 5).merge(Range::new(6, 7))); + assert_eq!(Some(Range::new(3, 7)), + Range::new(3, 7).merge(Range::new(4, 5))); + } +} diff --git a/src/imports.rs b/src/imports.rs index cb6444851993..89030392774c 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 defd8e1a7425..d83ad3c88ee8 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, ListTactic, 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 e672efb8feac..322eed374de8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ extern crate diff; extern crate term; extern crate itertools; +extern crate multimap; use syntax::ast; use syntax::codemap::{mk_sp, CodeMap, Span}; @@ -52,7 +53,9 @@ #[macro_use] mod utils; pub mod config; +pub mod codemap; pub mod filemap; +pub mod file_lines; pub mod visitor; mod checkstyle; mod items; @@ -470,6 +473,7 @@ pub fn format_input(input: Input, } } +#[derive(Debug)] pub enum Input { File(PathBuf), Text(String), diff --git a/src/macros.rs b/src/macros.rs index e9992fb0eb01..9cb0f8619c0b 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -25,10 +25,11 @@ use syntax::codemap::{mk_sp, BytePos}; use Indent; +use codemap::SpanUtils; use rewrite::{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 08d9db85201d..a0b15a0f8c9e 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 656701b6c787..b1b9b749dd8e 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,9 +17,10 @@ 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 188174e60211..82fbb8d5925a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -14,51 +14,14 @@ use itertools::Itertools; use syntax::ast::{self, Visibility, Attribute, MetaItem, MetaItemKind, Path}; -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 503153463dfb..48b5e0ff7d24 100644 --- a/src/visitor.rs +++ b/src/visitor.rs @@ -15,7 +15,8 @@ use strings::string_buffer::StringBuffer; use Indent; -use utils::{self, CodeMapSpanUtils}; +use utils; +use codemap::{LineRangeUtils, SpanUtils}; use config::Config; use rewrite::{Rewrite, RewriteContext}; use comment::rewrite_comment; @@ -42,6 +43,15 @@ 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)); + + // FIXME(#434): Move this check to somewhere more central, eg Rewrite. + if !self.config.file_lines.contains(&self.codemap.lookup_line_range(stmt.span)) { + 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 000000000000..43c18d8f46fa --- /dev/null +++ b/tests/source/file-lines-1.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-1.rs","range":[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 000000000000..6f8e9e6db3a2 --- /dev/null +++ b/tests/source/file-lines-2.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-2.rs","range":[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 000000000000..73d482695d6e --- /dev/null +++ b/tests/source/file-lines-3.rs @@ -0,0 +1,29 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-3.rs","range":[4,8]},{"file":"tests/source/file-lines-3.rs","range":[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-4.rs b/tests/source/file-lines-4.rs new file mode 100644 index 000000000000..381f021515fe --- /dev/null +++ b/tests/source/file-lines-4.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [] +// (Test that nothing is formatted if an empty array is specified.) + +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 000000000000..5b8478e03032 --- /dev/null +++ b/tests/target/file-lines-1.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-1.rs","range":[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 000000000000..db4902d49b7c --- /dev/null +++ b/tests/target/file-lines-2.rs @@ -0,0 +1,24 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-2.rs","range":[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 000000000000..19359b2ddf9a --- /dev/null +++ b/tests/target/file-lines-3.rs @@ -0,0 +1,25 @@ +// rustfmt-file_lines: [{"file":"tests/source/file-lines-3.rs","range":[4,8]},{"file":"tests/source/file-lines-3.rs","range":[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-4.rs b/tests/target/file-lines-4.rs new file mode 100644 index 000000000000..381f021515fe --- /dev/null +++ b/tests/target/file-lines-4.rs @@ -0,0 +1,30 @@ +// rustfmt-file_lines: [] +// (Test that nothing is formatted if an empty array is specified.) + +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()); + } + } + } +}