mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-30 03:27:07 +00:00 
			
		
		
		
	Hide empty snippets for full-file diagnostics (#19653)
Summary
--
This is the other commit I wanted to spin off from #19415, currently
stacked on #19644.
This PR suppresses blank snippets for empty ranges at the very beginning
of a file, and for empty ranges in non-existent files. Ruff includes
empty ranges for IO errors, for example.
f4e93b6335/crates/ruff_linter/src/message/text.rs (L100-L110)
The diagnostics now look like this (new snapshot test):
```
error[test-diagnostic]: main diagnostic message
--> example.py:1:1                             
```
Instead of [^*]
```
error[test-diagnostic]: main diagnostic message
--> example.py:1:1
 |
 |
```
Test Plan
--
A new `ruff_db` test showing the expected output format
[^*]: This doesn't correspond precisely to the example in the PR because
of some details of the diagnostic builder helper methods in `ruff_db`,
but you can see another example in the current version of the summary in
#19415.
			
			
This commit is contained in:
		
							parent
							
								
									2db4e5dbea
								
							
						
					
					
						commit
						b324ae1be3
					
				
					 6 changed files with 88 additions and 5 deletions
				
			
		|  | @ -712,6 +712,11 @@ pub struct Annotation { | |||
|     is_primary: bool, | ||||
|     /// The diagnostic tags associated with this annotation.
 | ||||
|     tags: Vec<DiagnosticTag>, | ||||
|     /// Whether this annotation is a file-level or full-file annotation.
 | ||||
|     ///
 | ||||
|     /// When set, rendering will only include the file's name and (optional) range. Everything else
 | ||||
|     /// is omitted, including any file snippet or message.
 | ||||
|     is_file_level: bool, | ||||
| } | ||||
| 
 | ||||
| impl Annotation { | ||||
|  | @ -730,6 +735,7 @@ impl Annotation { | |||
|             message: None, | ||||
|             is_primary: true, | ||||
|             tags: Vec::new(), | ||||
|             is_file_level: false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -746,6 +752,7 @@ impl Annotation { | |||
|             message: None, | ||||
|             is_primary: false, | ||||
|             tags: Vec::new(), | ||||
|             is_file_level: false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -811,6 +818,21 @@ impl Annotation { | |||
|     pub fn push_tag(&mut self, tag: DiagnosticTag) { | ||||
|         self.tags.push(tag); | ||||
|     } | ||||
| 
 | ||||
|     /// Set whether or not this annotation is file-level.
 | ||||
|     ///
 | ||||
|     /// File-level annotations are only rendered with their file name and range, if available. This
 | ||||
|     /// is intended for backwards compatibility with Ruff diagnostics, which historically used
 | ||||
|     /// `TextRange::default` to indicate a file-level diagnostic. In the new diagnostic model, a
 | ||||
|     /// [`Span`] with a range of `None` should be used instead, as mentioned in the `Span`
 | ||||
|     /// documentation.
 | ||||
|     ///
 | ||||
|     /// TODO(brent) update this usage in Ruff and remove `is_file_level` entirely. See
 | ||||
|     /// <https://github.com/astral-sh/ruff/issues/19688>, especially my first comment, for more
 | ||||
|     /// details.
 | ||||
|     pub fn set_file_level(&mut self, yes: bool) { | ||||
|         self.is_file_level = yes; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Tags that can be associated with an annotation.
 | ||||
|  |  | |||
|  | @ -387,6 +387,7 @@ struct ResolvedAnnotation<'a> { | |||
|     line_end: OneIndexed, | ||||
|     message: Option<&'a str>, | ||||
|     is_primary: bool, | ||||
|     is_file_level: bool, | ||||
| } | ||||
| 
 | ||||
| impl<'a> ResolvedAnnotation<'a> { | ||||
|  | @ -432,6 +433,7 @@ impl<'a> ResolvedAnnotation<'a> { | |||
|             line_end, | ||||
|             message: ann.get_message(), | ||||
|             is_primary: ann.is_primary, | ||||
|             is_file_level: ann.is_file_level, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | @ -653,6 +655,8 @@ struct RenderableAnnotation<'r> { | |||
|     message: Option<&'r str>, | ||||
|     /// Whether this annotation is considered "primary" or not.
 | ||||
|     is_primary: bool, | ||||
|     /// Whether this annotation applies to an entire file, rather than a snippet within it.
 | ||||
|     is_file_level: bool, | ||||
| } | ||||
| 
 | ||||
| impl<'r> RenderableAnnotation<'r> { | ||||
|  | @ -670,6 +674,7 @@ impl<'r> RenderableAnnotation<'r> { | |||
|             range, | ||||
|             message: ann.message, | ||||
|             is_primary: ann.is_primary, | ||||
|             is_file_level: ann.is_file_level, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -695,7 +700,7 @@ impl<'r> RenderableAnnotation<'r> { | |||
|         if let Some(message) = self.message { | ||||
|             ann = ann.label(message); | ||||
|         } | ||||
|         ann | ||||
|         ann.is_file_level(self.is_file_level) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -2551,7 +2556,12 @@ watermelon | |||
|         /// of the corresponding line minus one. (The "minus one" is because
 | ||||
|         /// otherwise, the span will end where the next line begins, and this
 | ||||
|         /// confuses `ruff_annotate_snippets` as of 2025-03-13.)
 | ||||
|         fn span(&self, path: &str, line_offset_start: &str, line_offset_end: &str) -> Span { | ||||
|         pub(super) fn span( | ||||
|             &self, | ||||
|             path: &str, | ||||
|             line_offset_start: &str, | ||||
|             line_offset_end: &str, | ||||
|         ) -> Span { | ||||
|             let span = self.path(path); | ||||
| 
 | ||||
|             let file = span.expect_ty_file(); | ||||
|  | @ -2574,7 +2584,7 @@ watermelon | |||
|         } | ||||
| 
 | ||||
|         /// Like `span`, but only attaches a file path.
 | ||||
|         fn path(&self, path: &str) -> Span { | ||||
|         pub(super) fn path(&self, path: &str) -> Span { | ||||
|             let file = system_path_to_file(&self.db, path).unwrap(); | ||||
|             Span::from(file) | ||||
|         } | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use ruff_diagnostics::Applicability; | ||||
|     use ruff_text_size::TextRange; | ||||
| 
 | ||||
|     use crate::diagnostic::{ | ||||
|         DiagnosticFormat, Severity, | ||||
|         Annotation, DiagnosticFormat, Severity, | ||||
|         render::tests::{TestEnvironment, create_diagnostics, create_syntax_error_diagnostics}, | ||||
|     }; | ||||
| 
 | ||||
|  | @ -264,4 +265,24 @@ print() | |||
|           | | ||||
|         ");
 | ||||
|     } | ||||
| 
 | ||||
|     /// For file-level diagnostics, we expect to see the header line with the diagnostic information
 | ||||
|     /// and the `-->` line with the file information but no lines of source code.
 | ||||
|     #[test] | ||||
|     fn file_level() { | ||||
|         let mut env = TestEnvironment::new(); | ||||
|         env.add("example.py", ""); | ||||
|         env.format(DiagnosticFormat::Full); | ||||
| 
 | ||||
|         let mut diagnostic = env.err().build(); | ||||
|         let span = env.path("example.py").with_range(TextRange::default()); | ||||
|         let mut annotation = Annotation::primary(span); | ||||
|         annotation.set_file_level(true); | ||||
|         diagnostic.annotate(annotation); | ||||
| 
 | ||||
|         insta::assert_snapshot!(env.render(&diagnostic), @r" | ||||
|         error[test-diagnostic]: main diagnostic message | ||||
|         --> example.py:1:1 | ||||
|         ");
 | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Brent Westbrook
						Brent Westbrook