Skip to content

Commit db04291

Browse files
committed
Store source code on Message
1 parent fb178f3 commit db04291

File tree

20 files changed

+537
-217
lines changed

20 files changed

+537
-217
lines changed

Cargo.lock

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

crates/ruff/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ruff_python_ast = { path = "../ruff_python_ast" }
2121
ruff_python_semantic = { path = "../ruff_python_semantic" }
2222
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
2323
ruff_rustpython = { path = "../ruff_rustpython" }
24+
ruff_text_size = { path = "../ruff_text_size" }
2425

2526
annotate-snippets = { version = "0.9.1", features = ["color"] }
2627
anyhow = { workspace = true }

crates/ruff/src/autofix/actions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ fn is_lone_child(child: &Stmt, parent: &Stmt, deleted: &[&Stmt]) -> Result<bool>
103103
/// Return the location of a trailing semicolon following a `Stmt`, if it's part
104104
/// of a multi-statement line.
105105
fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
106-
let contents = locator.skip(stmt.end_location.unwrap());
106+
let contents = locator.after(stmt.end_location.unwrap());
107107
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
108108
let trimmed = line.trim();
109109
if trimmed.starts_with(';') {
@@ -126,7 +126,7 @@ fn trailing_semicolon(stmt: &Stmt, locator: &Locator) -> Option<Location> {
126126
/// Find the next valid break for a `Stmt` after a semicolon.
127127
fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
128128
let start_location = Location::new(semicolon.row(), semicolon.column() + 1);
129-
let contents = locator.skip(start_location);
129+
let contents = locator.after(start_location);
130130
for (row, line) in NewlineWithTrailingNewline::from(contents).enumerate() {
131131
let trimmed = line.trim();
132132
// Skip past any continuations.
@@ -158,7 +158,7 @@ fn next_stmt_break(semicolon: Location, locator: &Locator) -> Location {
158158

159159
/// Return `true` if a `Stmt` occurs at the end of a file.
160160
fn is_end_of_file(stmt: &Stmt, locator: &Locator) -> bool {
161-
let contents = locator.skip(stmt.end_location.unwrap());
161+
let contents = locator.after(stmt.end_location.unwrap());
162162
contents.is_empty()
163163
}
164164

@@ -361,7 +361,7 @@ pub fn remove_argument(
361361
remove_parentheses: bool,
362362
) -> Result<Edit> {
363363
// TODO(sbrugman): Preserve trailing comments.
364-
let contents = locator.skip(call_at);
364+
let contents = locator.after(call_at);
365365

366366
let mut fix_start = None;
367367
let mut fix_end = None;

crates/ruff/src/autofix/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ fn apply_fixes<'a>(
8080
}
8181

8282
// Add the remaining content.
83-
let slice = locator.skip(last_pos.unwrap_or_default());
83+
let slice = locator.after(last_pos.unwrap_or_default());
8484
output.push_str(slice);
8585

8686
(output, fixed)

crates/ruff/src/importer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ fn match_docstring_end(body: &[Stmt]) -> Option<Location> {
174174
/// along with a trailing newline suffix.
175175
fn end_of_statement_insertion(stmt: &Stmt, locator: &Locator, stylist: &Stylist) -> Insertion {
176176
let location = stmt.end_location.unwrap();
177-
let mut tokens = lexer::lex_located(locator.skip(location), Mode::Module, location).flatten();
177+
let mut tokens = lexer::lex_located(locator.after(location), Mode::Module, location).flatten();
178178
if let Some((.., Tok::Semi, end)) = tokens.next() {
179179
// If the first token after the docstring is a semicolon, insert after the semicolon as an
180180
// inline statement;
@@ -207,7 +207,7 @@ fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) ->
207207
let mut location = if let Some(location) = match_docstring_end(body) {
208208
// If the first token after the docstring is a semicolon, insert after the semicolon as an
209209
// inline statement;
210-
let first_token = lexer::lex_located(locator.skip(location), Mode::Module, location)
210+
let first_token = lexer::lex_located(locator.after(location), Mode::Module, location)
211211
.flatten()
212212
.next();
213213
if let Some((.., Tok::Semi, end)) = first_token {
@@ -222,7 +222,7 @@ fn top_of_file_insertion(body: &[Stmt], locator: &Locator, stylist: &Stylist) ->
222222

223223
// Skip over any comments and empty lines.
224224
for (.., tok, end) in
225-
lexer::lex_located(locator.skip(location), Mode::Module, location).flatten()
225+
lexer::lex_located(locator.after(location), Mode::Module, location).flatten()
226226
{
227227
if matches!(tok, Tok::Comment(..) | Tok::Newline) {
228228
location = Location::new(end.row() + 1, 0);

crates/ruff/src/linter.rs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::checkers::physical_lines::check_physical_lines;
2222
use crate::checkers::tokens::check_tokens;
2323
use crate::directives::Directives;
2424
use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens};
25-
use crate::message::{Message, Source};
25+
use crate::message::Message;
2626
use crate::noqa::add_noqa;
2727
use crate::registry::{AsRule, Rule};
2828
use crate::rules::pycodestyle;
@@ -355,19 +355,26 @@ pub fn lint_only(
355355

356356
// Convert from diagnostics to messages.
357357
let path_lossy = path.to_string_lossy();
358+
358359
result.map(|(messages, imports)| {
360+
let source_code = if settings.show_source && !messages.is_empty() {
361+
Some(locator.to_source_code_buf())
362+
} else {
363+
None
364+
};
365+
359366
(
360367
messages
361368
.into_iter()
362369
.map(|diagnostic| {
363-
let source = if settings.show_source {
364-
Some(Source::from_diagnostic(&diagnostic, &locator))
365-
} else {
366-
None
367-
};
368370
let lineno = diagnostic.location.row();
369371
let noqa_row = *directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
370-
Message::from_diagnostic(diagnostic, path_lossy.to_string(), source, noqa_row)
372+
Message::from_diagnostic(
373+
diagnostic,
374+
path_lossy.to_string(),
375+
source_code.clone(),
376+
noqa_row,
377+
)
371378
})
372379
.collect(),
373380
imports,
@@ -500,22 +507,23 @@ This indicates a bug in `{}`. If you could open an issue at:
500507
let path_lossy = path.to_string_lossy();
501508
return Ok(FixerResult {
502509
result: result.map(|(messages, imports)| {
510+
let source_code = if settings.show_source && !messages.is_empty() {
511+
Some(locator.to_source_code_buf())
512+
} else {
513+
None
514+
};
515+
503516
(
504517
messages
505518
.into_iter()
506519
.map(|diagnostic| {
507-
let source = if settings.show_source {
508-
Some(Source::from_diagnostic(&diagnostic, &locator))
509-
} else {
510-
None
511-
};
512520
let lineno = diagnostic.location.row();
513521
let noqa_row =
514522
*directives.noqa_line_for.get(&lineno).unwrap_or(&lineno);
515523
Message::from_diagnostic(
516524
diagnostic,
517525
path_lossy.to_string(),
518-
source,
526+
source_code.clone(),
519527
noqa_row,
520528
)
521529
})

crates/ruff/src/message/grouped.rs

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
use crate::fs::relativize_path;
22
use crate::jupyter::JupyterIndex;
3-
use crate::message::text::RuleCodeAndBody;
3+
use crate::message::text::{MessageCodeFrame, RuleCodeAndBody};
44
use crate::message::{group_messages_by_filename, Emitter, EmitterContext, Message};
5-
use crate::registry::AsRule;
6-
use annotate_snippets::display_list::{DisplayList, FormatOptions};
7-
use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation};
85
use colored::Colorize;
9-
use std::fmt::{Display, Formatter, Write as fmtWrite};
6+
use std::fmt::{Display, Formatter};
107
use std::io::Write;
118

129
#[derive(Default)]
@@ -116,47 +113,10 @@ impl Display for DisplayGroupedMessage<'_> {
116113
},
117114
)?;
118115

119-
if let Some(source) = &message.source {
120-
let suggestion = message.kind.suggestion.clone();
121-
let footer = if suggestion.is_some() {
122-
vec![Annotation {
123-
id: None,
124-
label: suggestion.as_deref(),
125-
annotation_type: AnnotationType::Help,
126-
}]
127-
} else {
128-
vec![]
129-
};
130-
let label = message.kind.rule().noqa_code().to_string();
131-
let snippet = Snippet {
132-
title: None,
133-
footer,
134-
slices: vec![Slice {
135-
source: &source.contents,
136-
line_start: message.location.row(),
137-
annotations: vec![SourceAnnotation {
138-
label: &label,
139-
annotation_type: AnnotationType::Error,
140-
range: source.range,
141-
}],
142-
// The origin (file name, line number, and column number) is already encoded
143-
// in the `label`.
144-
origin: None,
145-
fold: false,
146-
}],
147-
opt: FormatOptions {
148-
#[cfg(test)]
149-
color: false,
150-
#[cfg(not(test))]
151-
color: colored::control::SHOULD_COLORIZE.should_colorize(),
152-
..FormatOptions::default()
153-
},
154-
};
155-
// Skip the first line, since we format the `label` ourselves.
156-
let message = DisplayList::from(snippet);
116+
{
117+
use std::fmt::Write;
157118
let mut padded = PadAdapter::new(f);
158-
159-
writeln!(&mut padded, "{message}")?;
119+
write!(padded, "{}", MessageCodeFrame { message })?;
160120
}
161121

162122
writeln!(f)?;

crates/ruff/src/message/mod.rs

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,30 +20,28 @@ pub use json::JsonEmitter;
2020
pub use junit::JunitEmitter;
2121
pub use pylint::PylintEmitter;
2222
pub use rustpython_parser::ast::Location;
23-
use serde::{Deserialize, Serialize};
2423
pub use text::TextEmitter;
2524

2625
use crate::jupyter::JupyterIndex;
2726
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Fix};
28-
use ruff_python_ast::source_code::Locator;
29-
use ruff_python_ast::types::Range;
27+
use ruff_python_ast::source_code::SourceCodeBuf;
3028

31-
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
29+
#[derive(Debug, PartialEq, Eq)]
3230
pub struct Message {
3331
pub kind: DiagnosticKind,
3432
pub location: Location,
3533
pub end_location: Location,
3634
pub fix: Fix,
3735
pub filename: String,
38-
pub source: Option<Source>,
36+
pub source: Option<SourceCodeBuf>,
3937
pub noqa_row: usize,
4038
}
4139

4240
impl Message {
4341
pub fn from_diagnostic(
4442
diagnostic: Diagnostic,
4543
filename: String,
46-
source: Option<Source>,
44+
source: Option<SourceCodeBuf>,
4745
noqa_row: usize,
4846
) -> Self {
4947
Self {
@@ -77,38 +75,6 @@ impl PartialOrd for Message {
7775
}
7876
}
7977

80-
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
81-
pub struct Source {
82-
pub contents: String,
83-
pub range: (usize, usize),
84-
}
85-
86-
impl Source {
87-
pub fn from_diagnostic(diagnostic: &Diagnostic, locator: &Locator) -> Self {
88-
let location = Location::new(diagnostic.location.row(), 0);
89-
// Diagnostics can already extend one-past-the-end. If they do, though, then
90-
// they'll end at the start of a line. We need to avoid extending by yet another
91-
// line past-the-end.
92-
let end_location = if diagnostic.end_location.column() == 0 {
93-
diagnostic.end_location
94-
} else {
95-
Location::new(diagnostic.end_location.row() + 1, 0)
96-
};
97-
let source = locator.slice(Range::new(location, end_location));
98-
let num_chars_in_range = locator
99-
.slice(Range::new(diagnostic.location, diagnostic.end_location))
100-
.chars()
101-
.count();
102-
Source {
103-
contents: source.to_string(),
104-
range: (
105-
diagnostic.location.column(),
106-
diagnostic.location.column() + num_chars_in_range,
107-
),
108-
}
109-
}
110-
}
111-
11278
fn group_messages_by_filename(messages: &[Message]) -> BTreeMap<&str, Vec<&Message>> {
11379
let mut grouped_messages = BTreeMap::default();
11480
for message in messages {
@@ -156,15 +122,15 @@ impl<'a> EmitterContext<'a> {
156122

157123
#[cfg(test)]
158124
mod tests {
159-
use crate::message::{Emitter, EmitterContext, Location, Message, Source};
125+
use crate::message::{Emitter, EmitterContext, Location, Message};
160126
use crate::rules::pyflakes::rules::{UndefinedName, UnusedImport, UnusedVariable};
161127
use ruff_diagnostics::{Diagnostic, Edit, Fix};
162-
use ruff_python_ast::source_code::Locator;
128+
use ruff_python_ast::source_code::SourceCodeBuf;
163129
use ruff_python_ast::types::Range;
164130
use rustc_hash::FxHashMap;
165131

166132
pub(super) fn create_messages() -> Vec<Message> {
167-
let file_1 = r#"import os
133+
let fib = r#"import os
168134
169135
def fibonacci(n):
170136
"""Compute the nth number in the Fibonacci sequence."""
@@ -177,8 +143,6 @@ def fibonacci(n):
177143
return fibonacci(n - 1) + fibonacci(n - 2)
178144
"#;
179145

180-
let file_1_locator = Locator::new(file_1);
181-
182146
let unused_import = Diagnostic::new(
183147
UnusedImport {
184148
name: "os".to_string(),
@@ -188,7 +152,7 @@ def fibonacci(n):
188152
Range::new(Location::new(1, 7), Location::new(1, 9)),
189153
);
190154

191-
let unused_source = Source::from_diagnostic(&unused_import, &file_1_locator);
155+
let fib_source = SourceCodeBuf::from_content(fib);
192156

193157
let unused_variable = Diagnostic::new(
194158
UnusedVariable {
@@ -201,8 +165,6 @@ def fibonacci(n):
201165
Location::new(5, 9),
202166
)]));
203167

204-
let unused_variable_source = Source::from_diagnostic(&unused_variable, &file_1_locator);
205-
206168
let file_2 = r#"if a == 1: pass"#;
207169

208170
let undefined_name = Diagnostic::new(
@@ -212,20 +174,20 @@ def fibonacci(n):
212174
Range::new(Location::new(1, 3), Location::new(1, 4)),
213175
);
214176

215-
let undefined_source = Source::from_diagnostic(&undefined_name, &Locator::new(file_2));
177+
let file_2_source = SourceCodeBuf::from_content(file_2);
216178

217179
vec![
218-
Message::from_diagnostic(unused_import, "fib.py".to_string(), Some(unused_source), 1),
219180
Message::from_diagnostic(
220-
unused_variable,
181+
unused_import,
221182
"fib.py".to_string(),
222-
Some(unused_variable_source),
183+
Some(fib_source.clone()),
223184
1,
224185
),
186+
Message::from_diagnostic(unused_variable, "fib.py".to_string(), Some(fib_source), 1),
225187
Message::from_diagnostic(
226188
undefined_name,
227189
"undef.py".to_string(),
228-
Some(undefined_source),
190+
Some(file_2_source),
229191
1,
230192
),
231193
]

0 commit comments

Comments
 (0)