summaryrefslogtreecommitdiffstats
path: root/src/tools/rustfmt/src/parse/session.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/tools/rustfmt/src/parse/session.rs507
1 files changed, 507 insertions, 0 deletions
diff --git a/src/tools/rustfmt/src/parse/session.rs b/src/tools/rustfmt/src/parse/session.rs
new file mode 100644
index 000000000..23db54219
--- /dev/null
+++ b/src/tools/rustfmt/src/parse/session.rs
@@ -0,0 +1,507 @@
+use std::path::Path;
+use std::sync::atomic::{AtomicBool, Ordering};
+
+use rustc_data_structures::sync::{Lrc, Send};
+use rustc_errors::emitter::{Emitter, EmitterWriter};
+use rustc_errors::{ColorConfig, Diagnostic, Handler, Level as DiagnosticLevel};
+use rustc_session::parse::ParseSess as RawParseSess;
+use rustc_span::{
+ source_map::{FilePathMapping, SourceMap},
+ symbol, BytePos, Span,
+};
+
+use crate::config::file_lines::LineRange;
+use crate::ignore_path::IgnorePathSet;
+use crate::parse::parser::{ModError, ModulePathSuccess};
+use crate::source_map::LineRangeUtils;
+use crate::utils::starts_with_newline;
+use crate::visitor::SnippetProvider;
+use crate::{Config, ErrorKind, FileName};
+
+/// ParseSess holds structs necessary for constructing a parser.
+pub(crate) struct ParseSess {
+ parse_sess: RawParseSess,
+ ignore_path_set: Lrc<IgnorePathSet>,
+ can_reset_errors: Lrc<AtomicBool>,
+}
+
+/// Emitter which discards every error.
+struct SilentEmitter;
+
+impl Emitter for SilentEmitter {
+ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
+ None
+ }
+ fn emit_diagnostic(&mut self, _db: &Diagnostic) {}
+ fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+ None
+ }
+ fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
+ panic!("silent emitter attempted to translate a diagnostic");
+ }
+}
+
+fn silent_emitter() -> Box<dyn Emitter + Send> {
+ Box::new(SilentEmitter {})
+}
+
+/// Emit errors against every files expect ones specified in the `ignore_path_set`.
+struct SilentOnIgnoredFilesEmitter {
+ ignore_path_set: Lrc<IgnorePathSet>,
+ source_map: Lrc<SourceMap>,
+ emitter: Box<dyn Emitter + Send>,
+ has_non_ignorable_parser_errors: bool,
+ can_reset: Lrc<AtomicBool>,
+}
+
+impl SilentOnIgnoredFilesEmitter {
+ fn handle_non_ignoreable_error(&mut self, db: &Diagnostic) {
+ self.has_non_ignorable_parser_errors = true;
+ self.can_reset.store(false, Ordering::Release);
+ self.emitter.emit_diagnostic(db);
+ }
+}
+
+impl Emitter for SilentOnIgnoredFilesEmitter {
+ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
+ None
+ }
+ fn emit_diagnostic(&mut self, db: &Diagnostic) {
+ if db.level() == DiagnosticLevel::Fatal {
+ return self.handle_non_ignoreable_error(db);
+ }
+ if let Some(primary_span) = &db.span.primary_span() {
+ let file_name = self.source_map.span_to_filename(*primary_span);
+ if let rustc_span::FileName::Real(rustc_span::RealFileName::LocalPath(ref path)) =
+ file_name
+ {
+ if self
+ .ignore_path_set
+ .is_match(&FileName::Real(path.to_path_buf()))
+ {
+ if !self.has_non_ignorable_parser_errors {
+ self.can_reset.store(true, Ordering::Release);
+ }
+ return;
+ }
+ };
+ }
+ self.handle_non_ignoreable_error(db);
+ }
+
+ fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+ self.emitter.fluent_bundle()
+ }
+
+ fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
+ self.emitter.fallback_fluent_bundle()
+ }
+}
+
+fn default_handler(
+ source_map: Lrc<SourceMap>,
+ ignore_path_set: Lrc<IgnorePathSet>,
+ can_reset: Lrc<AtomicBool>,
+ hide_parse_errors: bool,
+) -> Handler {
+ let supports_color = term::stderr().map_or(false, |term| term.supports_color());
+ let color_cfg = if supports_color {
+ ColorConfig::Auto
+ } else {
+ ColorConfig::Never
+ };
+
+ let emitter = if hide_parse_errors {
+ silent_emitter()
+ } else {
+ let fallback_bundle =
+ rustc_errors::fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false);
+ Box::new(EmitterWriter::stderr(
+ color_cfg,
+ Some(source_map.clone()),
+ None,
+ fallback_bundle,
+ false,
+ false,
+ None,
+ false,
+ ))
+ };
+ Handler::with_emitter(
+ true,
+ None,
+ Box::new(SilentOnIgnoredFilesEmitter {
+ has_non_ignorable_parser_errors: false,
+ source_map,
+ emitter,
+ ignore_path_set,
+ can_reset,
+ }),
+ )
+}
+
+impl ParseSess {
+ pub(crate) fn new(config: &Config) -> Result<ParseSess, ErrorKind> {
+ let ignore_path_set = match IgnorePathSet::from_ignore_list(&config.ignore()) {
+ Ok(ignore_path_set) => Lrc::new(ignore_path_set),
+ Err(e) => return Err(ErrorKind::InvalidGlobPattern(e)),
+ };
+ let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let can_reset_errors = Lrc::new(AtomicBool::new(false));
+
+ let handler = default_handler(
+ Lrc::clone(&source_map),
+ Lrc::clone(&ignore_path_set),
+ Lrc::clone(&can_reset_errors),
+ config.hide_parse_errors(),
+ );
+ let parse_sess = RawParseSess::with_span_handler(handler, source_map);
+
+ Ok(ParseSess {
+ parse_sess,
+ ignore_path_set,
+ can_reset_errors,
+ })
+ }
+
+ /// Determine the submodule path for the given module identifier.
+ ///
+ /// * `id` - The name of the module
+ /// * `relative` - If Some(symbol), the symbol name is a directory relative to the dir_path.
+ /// If relative is Some, resolve the submodle at {dir_path}/{symbol}/{id}.rs
+ /// or {dir_path}/{symbol}/{id}/mod.rs. if None, resolve the module at {dir_path}/{id}.rs.
+ /// * `dir_path` - Module resolution will occur relative to this directory.
+ pub(crate) fn default_submod_path(
+ &self,
+ id: symbol::Ident,
+ relative: Option<symbol::Ident>,
+ dir_path: &Path,
+ ) -> Result<ModulePathSuccess, ModError<'_>> {
+ rustc_expand::module::default_submod_path(&self.parse_sess, id, relative, dir_path).or_else(
+ |e| {
+ // If resloving a module relative to {dir_path}/{symbol} fails because a file
+ // could not be found, then try to resolve the module relative to {dir_path}.
+ // If we still can't find the module after searching for it in {dir_path},
+ // surface the original error.
+ if matches!(e, ModError::FileNotFound(..)) && relative.is_some() {
+ rustc_expand::module::default_submod_path(&self.parse_sess, id, None, dir_path)
+ .map_err(|_| e)
+ } else {
+ Err(e)
+ }
+ },
+ )
+ }
+
+ pub(crate) fn is_file_parsed(&self, path: &Path) -> bool {
+ self.parse_sess
+ .source_map()
+ .get_source_file(&rustc_span::FileName::Real(
+ rustc_span::RealFileName::LocalPath(path.to_path_buf()),
+ ))
+ .is_some()
+ }
+
+ pub(crate) fn ignore_file(&self, path: &FileName) -> bool {
+ self.ignore_path_set.as_ref().is_match(path)
+ }
+
+ pub(crate) fn set_silent_emitter(&mut self) {
+ self.parse_sess.span_diagnostic = Handler::with_emitter(true, None, silent_emitter());
+ }
+
+ pub(crate) fn span_to_filename(&self, span: Span) -> FileName {
+ self.parse_sess.source_map().span_to_filename(span).into()
+ }
+
+ pub(crate) fn span_to_file_contents(&self, span: Span) -> Lrc<rustc_span::SourceFile> {
+ self.parse_sess
+ .source_map()
+ .lookup_source_file(span.data().lo)
+ }
+
+ pub(crate) fn span_to_first_line_string(&self, span: Span) -> String {
+ let file_lines = self.parse_sess.source_map().span_to_lines(span).ok();
+
+ match file_lines {
+ Some(fl) => fl
+ .file
+ .get_line(fl.lines[0].line_index)
+ .map_or_else(String::new, |s| s.to_string()),
+ None => String::new(),
+ }
+ }
+
+ pub(crate) fn line_of_byte_pos(&self, pos: BytePos) -> usize {
+ self.parse_sess.source_map().lookup_char_pos(pos).line
+ }
+
+ // TODO(calebcartwright): Preemptive, currently unused addition
+ // that will be used to support formatting scenarios that take original
+ // positions into account
+ /// Determines whether two byte positions are in the same source line.
+ #[allow(dead_code)]
+ pub(crate) fn byte_pos_same_line(&self, a: BytePos, b: BytePos) -> bool {
+ self.line_of_byte_pos(a) == self.line_of_byte_pos(b)
+ }
+
+ pub(crate) fn span_to_debug_info(&self, span: Span) -> String {
+ self.parse_sess.source_map().span_to_diagnostic_string(span)
+ }
+
+ pub(crate) fn inner(&self) -> &RawParseSess {
+ &self.parse_sess
+ }
+
+ pub(crate) fn snippet_provider(&self, span: Span) -> SnippetProvider {
+ let source_file = self.parse_sess.source_map().lookup_char_pos(span.lo()).file;
+ SnippetProvider::new(
+ source_file.start_pos,
+ source_file.end_pos,
+ Lrc::clone(source_file.src.as_ref().unwrap()),
+ )
+ }
+
+ pub(crate) fn get_original_snippet(&self, file_name: &FileName) -> Option<Lrc<String>> {
+ self.parse_sess
+ .source_map()
+ .get_source_file(&file_name.into())
+ .and_then(|source_file| source_file.src.clone())
+ }
+}
+
+// Methods that should be restricted within the parse module.
+impl ParseSess {
+ pub(super) fn emit_diagnostics(&self, diagnostics: Vec<Diagnostic>) {
+ for mut diagnostic in diagnostics {
+ self.parse_sess
+ .span_diagnostic
+ .emit_diagnostic(&mut diagnostic);
+ }
+ }
+
+ pub(super) fn can_reset_errors(&self) -> bool {
+ self.can_reset_errors.load(Ordering::Acquire)
+ }
+
+ pub(super) fn has_errors(&self) -> bool {
+ self.parse_sess.span_diagnostic.has_errors().is_some()
+ }
+
+ pub(super) fn reset_errors(&self) {
+ self.parse_sess.span_diagnostic.reset_err_count();
+ }
+}
+
+impl LineRangeUtils for ParseSess {
+ fn lookup_line_range(&self, span: Span) -> LineRange {
+ let snippet = self
+ .parse_sess
+ .source_map()
+ .span_to_snippet(span)
+ .unwrap_or_default();
+ let lo = self.parse_sess.source_map().lookup_line(span.lo()).unwrap();
+ let hi = self.parse_sess.source_map().lookup_line(span.hi()).unwrap();
+
+ debug_assert_eq!(
+ lo.sf.name, hi.sf.name,
+ "span crossed file boundary: lo: {:?}, hi: {:?}",
+ lo, hi
+ );
+
+ // in case the span starts with a newline, the line range is off by 1 without the
+ // adjustment below
+ let offset = 1 + if starts_with_newline(&snippet) { 1 } else { 0 };
+ // Line numbers start at 1
+ LineRange {
+ file: lo.sf.clone(),
+ lo: lo.line + offset,
+ hi: hi.line + offset,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ use rustfmt_config_proc_macro::nightly_only_test;
+
+ mod emitter {
+ use super::*;
+ use crate::config::IgnoreList;
+ use crate::utils::mk_sp;
+ use rustc_errors::MultiSpan;
+ use rustc_span::{FileName as SourceMapFileName, RealFileName};
+ use std::path::PathBuf;
+ use std::sync::atomic::AtomicU32;
+
+ struct TestEmitter {
+ num_emitted_errors: Lrc<AtomicU32>,
+ }
+
+ impl Emitter for TestEmitter {
+ fn source_map(&self) -> Option<&Lrc<SourceMap>> {
+ None
+ }
+ fn emit_diagnostic(&mut self, _db: &Diagnostic) {
+ self.num_emitted_errors.fetch_add(1, Ordering::Release);
+ }
+ fn fluent_bundle(&self) -> Option<&Lrc<rustc_errors::FluentBundle>> {
+ None
+ }
+ fn fallback_fluent_bundle(&self) -> &rustc_errors::FluentBundle {
+ panic!("test emitter attempted to translate a diagnostic");
+ }
+ }
+
+ fn build_diagnostic(level: DiagnosticLevel, span: Option<MultiSpan>) -> Diagnostic {
+ let mut diag = Diagnostic::new(level, "");
+ diag.message.clear();
+ if let Some(span) = span {
+ diag.span = span;
+ }
+ diag
+ }
+
+ fn build_emitter(
+ num_emitted_errors: Lrc<AtomicU32>,
+ can_reset: Lrc<AtomicBool>,
+ source_map: Option<Lrc<SourceMap>>,
+ ignore_list: Option<IgnoreList>,
+ ) -> SilentOnIgnoredFilesEmitter {
+ let emitter_writer = TestEmitter { num_emitted_errors };
+ let source_map =
+ source_map.unwrap_or_else(|| Lrc::new(SourceMap::new(FilePathMapping::empty())));
+ let ignore_path_set = Lrc::new(
+ IgnorePathSet::from_ignore_list(&ignore_list.unwrap_or_default()).unwrap(),
+ );
+ SilentOnIgnoredFilesEmitter {
+ has_non_ignorable_parser_errors: false,
+ source_map,
+ emitter: Box::new(emitter_writer),
+ ignore_path_set,
+ can_reset,
+ }
+ }
+
+ fn get_ignore_list(config: &str) -> IgnoreList {
+ Config::from_toml(config, Path::new("")).unwrap().ignore()
+ }
+
+ #[test]
+ fn handles_fatal_parse_error_in_ignored_file() {
+ let num_emitted_errors = Lrc::new(AtomicU32::new(0));
+ let can_reset_errors = Lrc::new(AtomicBool::new(false));
+ let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
+ let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let source =
+ String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
+ source,
+ );
+ let mut emitter = build_emitter(
+ Lrc::clone(&num_emitted_errors),
+ Lrc::clone(&can_reset_errors),
+ Some(Lrc::clone(&source_map)),
+ Some(ignore_list),
+ );
+ let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
+ let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, Some(span));
+ emitter.emit_diagnostic(&fatal_diagnostic);
+ assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
+ assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
+ }
+
+ #[nightly_only_test]
+ #[test]
+ fn handles_recoverable_parse_error_in_ignored_file() {
+ let num_emitted_errors = Lrc::new(AtomicU32::new(0));
+ let can_reset_errors = Lrc::new(AtomicBool::new(false));
+ let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
+ let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let source = String::from(r#"pub fn bar() { 1x; }"#);
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
+ source,
+ );
+ let mut emitter = build_emitter(
+ Lrc::clone(&num_emitted_errors),
+ Lrc::clone(&can_reset_errors),
+ Some(Lrc::clone(&source_map)),
+ Some(ignore_list),
+ );
+ let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
+ let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(span));
+ emitter.emit_diagnostic(&non_fatal_diagnostic);
+ assert_eq!(num_emitted_errors.load(Ordering::Acquire), 0);
+ assert_eq!(can_reset_errors.load(Ordering::Acquire), true);
+ }
+
+ #[nightly_only_test]
+ #[test]
+ fn handles_recoverable_parse_error_in_non_ignored_file() {
+ let num_emitted_errors = Lrc::new(AtomicU32::new(0));
+ let can_reset_errors = Lrc::new(AtomicBool::new(false));
+ let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let source = String::from(r#"pub fn bar() { 1x; }"#);
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
+ source,
+ );
+ let mut emitter = build_emitter(
+ Lrc::clone(&num_emitted_errors),
+ Lrc::clone(&can_reset_errors),
+ Some(Lrc::clone(&source_map)),
+ None,
+ );
+ let span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
+ let non_fatal_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(span));
+ emitter.emit_diagnostic(&non_fatal_diagnostic);
+ assert_eq!(num_emitted_errors.load(Ordering::Acquire), 1);
+ assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
+ }
+
+ #[nightly_only_test]
+ #[test]
+ fn handles_mix_of_recoverable_parse_error() {
+ let num_emitted_errors = Lrc::new(AtomicU32::new(0));
+ let can_reset_errors = Lrc::new(AtomicBool::new(false));
+ let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+ let ignore_list = get_ignore_list(r#"ignore = ["foo.rs"]"#);
+ let bar_source = String::from(r#"pub fn bar() { 1x; }"#);
+ let foo_source = String::from(r#"pub fn foo() { 1x; }"#);
+ let fatal_source =
+ String::from(r#"extern "system" fn jni_symbol!( funcName ) ( ... ) -> {} "#);
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("bar.rs"))),
+ bar_source,
+ );
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("foo.rs"))),
+ foo_source,
+ );
+ source_map.new_source_file(
+ SourceMapFileName::Real(RealFileName::LocalPath(PathBuf::from("fatal.rs"))),
+ fatal_source,
+ );
+ let mut emitter = build_emitter(
+ Lrc::clone(&num_emitted_errors),
+ Lrc::clone(&can_reset_errors),
+ Some(Lrc::clone(&source_map)),
+ Some(ignore_list),
+ );
+ let bar_span = MultiSpan::from_span(mk_sp(BytePos(0), BytePos(1)));
+ let foo_span = MultiSpan::from_span(mk_sp(BytePos(21), BytePos(22)));
+ let bar_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(bar_span));
+ let foo_diagnostic = build_diagnostic(DiagnosticLevel::Warning(None), Some(foo_span));
+ let fatal_diagnostic = build_diagnostic(DiagnosticLevel::Fatal, None);
+ emitter.emit_diagnostic(&bar_diagnostic);
+ emitter.emit_diagnostic(&foo_diagnostic);
+ emitter.emit_diagnostic(&fatal_diagnostic);
+ assert_eq!(num_emitted_errors.load(Ordering::Acquire), 2);
+ assert_eq!(can_reset_errors.load(Ordering::Acquire), false);
+ }
+ }
+}