mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
Render tabs as 4 spaces in diagnostics (#4132)
This commit is contained in:
parent
ac600bb3da
commit
b14358fbfe
13 changed files with 229 additions and 176 deletions
|
@ -8,7 +8,8 @@ use bitflags::bitflags;
|
|||
use colored::Colorize;
|
||||
use ruff_diagnostics::DiagnosticKind;
|
||||
use ruff_python_ast::source_code::{OneIndexed, SourceLocation};
|
||||
use ruff_text_size::TextRange;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::io::Write;
|
||||
|
||||
|
@ -172,6 +173,7 @@ impl Display for MessageCodeFrame<'_> {
|
|||
};
|
||||
|
||||
let source_code = file.to_source_code();
|
||||
|
||||
let content_start_index = source_code.line_index(range.start());
|
||||
let mut start_index = content_start_index.saturating_sub(2);
|
||||
|
||||
|
@ -200,26 +202,23 @@ impl Display for MessageCodeFrame<'_> {
|
|||
let start_offset = source_code.line_start(start_index);
|
||||
let end_offset = source_code.line_end(end_index);
|
||||
|
||||
let source_text = source_code.slice(TextRange::new(start_offset, end_offset));
|
||||
let source = replace_whitespace(
|
||||
source_code.slice(TextRange::new(start_offset, end_offset)),
|
||||
range - start_offset,
|
||||
);
|
||||
|
||||
let annotation_start_offset = range.start() - start_offset;
|
||||
let annotation_end_offset = range.end() - start_offset;
|
||||
|
||||
let start_char = source_text[TextRange::up_to(annotation_start_offset)]
|
||||
let start_char = source.text[TextRange::up_to(source.annotation_range.start())]
|
||||
.chars()
|
||||
.count();
|
||||
|
||||
let char_length = source_text
|
||||
[TextRange::new(annotation_start_offset, annotation_end_offset)]
|
||||
.chars()
|
||||
.count();
|
||||
let char_length = source.text[source.annotation_range].chars().count();
|
||||
|
||||
let label = kind.rule().noqa_code().to_string();
|
||||
|
||||
let snippet = Snippet {
|
||||
title: None,
|
||||
slices: vec![Slice {
|
||||
source: source_text,
|
||||
source: &source.text,
|
||||
line_start: content_start_index.get(),
|
||||
annotations: vec![SourceAnnotation {
|
||||
label: &label,
|
||||
|
@ -245,6 +244,60 @@ impl Display for MessageCodeFrame<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
static TAB_SIZE: TextSize = TextSize::new(4);
|
||||
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
let mut column = 0;
|
||||
|
||||
for (index, m) in source.match_indices(['\t', '\n', '\r']) {
|
||||
match m {
|
||||
"\t" => {
|
||||
let tab_width = TAB_SIZE - TextSize::new(column % 4);
|
||||
|
||||
if index < usize::from(annotation_range.start()) {
|
||||
range += tab_width - TextSize::new(1);
|
||||
} else if index < usize::from(annotation_range.end()) {
|
||||
range = range.add_end(tab_width - TextSize::new(1));
|
||||
}
|
||||
|
||||
result.push_str(&source[last_end..index]);
|
||||
|
||||
for _ in 0..u32::from(tab_width) {
|
||||
result.push(' ');
|
||||
}
|
||||
|
||||
last_end = index + 1;
|
||||
}
|
||||
"\n" | "\r" => {
|
||||
column = 0;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// No tabs
|
||||
if result.is_empty() {
|
||||
SourceCode {
|
||||
annotation_range,
|
||||
text: Cow::Borrowed(source),
|
||||
}
|
||||
} else {
|
||||
result.push_str(&source[last_end..]);
|
||||
SourceCode {
|
||||
annotation_range: range,
|
||||
text: Cow::Owned(result),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SourceCode<'a> {
|
||||
text: Cow<'a, str>,
|
||||
annotation_range: TextRange,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::message::tests::{capture_emitter_output, create_messages};
|
||||
|
|
|
@ -6,7 +6,7 @@ E101.py:11:1: E101 Indentation contains mixed spaces and tabs
|
|||
11 | def func_mixed_start_with_tab():
|
||||
12 | # E101
|
||||
13 | print("mixed starts with tab")
|
||||
| ^^ E101
|
||||
| ^^^^^^ E101
|
||||
14 |
|
||||
15 | def func_mixed_start_with_space():
|
||||
|
|
||||
|
@ -16,7 +16,7 @@ E101.py:15:1: E101 Indentation contains mixed spaces and tabs
|
|||
15 | def func_mixed_start_with_space():
|
||||
16 | # E101
|
||||
17 | print("mixed starts with space")
|
||||
| ^^^^^^^^ E101
|
||||
| ^^^^^^^^^^^^^^^^^^^^ E101
|
||||
18 |
|
||||
19 | def xyz():
|
||||
|
|
||||
|
@ -26,7 +26,7 @@ E101.py:19:1: E101 Indentation contains mixed spaces and tabs
|
|||
19 | def xyz():
|
||||
20 | # E101
|
||||
21 | print("xyz");
|
||||
| ^^^ E101
|
||||
| ^^^^^^^ E101
|
||||
|
|
||||
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ E11.py:42:1: E117 Over-indented
|
|||
42 | #: E117 W191
|
||||
43 | def start():
|
||||
44 | print()
|
||||
| E117
|
||||
| ^^^^^^^^ E117
|
||||
|
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ W19.py:3:1: W191 Indentation contains tabs
|
|||
3 | #: W191
|
||||
4 | if False:
|
||||
5 | print # indented with 1 tab
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
6 | #:
|
||||
|
|
||||
|
||||
|
@ -15,7 +15,7 @@ W19.py:9:1: W191 Indentation contains tabs
|
|||
9 | #: W191
|
||||
10 | y = x == 2 \
|
||||
11 | or x == 3
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
12 | #: E101 W191 W504
|
||||
13 | if (
|
||||
|
|
||||
|
@ -25,7 +25,7 @@ W19.py:16:1: W191 Indentation contains tabs
|
|||
16 | ) or
|
||||
17 | y == 4):
|
||||
18 | pass
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
19 | #: E101 W191
|
||||
20 | if x == 2 \
|
||||
|
|
||||
|
@ -35,7 +35,7 @@ W19.py:21:1: W191 Indentation contains tabs
|
|||
21 | or y > 1 \
|
||||
22 | or x == 3:
|
||||
23 | pass
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
24 | #: E101 W191
|
||||
25 | if x == 2 \
|
||||
|
|
||||
|
@ -45,7 +45,7 @@ W19.py:26:1: W191 Indentation contains tabs
|
|||
26 | or y > 1 \
|
||||
27 | or x == 3:
|
||||
28 | pass
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
29 | #:
|
||||
|
|
||||
|
||||
|
@ -54,7 +54,7 @@ W19.py:32:1: W191 Indentation contains tabs
|
|||
32 | if (foo == bar and
|
||||
33 | baz == bop):
|
||||
34 | pass
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
35 | #: E101 W191 W504
|
||||
36 | if (
|
||||
|
|
||||
|
@ -64,7 +64,7 @@ W19.py:38:1: W191 Indentation contains tabs
|
|||
38 | baz == bop
|
||||
39 | ):
|
||||
40 | pass
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
41 | #:
|
||||
|
|
||||
|
||||
|
@ -73,7 +73,7 @@ W19.py:44:1: W191 Indentation contains tabs
|
|||
44 | if start[1] > end_col and not (
|
||||
45 | over_indent == 4 and indent_next):
|
||||
46 | return (0, "E121 continuation line over-"
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
47 | "indented for visual indent")
|
||||
48 | #:
|
||||
|
|
||||
|
@ -83,7 +83,7 @@ W19.py:45:1: W191 Indentation contains tabs
|
|||
45 | over_indent == 4 and indent_next):
|
||||
46 | return (0, "E121 continuation line over-"
|
||||
47 | "indented for visual indent")
|
||||
| ^^^^^^^^ W191
|
||||
| ^^^^^^^^^^^^ W191
|
||||
48 | #:
|
||||
|
|
||||
|
||||
|
@ -92,7 +92,7 @@ W19.py:54:1: W191 Indentation contains tabs
|
|||
54 | var_one, var_two, var_three,
|
||||
55 | var_four):
|
||||
56 | print(var_one)
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
57 | #: E101 W191 W504
|
||||
58 | if ((row < 0 or self.moduleCount <= row or
|
||||
|
|
||||
|
@ -102,7 +102,7 @@ W19.py:58:1: W191 Indentation contains tabs
|
|||
58 | if ((row < 0 or self.moduleCount <= row or
|
||||
59 | col < 0 or self.moduleCount <= col)):
|
||||
60 | raise Exception("%s,%s - %s" % (row, col, self.moduleCount))
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
61 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
62 | if bar:
|
||||
|
|
||||
|
@ -112,7 +112,7 @@ W19.py:61:1: W191 Indentation contains tabs
|
|||
61 | #: E101 E101 E101 E101 W191 W191 W191 W191 W191 W191
|
||||
62 | if bar:
|
||||
63 | return (
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
64 | start, 'E121 lines starting with a '
|
||||
65 | 'closing bracket should be indented '
|
||||
|
|
||||
|
@ -122,7 +122,7 @@ W19.py:62:1: W191 Indentation contains tabs
|
|||
62 | if bar:
|
||||
63 | return (
|
||||
64 | start, 'E121 lines starting with a '
|
||||
| ^^^^ W191
|
||||
| ^^^^^^^^ W191
|
||||
65 | 'closing bracket should be indented '
|
||||
66 | "to match that of the opening "
|
||||
|
|
||||
|
@ -132,7 +132,7 @@ W19.py:63:1: W191 Indentation contains tabs
|
|||
63 | return (
|
||||
64 | start, 'E121 lines starting with a '
|
||||
65 | 'closing bracket should be indented '
|
||||
| ^^^^ W191
|
||||
| ^^^^^^^^ W191
|
||||
66 | "to match that of the opening "
|
||||
67 | "bracket's line"
|
||||
|
|
||||
|
@ -142,7 +142,7 @@ W19.py:64:1: W191 Indentation contains tabs
|
|||
64 | start, 'E121 lines starting with a '
|
||||
65 | 'closing bracket should be indented '
|
||||
66 | "to match that of the opening "
|
||||
| ^^^^ W191
|
||||
| ^^^^^^^^ W191
|
||||
67 | "bracket's line"
|
||||
68 | )
|
||||
|
|
||||
|
@ -152,7 +152,7 @@ W19.py:65:1: W191 Indentation contains tabs
|
|||
65 | 'closing bracket should be indented '
|
||||
66 | "to match that of the opening "
|
||||
67 | "bracket's line"
|
||||
| ^^^^ W191
|
||||
| ^^^^^^^^ W191
|
||||
68 | )
|
||||
69 | #
|
||||
|
|
||||
|
@ -162,7 +162,7 @@ W19.py:66:1: W191 Indentation contains tabs
|
|||
66 | "to match that of the opening "
|
||||
67 | "bracket's line"
|
||||
68 | )
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
69 | #
|
||||
70 | #: E101 W191 W504
|
||||
|
|
||||
|
@ -172,7 +172,7 @@ W19.py:73:1: W191 Indentation contains tabs
|
|||
73 | foo.bar("bop")
|
||||
74 | )):
|
||||
75 | print "yes"
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
76 | #: E101 W191 W504
|
||||
77 | # also ok, but starting to look like LISP
|
||||
|
|
||||
|
@ -182,7 +182,7 @@ W19.py:78:1: W191 Indentation contains tabs
|
|||
78 | if ((foo.bar("baz") and
|
||||
79 | foo.bar("bop"))):
|
||||
80 | print "yes"
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
81 | #: E101 W191 W504
|
||||
82 | if (a == 2 or
|
||||
|
|
||||
|
@ -192,7 +192,7 @@ W19.py:83:1: W191 Indentation contains tabs
|
|||
83 | b == "abc def ghi"
|
||||
84 | "jkl mno"):
|
||||
85 | return True
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
86 | #: E101 W191 W504
|
||||
87 | if (a == 2 or
|
||||
|
|
||||
|
@ -202,7 +202,7 @@ W19.py:88:1: W191 Indentation contains tabs
|
|||
88 | b == """abc def ghi
|
||||
89 | jkl mno"""):
|
||||
90 | return True
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
91 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
92 | if length > options.max_line_length:
|
||||
|
|
||||
|
@ -212,7 +212,7 @@ W19.py:91:1: W191 Indentation contains tabs
|
|||
91 | #: W191:2:1 W191:3:1 E101:3:2
|
||||
92 | if length > options.max_line_length:
|
||||
93 | return options.max_line_length, \
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
94 | "E501 line too long (%d characters)" % length
|
||||
|
|
||||
|
||||
|
@ -221,7 +221,7 @@ W19.py:92:1: W191 Indentation contains tabs
|
|||
92 | if length > options.max_line_length:
|
||||
93 | return options.max_line_length, \
|
||||
94 | "E501 line too long (%d characters)" % length
|
||||
| ^^^^ W191
|
||||
| ^^^^^^^^ W191
|
||||
|
|
||||
|
||||
W19.py:98:1: W191 Indentation contains tabs
|
||||
|
@ -229,7 +229,7 @@ W19.py:98:1: W191 Indentation contains tabs
|
|||
98 | #: E101 W191 W191 W504
|
||||
99 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
100 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
101 | self._pep8_options(targetfile))
|
||||
102 | #: W191 - okay
|
||||
|
|
||||
|
@ -239,7 +239,7 @@ W19.py:99:1: W191 Indentation contains tabs
|
|||
99 | if os.path.exists(os.path.join(path, PEP8_BIN)):
|
||||
100 | cmd = ([os.path.join(path, PEP8_BIN)] +
|
||||
101 | self._pep8_options(targetfile))
|
||||
| ^^^^^^^ W191
|
||||
| ^^^^^^^^^^^ W191
|
||||
102 | #: W191 - okay
|
||||
103 | '''
|
||||
|
|
||||
|
@ -249,7 +249,7 @@ W19.py:125:1: W191 Indentation contains tabs
|
|||
125 | if foo is None and bar is "bop" and \
|
||||
126 | blah == 'yeah':
|
||||
127 | blah = 'yeahnah'
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
|
|
||||
|
||||
W19.py:131:1: W191 Indentation contains tabs
|
||||
|
@ -257,7 +257,7 @@ W19.py:131:1: W191 Indentation contains tabs
|
|||
131 | #: W191 W191 W191
|
||||
132 | if True:
|
||||
133 | foo(
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
134 | 1,
|
||||
135 | 2)
|
||||
|
|
||||
|
@ -267,7 +267,7 @@ W19.py:132:1: W191 Indentation contains tabs
|
|||
132 | if True:
|
||||
133 | foo(
|
||||
134 | 1,
|
||||
| W191
|
||||
| ^^^^^^^^ W191
|
||||
135 | 2)
|
||||
136 | #: W191 W191 W191 W191 W191
|
||||
|
|
||||
|
@ -277,7 +277,7 @@ W19.py:133:1: W191 Indentation contains tabs
|
|||
133 | foo(
|
||||
134 | 1,
|
||||
135 | 2)
|
||||
| W191
|
||||
| ^^^^^^^^ W191
|
||||
136 | #: W191 W191 W191 W191 W191
|
||||
137 | def test_keys(self):
|
||||
|
|
||||
|
@ -287,7 +287,7 @@ W19.py:136:1: W191 Indentation contains tabs
|
|||
136 | #: W191 W191 W191 W191 W191
|
||||
137 | def test_keys(self):
|
||||
138 | """areas.json - All regions are accounted for."""
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
139 | expected = set([
|
||||
140 | u'Norrbotten',
|
||||
|
|
||||
|
@ -297,7 +297,7 @@ W19.py:137:1: W191 Indentation contains tabs
|
|||
137 | def test_keys(self):
|
||||
138 | """areas.json - All regions are accounted for."""
|
||||
139 | expected = set([
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
140 | u'Norrbotten',
|
||||
141 | u'V\xe4sterbotten',
|
||||
|
|
||||
|
@ -307,7 +307,7 @@ W19.py:138:1: W191 Indentation contains tabs
|
|||
138 | """areas.json - All regions are accounted for."""
|
||||
139 | expected = set([
|
||||
140 | u'Norrbotten',
|
||||
| W191
|
||||
| ^^^^^^^^ W191
|
||||
141 | u'V\xe4sterbotten',
|
||||
142 | ])
|
||||
|
|
||||
|
@ -317,7 +317,7 @@ W19.py:139:1: W191 Indentation contains tabs
|
|||
139 | expected = set([
|
||||
140 | u'Norrbotten',
|
||||
141 | u'V\xe4sterbotten',
|
||||
| W191
|
||||
| ^^^^^^^^ W191
|
||||
142 | ])
|
||||
143 | #: W191
|
||||
|
|
||||
|
@ -327,7 +327,7 @@ W19.py:140:1: W191 Indentation contains tabs
|
|||
140 | u'Norrbotten',
|
||||
141 | u'V\xe4sterbotten',
|
||||
142 | ])
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
143 | #: W191
|
||||
144 | x = [
|
||||
|
|
||||
|
@ -337,7 +337,7 @@ W19.py:143:1: W191 Indentation contains tabs
|
|||
143 | #: W191
|
||||
144 | x = [
|
||||
145 | 'abc'
|
||||
| W191
|
||||
| ^^^^ W191
|
||||
146 | ]
|
||||
147 | #: W191 - okay
|
||||
|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue