Skip to content

Commit 240dba5

Browse files
committed
Merge pull request #1007 from kamalmarhubi/basic-line-ranges-v2
Add infrastructure for formatting specific line ranges
2 parents 5436977 + e252100 commit 240dba5

24 files changed

+638
-52
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ log = "0.3"
2626
env_logger = "0.3"
2727
getopts = "0.2"
2828
itertools = "0.4.15"
29+
multimap = "0.3"

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,25 @@ the command line. For example `rustfmt --write-mode=display src/filename.rs`
7373

7474
`cargo fmt` uses `--write-mode=replace` by default.
7575

76+
If you want to restrict reformatting to specific sets of lines, you can
77+
use the `--file-lines` option. Its argument is a JSON array of objects
78+
with `file` and `range` properties, where `file` is a file name, and
79+
`range` is an array representing a range of lines like `[7,13]`. Ranges
80+
are 1-based and inclusive of both end points. Specifying an empty array
81+
will result in no files being formatted. For example,
82+
83+
```
84+
rustfmt --file-lines '[
85+
{"file":"src/lib.rs","range":[7,13]},
86+
{"file":"src/lib.rs","range":[21,29]},
87+
{"file":"src/foo.rs","range":[10,11]},
88+
{"file":"src/foo.rs","range":[15,15]}]'
89+
```
90+
91+
would format lines `7-13` and `21-29` of `src/lib.rs`, and lines `10-11`,
92+
and `15` of `src/foo.rs`. No other files would be formatted, even if they
93+
are included as out of line modules from `src/lib.rs`.
94+
7695
If `rustfmt` successfully reformatted the code it will exit with `0` exit
7796
status. Exit status `1` signals some unexpected error, like an unknown option or
7897
a failure to read a file. Exit status `2` is returned if there are syntax errors

src/bin/rustfmt.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ extern crate env_logger;
1818
extern crate getopts;
1919

2020
use rustfmt::{run, Input, Summary};
21+
use rustfmt::file_lines::FileLines;
2122
use rustfmt::config::{Config, WriteMode};
2223

2324
use std::{env, error};
@@ -57,6 +58,7 @@ struct CliOptions {
5758
skip_children: bool,
5859
verbose: bool,
5960
write_mode: Option<WriteMode>,
61+
file_lines: FileLines, // Default is all lines in all files.
6062
}
6163

6264
impl CliOptions {
@@ -73,12 +75,17 @@ impl CliOptions {
7375
}
7476
}
7577

78+
if let Some(ref file_lines) = matches.opt_str("file-lines") {
79+
options.file_lines = try!(file_lines.parse());
80+
}
81+
7682
Ok(options)
7783
}
7884

79-
fn apply_to(&self, config: &mut Config) {
85+
fn apply_to(self, config: &mut Config) {
8086
config.skip_children = self.skip_children;
8187
config.verbose = self.verbose;
88+
config.file_lines = self.file_lines;
8289
if let Some(write_mode) = self.write_mode {
8390
config.write_mode = write_mode;
8491
}
@@ -168,6 +175,10 @@ fn make_opts() -> Options {
168175
"Recursively searches the given path for the rustfmt.toml config file. If not \
169176
found reverts to the input file path",
170177
"[Path for the configuration file]");
178+
opts.optopt("",
179+
"file-lines",
180+
"Format specified line ranges. See README for more detail on the JSON format.",
181+
"JSON");
171182

172183
opts
173184
}
@@ -198,8 +209,12 @@ fn execute(opts: &Options) -> FmtResult<Summary> {
198209

199210
Ok(run(Input::Text(input), &config))
200211
}
201-
Operation::Format { files, config_path } => {
212+
Operation::Format { mut files, config_path } => {
202213
let options = try!(CliOptions::from_matches(&matches));
214+
215+
// Add any additional files that were specified via `--file-lines`.
216+
files.extend(options.file_lines.files().cloned().map(PathBuf::from));
217+
203218
let mut config = Config::default();
204219
let mut path = None;
205220
// Load the config path file if provided
@@ -227,7 +242,7 @@ fn execute(opts: &Options) -> FmtResult<Summary> {
227242
config = config_tmp;
228243
}
229244

230-
options.apply_to(&mut config);
245+
options.clone().apply_to(&mut config);
231246
error_summary.add(run(Input::File(file), &config));
232247
}
233248
Ok(error_summary)
@@ -306,8 +321,8 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
306321
Some(dir)
307322
});
308323

309-
// if no file argument is supplied, read from stdin
310-
if matches.free.is_empty() {
324+
// if no file argument is supplied and `--file-lines` is not specified, read from stdin
325+
if matches.free.is_empty() && !matches.opt_present("file-lines") {
311326

312327
let mut buffer = String::new();
313328
try!(io::stdin().read_to_string(&mut buffer));
@@ -318,6 +333,7 @@ fn determine_operation(matches: &Matches) -> FmtResult<Operation> {
318333
});
319334
}
320335

336+
// We append files from `--file-lines` later in `execute()`.
321337
let files: Vec<_> = matches.free.iter().map(PathBuf::from).collect();
322338

323339
Ok(Operation::Format {

src/codemap.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! This module contains utilities that work with the `CodeMap` from libsyntax / syntex_syntax.
12+
//! This includes extension traits and methods for looking up spans and line ranges for AST nodes.
13+
14+
use std::rc::Rc;
15+
16+
use syntax::codemap::{BytePos, CodeMap, FileMap, Span};
17+
18+
use comment::FindUncommented;
19+
20+
/// A range of lines in a file, inclusive of both ends.
21+
pub struct LineRange {
22+
pub file: Rc<FileMap>,
23+
pub lo: usize,
24+
pub hi: usize,
25+
}
26+
27+
impl LineRange {
28+
pub fn file_name(&self) -> &str {
29+
self.file.as_ref().name.as_str()
30+
}
31+
}
32+
33+
pub trait SpanUtils {
34+
fn span_after(&self, original: Span, needle: &str) -> BytePos;
35+
fn span_after_last(&self, original: Span, needle: &str) -> BytePos;
36+
fn span_before(&self, original: Span, needle: &str) -> BytePos;
37+
}
38+
39+
pub trait LineRangeUtils {
40+
/// Returns the `LineRange` that corresponds to `span` in `self`.
41+
///
42+
/// # Panics
43+
///
44+
/// Panics if `span` crosses a file boundary, which shouldn't happen.
45+
fn lookup_line_range(&self, span: Span) -> LineRange;
46+
}
47+
48+
impl SpanUtils for CodeMap {
49+
#[inline]
50+
fn span_after(&self, original: Span, needle: &str) -> BytePos {
51+
let snippet = self.span_to_snippet(original).unwrap();
52+
let offset = snippet.find_uncommented(needle).unwrap() + needle.len();
53+
54+
original.lo + BytePos(offset as u32)
55+
}
56+
57+
#[inline]
58+
fn span_after_last(&self, original: Span, needle: &str) -> BytePos {
59+
let snippet = self.span_to_snippet(original).unwrap();
60+
let mut offset = 0;
61+
62+
while let Some(additional_offset) = snippet[offset..].find_uncommented(needle) {
63+
offset += additional_offset + needle.len();
64+
}
65+
66+
original.lo + BytePos(offset as u32)
67+
}
68+
69+
#[inline]
70+
fn span_before(&self, original: Span, needle: &str) -> BytePos {
71+
let snippet = self.span_to_snippet(original).unwrap();
72+
let offset = snippet.find_uncommented(needle).unwrap();
73+
74+
original.lo + BytePos(offset as u32)
75+
}
76+
}
77+
78+
impl LineRangeUtils for CodeMap {
79+
fn lookup_line_range(&self, span: Span) -> LineRange {
80+
let lo = self.lookup_char_pos(span.lo);
81+
let hi = self.lookup_char_pos(span.hi);
82+
83+
assert!(lo.file.name == hi.file.name,
84+
"span crossed file boundary: lo: {:?}, hi: {:?}",
85+
lo,
86+
hi);
87+
88+
LineRange {
89+
file: lo.file.clone(),
90+
lo: lo.line,
91+
hi: hi.line,
92+
}
93+
}
94+
}

src/config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
extern crate toml;
1212

13+
use file_lines::FileLines;
1314
use lists::{SeparatorTactic, ListTactic};
1415
use std::io::Write;
1516

@@ -200,6 +201,12 @@ impl ConfigType for String {
200201
}
201202
}
202203

204+
impl ConfigType for FileLines {
205+
fn doc_hint() -> String {
206+
String::from("<json>")
207+
}
208+
}
209+
203210
pub struct ConfigHelpItem {
204211
option_name: &'static str,
205212
doc_string: &'static str,
@@ -327,6 +334,9 @@ macro_rules! create_config {
327334
create_config! {
328335
verbose: bool, false, "Use verbose output";
329336
skip_children: bool, false, "Don't reformat out of line modules";
337+
file_lines: FileLines, FileLines::all(),
338+
"Lines to format; this is not supported in rustfmt.toml, and can only be specified \
339+
via the --file-lines option";
330340
max_width: usize, 100, "Maximum width of each line";
331341
ideal_width: usize, 80, "Ideal width of each line";
332342
tab_spaces: usize, 4, "Number of spaces per tab";

src/expr.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ use std::iter::ExactSizeIterator;
1616
use std::fmt::Write;
1717

1818
use {Indent, Spanned};
19+
use codemap::SpanUtils;
1920
use rewrite::{Rewrite, RewriteContext};
2021
use lists::{write_list, itemize_list, ListFormatting, SeparatorTactic, ListTactic,
2122
DefinitiveListTactic, definitive_tactic, ListItem, format_item_list};
2223
use string::{StringFormat, rewrite_string};
23-
use utils::{CodeMapSpanUtils, extra_offset, last_line_width, wrap_str, binary_search,
24-
first_line_width, semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr};
24+
use utils::{extra_offset, last_line_width, wrap_str, binary_search, first_line_width,
25+
semicolon_for_stmt, trimmed_last_line_width, left_most_sub_expr};
2526
use visitor::FmtVisitor;
2627
use config::{Config, StructLitStyle, MultilineStyle, ElseIfBraceStyle, ControlBraceStyle};
2728
use comment::{FindUncommented, rewrite_comment, contains_comment, recover_comment_removed};

0 commit comments

Comments
 (0)