Skip to content

Commit aef8e93

Browse files
Merge remote-tracking branch 'upstream/master' into accept-manifest-path
2 parents 17cbbd8 + 71289e1 commit aef8e93

File tree

7 files changed

+388
-291
lines changed

7 files changed

+388
-291
lines changed

src/cargo-fmt/main.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,14 @@ fn execute() -> i32 {
8989
};
9090

9191
if opts.version {
92-
return handle_command_status(get_version());
92+
return handle_command_status(get_rustfmt_info(&[String::from("--version")]));
93+
}
94+
if opts.rustfmt_options.iter().any(|s| {
95+
["--print-config", "-h", "--help", "-V", "--version"].contains(&s.as_str())
96+
|| s.starts_with("--help=")
97+
|| s.starts_with("--print-config=")
98+
}) {
99+
return handle_command_status(get_rustfmt_info(&opts.rustfmt_options));
93100
}
94101

95102
let strategy = CargoFmtStrategy::from_opts(&opts);
@@ -142,10 +149,10 @@ fn handle_command_status(status: Result<i32, io::Error>) -> i32 {
142149
}
143150
}
144151

145-
fn get_version() -> Result<i32, io::Error> {
152+
fn get_rustfmt_info(args: &[String]) -> Result<i32, io::Error> {
146153
let mut command = Command::new("rustfmt")
147154
.stdout(std::process::Stdio::inherit())
148-
.args(&[String::from("--version")])
155+
.args(args)
149156
.spawn()
150157
.map_err(|e| match e.kind() {
151158
io::ErrorKind::NotFound => io::Error::new(
@@ -168,14 +175,7 @@ fn format_crate(
168175
rustfmt_args: Vec<String>,
169176
manifest_path: Option<&Path>,
170177
) -> Result<i32, io::Error> {
171-
let targets = if rustfmt_args
172-
.iter()
173-
.any(|s| ["--print-config", "-h", "--help", "-V", "--version"].contains(&s.as_str()))
174-
{
175-
BTreeSet::new()
176-
} else {
177-
get_targets(strategy, manifest_path)?
178-
};
178+
let targets = get_targets(strategy, manifest_path)?;
179179

180180
// Currently only bin and lib files get formatted.
181181
run_rustfmt(&targets, &rustfmt_args, verbosity)

src/lists.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,11 @@ pub(crate) fn extract_post_comment(
612612
post_snippet[1..].trim_matches(white_space)
613613
} else if post_snippet.starts_with(separator) {
614614
post_snippet[separator.len()..].trim_matches(white_space)
615-
} else if post_snippet.ends_with(',') && !post_snippet.trim().starts_with("//") {
615+
}
616+
// not comment or over two lines
617+
else if post_snippet.ends_with(',')
618+
&& (!post_snippet.trim().starts_with("//") || post_snippet.trim().contains('\n'))
619+
{
616620
post_snippet[..(post_snippet.len() - 1)].trim_matches(white_space)
617621
} else {
618622
post_snippet

src/test/configuration_snippet.rs

+286
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
use std::collections::{HashMap, HashSet};
2+
use std::fs;
3+
use std::io::{BufRead, BufReader, Write};
4+
use std::iter::Enumerate;
5+
use std::path::{Path, PathBuf};
6+
7+
use super::{print_mismatches, write_message, DIFF_CONTEXT_SIZE};
8+
use crate::config::{Config, EmitMode, Verbosity};
9+
use crate::rustfmt_diff::{make_diff, Mismatch};
10+
use crate::{Input, Session};
11+
12+
const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";
13+
14+
// This enum is used to represent one of three text features in Configurations.md: a block of code
15+
// with its starting line number, the name of a rustfmt configuration option, or the value of a
16+
// rustfmt configuration option.
17+
enum ConfigurationSection {
18+
CodeBlock((String, u32)), // (String: block of code, u32: line number of code block start)
19+
ConfigName(String),
20+
ConfigValue(String),
21+
}
22+
23+
impl ConfigurationSection {
24+
fn get_section<I: Iterator<Item = String>>(
25+
file: &mut Enumerate<I>,
26+
) -> Option<ConfigurationSection> {
27+
lazy_static! {
28+
static ref CONFIG_NAME_REGEX: regex::Regex =
29+
regex::Regex::new(r"^## `([^`]+)`").expect("failed creating configuration pattern");
30+
static ref CONFIG_VALUE_REGEX: regex::Regex =
31+
regex::Regex::new(r#"^#### `"?([^`"]+)"?`"#)
32+
.expect("failed creating configuration value pattern");
33+
}
34+
35+
loop {
36+
match file.next() {
37+
Some((i, line)) => {
38+
if line.starts_with("```rust") {
39+
// Get the lines of the code block.
40+
let lines: Vec<String> = file
41+
.map(|(_i, l)| l)
42+
.take_while(|l| !l.starts_with("```"))
43+
.collect();
44+
let block = format!("{}\n", lines.join("\n"));
45+
46+
// +1 to translate to one-based indexing
47+
// +1 to get to first line of code (line after "```")
48+
let start_line = (i + 2) as u32;
49+
50+
return Some(ConfigurationSection::CodeBlock((block, start_line)));
51+
} else if let Some(c) = CONFIG_NAME_REGEX.captures(&line) {
52+
return Some(ConfigurationSection::ConfigName(String::from(&c[1])));
53+
} else if let Some(c) = CONFIG_VALUE_REGEX.captures(&line) {
54+
return Some(ConfigurationSection::ConfigValue(String::from(&c[1])));
55+
}
56+
}
57+
None => return None, // reached the end of the file
58+
}
59+
}
60+
}
61+
}
62+
63+
// This struct stores the information about code blocks in the configurations
64+
// file, formats the code blocks, and prints formatting errors.
65+
struct ConfigCodeBlock {
66+
config_name: Option<String>,
67+
config_value: Option<String>,
68+
code_block: Option<String>,
69+
code_block_start: Option<u32>,
70+
}
71+
72+
impl ConfigCodeBlock {
73+
fn new() -> ConfigCodeBlock {
74+
ConfigCodeBlock {
75+
config_name: None,
76+
config_value: None,
77+
code_block: None,
78+
code_block_start: None,
79+
}
80+
}
81+
82+
fn set_config_name(&mut self, name: Option<String>) {
83+
self.config_name = name;
84+
self.config_value = None;
85+
}
86+
87+
fn set_config_value(&mut self, value: Option<String>) {
88+
self.config_value = value;
89+
}
90+
91+
fn set_code_block(&mut self, code_block: String, code_block_start: u32) {
92+
self.code_block = Some(code_block);
93+
self.code_block_start = Some(code_block_start);
94+
}
95+
96+
fn get_block_config(&self) -> Config {
97+
let mut config = Config::default();
98+
config.set().verbose(Verbosity::Quiet);
99+
if self.config_name.is_some() && self.config_value.is_some() {
100+
config.override_value(
101+
self.config_name.as_ref().unwrap(),
102+
self.config_value.as_ref().unwrap(),
103+
);
104+
}
105+
config
106+
}
107+
108+
fn code_block_valid(&self) -> bool {
109+
// We never expect to not have a code block.
110+
assert!(self.code_block.is_some() && self.code_block_start.is_some());
111+
112+
// See if code block begins with #![rustfmt::skip].
113+
let fmt_skip = self
114+
.code_block
115+
.as_ref()
116+
.unwrap()
117+
.lines()
118+
.nth(0)
119+
.unwrap_or("")
120+
== "#![rustfmt::skip]";
121+
122+
if self.config_name.is_none() && !fmt_skip {
123+
write_message(&format!(
124+
"No configuration name for {}:{}",
125+
CONFIGURATIONS_FILE_NAME,
126+
self.code_block_start.unwrap()
127+
));
128+
return false;
129+
}
130+
if self.config_value.is_none() && !fmt_skip {
131+
write_message(&format!(
132+
"No configuration value for {}:{}",
133+
CONFIGURATIONS_FILE_NAME,
134+
self.code_block_start.unwrap()
135+
));
136+
return false;
137+
}
138+
true
139+
}
140+
141+
fn has_parsing_errors<T: Write>(&self, session: &Session<'_, T>) -> bool {
142+
if session.has_parsing_errors() {
143+
write_message(&format!(
144+
"\u{261d}\u{1f3fd} Cannot format {}:{}",
145+
CONFIGURATIONS_FILE_NAME,
146+
self.code_block_start.unwrap()
147+
));
148+
return true;
149+
}
150+
151+
false
152+
}
153+
154+
fn print_diff(&self, compare: Vec<Mismatch>) {
155+
let mut mismatches = HashMap::new();
156+
mismatches.insert(PathBuf::from(CONFIGURATIONS_FILE_NAME), compare);
157+
print_mismatches(mismatches, |line_num| {
158+
format!(
159+
"\nMismatch at {}:{}:",
160+
CONFIGURATIONS_FILE_NAME,
161+
line_num + self.code_block_start.unwrap() - 1
162+
)
163+
});
164+
}
165+
166+
fn formatted_has_diff(&self, text: &str) -> bool {
167+
let compare = make_diff(self.code_block.as_ref().unwrap(), text, DIFF_CONTEXT_SIZE);
168+
if !compare.is_empty() {
169+
self.print_diff(compare);
170+
return true;
171+
}
172+
173+
false
174+
}
175+
176+
// Return a bool indicating if formatting this code block is an idempotent
177+
// operation. This function also triggers printing any formatting failure
178+
// messages.
179+
fn formatted_is_idempotent(&self) -> bool {
180+
// Verify that we have all of the expected information.
181+
if !self.code_block_valid() {
182+
return false;
183+
}
184+
185+
let input = Input::Text(self.code_block.as_ref().unwrap().to_owned());
186+
let mut config = self.get_block_config();
187+
config.set().emit_mode(EmitMode::Stdout);
188+
let mut buf: Vec<u8> = vec![];
189+
190+
{
191+
let mut session = Session::new(config, Some(&mut buf));
192+
session.format(input).unwrap();
193+
if self.has_parsing_errors(&session) {
194+
return false;
195+
}
196+
}
197+
198+
!self.formatted_has_diff(&String::from_utf8(buf).unwrap())
199+
}
200+
201+
// Extract a code block from the iterator. Behavior:
202+
// - Rust code blocks are identifed by lines beginning with "```rust".
203+
// - One explicit configuration setting is supported per code block.
204+
// - Rust code blocks with no configuration setting are illegal and cause an
205+
// assertion failure, unless the snippet begins with #![rustfmt::skip].
206+
// - Configuration names in Configurations.md must be in the form of
207+
// "## `NAME`".
208+
// - Configuration values in Configurations.md must be in the form of
209+
// "#### `VALUE`".
210+
fn extract<I: Iterator<Item = String>>(
211+
file: &mut Enumerate<I>,
212+
prev: Option<&ConfigCodeBlock>,
213+
hash_set: &mut HashSet<String>,
214+
) -> Option<ConfigCodeBlock> {
215+
let mut code_block = ConfigCodeBlock::new();
216+
code_block.config_name = prev.and_then(|cb| cb.config_name.clone());
217+
218+
loop {
219+
match ConfigurationSection::get_section(file) {
220+
Some(ConfigurationSection::CodeBlock((block, start_line))) => {
221+
code_block.set_code_block(block, start_line);
222+
break;
223+
}
224+
Some(ConfigurationSection::ConfigName(name)) => {
225+
assert!(
226+
Config::is_valid_name(&name),
227+
"an unknown configuration option was found: {}",
228+
name
229+
);
230+
assert!(
231+
hash_set.remove(&name),
232+
"multiple configuration guides found for option {}",
233+
name
234+
);
235+
code_block.set_config_name(Some(name));
236+
}
237+
Some(ConfigurationSection::ConfigValue(value)) => {
238+
code_block.set_config_value(Some(value));
239+
}
240+
None => return None, // end of file was reached
241+
}
242+
}
243+
244+
Some(code_block)
245+
}
246+
}
247+
248+
#[test]
249+
fn configuration_snippet_tests() {
250+
let blocks = get_code_blocks();
251+
let failures = blocks
252+
.iter()
253+
.map(ConfigCodeBlock::formatted_is_idempotent)
254+
.fold(0, |acc, r| acc + (!r as u32));
255+
256+
// Display results.
257+
println!("Ran {} configurations tests.", blocks.len());
258+
assert_eq!(failures, 0, "{} configurations tests failed", failures);
259+
}
260+
261+
// Read Configurations.md and build a `Vec` of `ConfigCodeBlock` structs with one
262+
// entry for each Rust code block found.
263+
fn get_code_blocks() -> Vec<ConfigCodeBlock> {
264+
let mut file_iter = BufReader::new(
265+
fs::File::open(Path::new(CONFIGURATIONS_FILE_NAME))
266+
.unwrap_or_else(|_| panic!("couldn't read file {}", CONFIGURATIONS_FILE_NAME)),
267+
)
268+
.lines()
269+
.map(Result::unwrap)
270+
.enumerate();
271+
let mut code_blocks: Vec<ConfigCodeBlock> = Vec::new();
272+
let mut hash_set = Config::hash_set();
273+
274+
while let Some(cb) = ConfigCodeBlock::extract(&mut file_iter, code_blocks.last(), &mut hash_set)
275+
{
276+
code_blocks.push(cb);
277+
}
278+
279+
for name in hash_set {
280+
if !Config::is_hidden_option(&name) {
281+
panic!("{} does not have a configuration guide", name);
282+
}
283+
}
284+
285+
code_blocks
286+
}

0 commit comments

Comments
 (0)