Skip to content

Commit f048fc0

Browse files
authored
Merge pull request rust-lang#3448 from topecongiro/use-new_sub_parser_from_file
Support path clarity module even when we start from internal module
2 parents ad6d898 + be0ada2 commit f048fc0

File tree

10 files changed

+230
-89
lines changed

10 files changed

+230
-89
lines changed

src/formatting.rs

+30-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use syntax::ast;
1010
use syntax::errors::emitter::{ColorConfig, EmitterWriter};
1111
use syntax::errors::{DiagnosticBuilder, Handler};
1212
use syntax::parse::{self, ParseSess};
13-
use syntax::source_map::{FilePathMapping, SourceMap, Span};
13+
use syntax::source_map::{FilePathMapping, SourceMap, Span, DUMMY_SP};
1414

1515
use crate::comment::{CharClasses, FullCodeCharKind};
1616
use crate::config::{Config, FileName, Verbosity};
@@ -73,7 +73,14 @@ fn format_project<T: FormatHandler>(
7373
let source_map = Rc::new(SourceMap::new(FilePathMapping::empty()));
7474
let mut parse_session = make_parse_sess(source_map.clone(), config);
7575
let mut report = FormatReport::new();
76-
let krate = match parse_crate(input, &parse_session, config, &mut report) {
76+
let directory_ownership = input.to_directory_ownership();
77+
let krate = match parse_crate(
78+
input,
79+
&parse_session,
80+
config,
81+
&mut report,
82+
directory_ownership,
83+
) {
7784
Ok(krate) => krate,
7885
// Surface parse error via Session (errors are merged there from report)
7986
Err(ErrorKind::ParseError) => return Ok(report),
@@ -87,8 +94,14 @@ fn format_project<T: FormatHandler>(
8794

8895
let mut context = FormatContext::new(&krate, report, parse_session, config, handler);
8996

90-
let files = modules::list_files(&krate, context.parse_session.source_map())?;
91-
for (path, module) in files {
97+
let files = modules::ModResolver::new(
98+
context.parse_session.source_map(),
99+
directory_ownership.unwrap_or(parse::DirectoryOwnership::UnownedViaMod(false)),
100+
input_is_stdin,
101+
)
102+
.visit_crate(&krate)
103+
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
104+
for (path, (module, _)) in files {
92105
if (config.skip_children() && path != main_file) || config.ignore().skip_file(&path) {
93106
continue;
94107
}
@@ -593,16 +606,28 @@ fn parse_crate(
593606
parse_session: &ParseSess,
594607
config: &Config,
595608
report: &mut FormatReport,
609+
directory_ownership: Option<parse::DirectoryOwnership>,
596610
) -> Result<ast::Crate, ErrorKind> {
597611
let input_is_stdin = input.is_text();
598612

599613
let parser = match input {
600-
Input::File(file) => Ok(parse::new_parser_from_file(parse_session, &file)),
614+
Input::File(ref file) => {
615+
// Use `new_sub_parser_from_file` when we the input is a submodule.
616+
Ok(if let Some(dir_own) = directory_ownership {
617+
parse::new_sub_parser_from_file(parse_session, file, dir_own, None, DUMMY_SP)
618+
} else {
619+
parse::new_parser_from_file(parse_session, file)
620+
})
621+
}
601622
Input::Text(text) => parse::maybe_new_parser_from_source_str(
602623
parse_session,
603624
syntax::source_map::FileName::Custom("stdin".to_owned()),
604625
text,
605626
)
627+
.map(|mut parser| {
628+
parser.recurse_into_file_modules = false;
629+
parser
630+
})
606631
.map_err(|diags| {
607632
diags
608633
.into_iter()

src/lib.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::path::PathBuf;
2020
use std::rc::Rc;
2121

2222
use failure::Fail;
23-
use syntax::ast;
23+
use syntax::{ast, parse::DirectoryOwnership};
2424

2525
use crate::comment::LineClasses;
2626
use crate::formatting::{FormatErrorMap, FormattingError, ReportedErrors, SourceFile};
@@ -586,6 +586,24 @@ impl Input {
586586
Input::Text(..) => FileName::Stdin,
587587
}
588588
}
589+
590+
fn to_directory_ownership(&self) -> Option<DirectoryOwnership> {
591+
match self {
592+
Input::File(ref file) => {
593+
// If there exists a directory with the same name as an input,
594+
// then the input should be parsed as a sub module.
595+
let file_stem = file.file_stem()?;
596+
if file.parent()?.to_path_buf().join(file_stem).is_dir() {
597+
Some(DirectoryOwnership::Owned {
598+
relative: file_stem.to_str().map(ast::Ident::from_str),
599+
})
600+
} else {
601+
None
602+
}
603+
}
604+
_ => None,
605+
}
606+
}
589607
}
590608

591609
#[cfg(test)]

src/modules.rs

+147-82
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::collections::BTreeMap;
2-
use std::io;
32
use std::path::{Path, PathBuf};
43

54
use syntax::ast;
@@ -8,25 +7,157 @@ use syntax::source_map;
87
use syntax_pos::symbol::Symbol;
98

109
use crate::config::FileName;
10+
use crate::items::is_mod_decl;
1111
use crate::utils::contains_skip;
1212

13-
/// List all the files containing modules of a crate.
14-
/// If a file is used twice in a crate, it appears only once.
15-
pub fn list_files<'a>(
16-
krate: &'a ast::Crate,
17-
source_map: &source_map::SourceMap,
18-
) -> Result<BTreeMap<FileName, &'a ast::Mod>, io::Error> {
19-
let mut result = BTreeMap::new(); // Enforce file order determinism
20-
let root_filename = source_map.span_to_filename(krate.span);
21-
{
22-
let parent = match root_filename {
23-
source_map::FileName::Real(ref path) => path.parent().unwrap(),
24-
_ => Path::new(""),
13+
type FileModMap<'a> = BTreeMap<FileName, (&'a ast::Mod, &'a str)>;
14+
15+
/// Maps each module to the corresponding file.
16+
pub struct ModResolver<'a, 'b> {
17+
source_map: &'b source_map::SourceMap,
18+
directory: Directory,
19+
file_map: FileModMap<'a>,
20+
is_input_stdin: bool,
21+
}
22+
23+
#[derive(Clone)]
24+
struct Directory {
25+
path: PathBuf,
26+
ownership: DirectoryOwnership,
27+
}
28+
29+
impl<'a, 'b> ModResolver<'a, 'b> {
30+
/// Creates a new `ModResolver`.
31+
pub fn new(
32+
source_map: &'b source_map::SourceMap,
33+
directory_ownership: DirectoryOwnership,
34+
is_input_stdin: bool,
35+
) -> Self {
36+
ModResolver {
37+
directory: Directory {
38+
path: PathBuf::new(),
39+
ownership: directory_ownership,
40+
},
41+
file_map: BTreeMap::new(),
42+
source_map,
43+
is_input_stdin,
44+
}
45+
}
46+
47+
/// Creates a map that maps a file name to the module in AST.
48+
pub fn visit_crate(mut self, krate: &'a ast::Crate) -> Result<FileModMap<'a>, String> {
49+
let root_filename = self.source_map.span_to_filename(krate.span);
50+
self.directory.path = match root_filename {
51+
source_map::FileName::Real(ref path) => path
52+
.parent()
53+
.expect("Parent directory should exists")
54+
.to_path_buf(),
55+
_ => PathBuf::new(),
56+
};
57+
58+
// Skip visiting sub modules when the input is from stdin.
59+
if !self.is_input_stdin {
60+
self.visit_mod(&krate.module)?;
61+
}
62+
63+
self.file_map
64+
.insert(root_filename.into(), (&krate.module, ""));
65+
Ok(self.file_map)
66+
}
67+
68+
fn visit_mod(&mut self, module: &'a ast::Mod) -> Result<(), String> {
69+
for item in &module.items {
70+
if let ast::ItemKind::Mod(ref sub_mod) = item.node {
71+
if contains_skip(&item.attrs) {
72+
continue;
73+
}
74+
75+
let old_direcotry = self.directory.clone();
76+
if is_mod_decl(item) {
77+
// mod foo;
78+
// Look for an extern file.
79+
let (mod_path, directory_ownership) =
80+
self.find_external_module(item.ident, &item.attrs)?;
81+
self.file_map.insert(
82+
FileName::Real(mod_path.clone()),
83+
(sub_mod, item.ident.name.as_str().get()),
84+
);
85+
self.directory = Directory {
86+
path: mod_path.parent().unwrap().to_path_buf(),
87+
ownership: directory_ownership,
88+
}
89+
} else {
90+
// An internal module (`mod foo { /* ... */ }`);
91+
if let Some(path) = find_path_value(&item.attrs) {
92+
// All `#[path]` files are treated as though they are a `mod.rs` file.
93+
self.directory = Directory {
94+
path: Path::new(&path.as_str()).to_path_buf(),
95+
ownership: DirectoryOwnership::Owned { relative: None },
96+
};
97+
} else {
98+
self.push_inline_mod_directory(item.ident, &item.attrs);
99+
}
100+
}
101+
self.visit_mod(sub_mod)?;
102+
self.directory = old_direcotry;
103+
}
104+
}
105+
Ok(())
106+
}
107+
108+
fn find_external_module(
109+
&self,
110+
mod_name: ast::Ident,
111+
attrs: &[ast::Attribute],
112+
) -> Result<(PathBuf, DirectoryOwnership), String> {
113+
if let Some(path) = parser::Parser::submod_path_from_attr(attrs, &self.directory.path) {
114+
return Ok((path, DirectoryOwnership::Owned { relative: None }));
115+
}
116+
117+
let relative = match self.directory.ownership {
118+
DirectoryOwnership::Owned { relative } => relative,
119+
DirectoryOwnership::UnownedViaBlock | DirectoryOwnership::UnownedViaMod(_) => None,
25120
};
26-
list_submodules(&krate.module, parent, None, source_map, &mut result)?;
121+
match parser::Parser::default_submod_path(
122+
mod_name,
123+
relative,
124+
&self.directory.path,
125+
self.source_map,
126+
)
127+
.result
128+
{
129+
Ok(parser::ModulePathSuccess {
130+
path,
131+
directory_ownership,
132+
..
133+
}) => Ok((path, directory_ownership)),
134+
Err(_) => Err(format!(
135+
"Failed to find module {} in {:?} {:?}",
136+
mod_name, self.directory.path, relative,
137+
)),
138+
}
139+
}
140+
141+
fn push_inline_mod_directory(&mut self, id: ast::Ident, attrs: &[ast::Attribute]) {
142+
if let Some(path) = find_path_value(attrs) {
143+
self.directory.path.push(&path.as_str());
144+
self.directory.ownership = DirectoryOwnership::Owned { relative: None };
145+
} else {
146+
// We have to push on the current module name in the case of relative
147+
// paths in order to ensure that any additional module paths from inline
148+
// `mod x { ... }` come after the relative extension.
149+
//
150+
// For example, a `mod z { ... }` inside `x/y.rs` should set the current
151+
// directory path to `/x/y/z`, not `/x/z` with a relative offset of `y`.
152+
if let DirectoryOwnership::Owned { relative } = &mut self.directory.ownership {
153+
if let Some(ident) = relative.take() {
154+
// remove the relative offset
155+
self.directory.path.push(ident.as_str());
156+
}
157+
}
158+
self.directory.path.push(&id.as_str());
159+
}
27160
}
28-
result.insert(root_filename.into(), &krate.module);
29-
Ok(result)
30161
}
31162

32163
fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
@@ -43,69 +174,3 @@ fn path_value(attr: &ast::Attribute) -> Option<Symbol> {
43174
fn find_path_value(attrs: &[ast::Attribute]) -> Option<Symbol> {
44175
attrs.iter().flat_map(path_value).next()
45176
}
46-
47-
/// Recursively list all external modules included in a module.
48-
fn list_submodules<'a>(
49-
module: &'a ast::Mod,
50-
search_dir: &Path,
51-
relative: Option<ast::Ident>,
52-
source_map: &source_map::SourceMap,
53-
result: &mut BTreeMap<FileName, &'a ast::Mod>,
54-
) -> Result<(), io::Error> {
55-
debug!("list_submodules: search_dir: {:?}", search_dir);
56-
for item in &module.items {
57-
if let ast::ItemKind::Mod(ref sub_mod) = item.node {
58-
if !contains_skip(&item.attrs) {
59-
let is_internal = source_map.span_to_filename(item.span)
60-
== source_map.span_to_filename(sub_mod.inner);
61-
let (dir_path, relative) = if is_internal {
62-
if let Some(path) = find_path_value(&item.attrs) {
63-
(search_dir.join(&path.as_str()), None)
64-
} else {
65-
(search_dir.join(&item.ident.to_string()), None)
66-
}
67-
} else {
68-
let (mod_path, relative) =
69-
module_file(item.ident, &item.attrs, search_dir, relative, source_map)?;
70-
let dir_path = mod_path.parent().unwrap().to_owned();
71-
result.insert(FileName::Real(mod_path), sub_mod);
72-
(dir_path, relative)
73-
};
74-
list_submodules(sub_mod, &dir_path, relative, source_map, result)?;
75-
}
76-
}
77-
}
78-
Ok(())
79-
}
80-
81-
/// Finds the file corresponding to an external mod
82-
fn module_file(
83-
id: ast::Ident,
84-
attrs: &[ast::Attribute],
85-
dir_path: &Path,
86-
relative: Option<ast::Ident>,
87-
source_map: &source_map::SourceMap,
88-
) -> Result<(PathBuf, Option<ast::Ident>), io::Error> {
89-
if let Some(path) = parser::Parser::submod_path_from_attr(attrs, dir_path) {
90-
return Ok((path, None));
91-
}
92-
93-
match parser::Parser::default_submod_path(id, relative, dir_path, source_map).result {
94-
Ok(parser::ModulePathSuccess {
95-
path,
96-
directory_ownership,
97-
..
98-
}) => {
99-
let relative = if let DirectoryOwnership::Owned { relative } = directory_ownership {
100-
relative
101-
} else {
102-
None
103-
};
104-
Ok((path, relative))
105-
}
106-
Err(_) => Err(io::Error::new(
107-
io::ErrorKind::Other,
108-
format!("Couldn't find module {}", id),
109-
)),
110-
}
111-
}

src/test/mod.rs

+18-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ use crate::{FormatReport, Input, Session};
1717
const DIFF_CONTEXT_SIZE: usize = 3;
1818
const CONFIGURATIONS_FILE_NAME: &str = "Configurations.md";
1919

20+
// A list of files on which we want to skip testing.
21+
const SKIP_FILE_WHITE_LIST: &[&str] = &[
22+
// We want to make sure that the `skip_children` is correctly working,
23+
// so we do not want to test this file directly.
24+
"configs/skip_children/foo/mod.rs",
25+
];
26+
27+
fn is_file_skip(path: &Path) -> bool {
28+
SKIP_FILE_WHITE_LIST
29+
.iter()
30+
.any(|file_path| path.ends_with(file_path))
31+
}
32+
2033
// Returns a `Vec` containing `PathBuf`s of files with an `rs` extension in the
2134
// given path. The `recursive` argument controls if files from subdirectories
2235
// are also returned.
@@ -31,7 +44,7 @@ fn get_test_files(path: &Path, recursive: bool) -> Vec<PathBuf> {
3144
let path = entry.path();
3245
if path.is_dir() && recursive {
3346
files.append(&mut get_test_files(&path, recursive));
34-
} else if path.extension().map_or(false, |f| f == "rs") {
47+
} else if path.extension().map_or(false, |f| f == "rs") && !is_file_skip(&path) {
3548
files.push(path);
3649
}
3750
}
@@ -231,6 +244,10 @@ fn idempotence_tests() {
231244
// no warnings are emitted.
232245
#[test]
233246
fn self_tests() {
247+
match option_env!("CFG_RELEASE_CHANNEL") {
248+
None | Some("nightly") => {}
249+
_ => return, // Issue-3443: these tests require nightly
250+
}
234251
let mut files = get_test_files(Path::new("tests"), false);
235252
let bin_directories = vec!["cargo-fmt", "git-rustfmt", "bin", "format-diff"];
236253
for dir in bin_directories {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn skip_formatting_this() {
2+
println ! ( "Skip this" ) ;
3+
}

0 commit comments

Comments
 (0)