Skip to content

Commit e7b4969

Browse files
dhruvmanilaMichaReiser
authored andcommitted
Remove E999 as a rule, disallow any disablement methods for syntax error (#11901)
## Summary This PR updates the way syntax errors are handled throughout the linter. The main change is that it's now not considered as a rule which involves the following changes: * Update `Message` to be an enum with two variants - one for diagnostic message and the other for syntax error message * Provide methods on the new message enum to query information required by downstream usages This means that the syntax errors cannot be hidden / disabled via any disablement methods. These are: 1. Configuration via `select`, `ignore`, `per-file-ignores`, and their `extend-*` variants ```console $ cargo run -- check ~/playground/ruff/src/lsp.py --extend-select=E999 --no-preview --no-cache Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s Running `target/debug/ruff check /Users/dhruv/playground/ruff/src/lsp.py --extend-select=E999 --no-preview --no-cache` warning: Rule `E999` is deprecated and will be removed in a future release. Syntax errors will always be shown regardless of whether this rule is selected or not. /Users/dhruv/playground/ruff/src/lsp.py:1:8: F401 [*] `abc` imported but unused | 1 | import abc | ^^^ F401 2 | from pathlib import Path 3 | import os | = help: Remove unused import: `abc` ``` 3. Command-line flags via `--select`, `--ignore`, `--per-file-ignores`, and their `--extend-*` variants ```console $ cargo run -- check ~/playground/ruff/src/lsp.py --no-cache --config=~/playground/ruff/pyproject.toml Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s Running `target/debug/ruff check /Users/dhruv/playground/ruff/src/lsp.py --no-cache --config=/Users/dhruv/playground/ruff/pyproject.toml` warning: Rule `E999` is deprecated and will be removed in a future release. Syntax errors will always be shown regardless of whether this rule is selected or not. /Users/dhruv/playground/ruff/src/lsp.py:1:8: F401 [*] `abc` imported but unused | 1 | import abc | ^^^ F401 2 | from pathlib import Path 3 | import os | = help: Remove unused import: `abc` ``` This also means that the **output format** needs to be updated: 1. The `code`, `noqa_row`, `url` fields in the JSON output is optional (`null` for syntax errors) 2. Other formats are changed accordingly For each format, a new test case specific to syntax errors have been added. Please refer to the snapshot output for the exact format for syntax error message. The output of the `--statistics` flag will have a blank entry for syntax errors: ``` 315 F821 [ ] undefined-name 119 [ ] syntax-error 103 F811 [ ] redefined-while-unused ``` The **language server** is updated to consider the syntax errors by convert them into LSP diagnostic format separately. ### Preview There are no quick fixes provided to disable syntax errors. This will automatically work for `ruff-lsp` because the `noqa_row` field will be `null` in that case. <img width="772" alt="Screenshot 2024-06-26 at 14 57 08" src="https://github.com/astral-sh/ruff/assets/67177269/aaac827e-4777-4ac8-8c68-eaf9f2c36774"> Even with `noqa` comment, the syntax error is displayed: <img width="763" alt="Screenshot 2024-06-26 at 14 59 51" src="https://github.com/astral-sh/ruff/assets/67177269/ba1afb68-7eaf-4b44-91af-6d93246475e2"> Rule documentation page: <img width="1371" alt="Screenshot 2024-06-26 at 16 48 07" src="https://github.com/astral-sh/ruff/assets/67177269/524f01df-d91f-4ac0-86cc-40e76b318b24"> ## Test Plan - [x] Disablement methods via config shows a warning - [x] `select`, `extend-select` - [ ] ~`ignore`~ _doesn't show any message_ - [ ] ~`per-file-ignores`, `extend-per-file-ignores`~ _doesn't show any message_ - [x] Disablement methods via command-line flag shows a warning - [x] `--select`, `--extend-select` - [ ] ~`--ignore`~ _doesn't show any message_ - [ ] ~`--per-file-ignores`, `--extend-per-file-ignores`~ _doesn't show any message_ - [x] File with syntax errors should exit with code 1 - [x] Language server - [x] Should show diagnostics for syntax errors - [x] Should not recommend a quick fix edit for adding `noqa` comment - [x] Same for `ruff-lsp` resolves: #8447
1 parent c98d8a0 commit e7b4969

File tree

52 files changed

+1235
-380
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1235
-380
lines changed

crates/ruff/src/cache.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use tempfile::NamedTempFile;
1919

2020
use ruff_cache::{CacheKey, CacheKeyHasher};
2121
use ruff_diagnostics::{DiagnosticKind, Fix};
22-
use ruff_linter::message::Message;
22+
use ruff_linter::message::{DiagnosticMessage, Message};
2323
use ruff_linter::{warn_user, VERSION};
2424
use ruff_macros::CacheKey;
2525
use ruff_notebook::NotebookIndex;
@@ -333,12 +333,14 @@ impl FileCache {
333333
let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish();
334334
lint.messages
335335
.iter()
336-
.map(|msg| Message {
337-
kind: msg.kind.clone(),
338-
range: msg.range,
339-
fix: msg.fix.clone(),
340-
file: file.clone(),
341-
noqa_offset: msg.noqa_offset,
336+
.map(|msg| {
337+
Message::Diagnostic(DiagnosticMessage {
338+
kind: msg.kind.clone(),
339+
range: msg.range,
340+
fix: msg.fix.clone(),
341+
file: file.clone(),
342+
noqa_offset: msg.noqa_offset,
343+
})
342344
})
343345
.collect()
344346
};
@@ -412,18 +414,19 @@ impl LintCacheData {
412414
notebook_index: Option<NotebookIndex>,
413415
) -> Self {
414416
let source = if let Some(msg) = messages.first() {
415-
msg.file.source_text().to_owned()
417+
msg.source_file().source_text().to_owned()
416418
} else {
417419
String::new() // No messages, no need to keep the source!
418420
};
419421

420422
let messages = messages
421423
.iter()
424+
.filter_map(|message| message.as_diagnostic_message())
422425
.map(|msg| {
423426
// Make sure that all message use the same source file.
424427
assert_eq!(
425-
msg.file,
426-
messages.first().unwrap().file,
428+
&msg.file,
429+
messages.first().unwrap().source_file(),
427430
"message uses a different source file"
428431
);
429432
CacheMessage {
@@ -571,6 +574,7 @@ mod tests {
571574
use test_case::test_case;
572575

573576
use ruff_cache::CACHE_DIR_NAME;
577+
use ruff_linter::message::Message;
574578
use ruff_linter::settings::flags;
575579
use ruff_linter::settings::types::UnsafeFixes;
576580
use ruff_python_ast::PySourceType;
@@ -633,11 +637,7 @@ mod tests {
633637
UnsafeFixes::Enabled,
634638
)
635639
.unwrap();
636-
if diagnostics
637-
.messages
638-
.iter()
639-
.any(|m| m.kind.name == "SyntaxError")
640-
{
640+
if diagnostics.messages.iter().any(Message::is_syntax_error) {
641641
parse_errors.push(path.clone());
642642
}
643643
paths.push(path);

crates/ruff/src/diagnostics.rs

Lines changed: 48 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ use std::path::Path;
1010
use anyhow::{Context, Result};
1111
use colored::Colorize;
1212
use log::{debug, error, warn};
13+
use ruff_linter::codes::Rule;
1314
use rustc_hash::FxHashMap;
1415

1516
use ruff_diagnostics::Diagnostic;
1617
use ruff_linter::linter::{lint_fix, lint_only, FixTable, FixerResult, LinterResult, ParseSource};
1718
use ruff_linter::logging::DisplayParseError;
18-
use ruff_linter::message::Message;
19+
use ruff_linter::message::{Message, SyntaxErrorMessage};
1920
use ruff_linter::pyproject_toml::lint_pyproject_toml;
20-
use ruff_linter::registry::AsRule;
2121
use ruff_linter::settings::types::UnsafeFixes;
2222
use ruff_linter::settings::{flags, LinterSettings};
2323
use ruff_linter::source_kind::{SourceError, SourceKind};
24-
use ruff_linter::{fs, IOError, SyntaxError};
24+
use ruff_linter::{fs, IOError};
2525
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
2626
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
2727
use ruff_source_file::SourceFileBuilder;
@@ -55,57 +55,61 @@ impl Diagnostics {
5555
path: Option<&Path>,
5656
settings: &LinterSettings,
5757
) -> Self {
58-
let diagnostic = match err {
58+
match err {
5959
// IO errors.
6060
SourceError::Io(_)
6161
| SourceError::Notebook(NotebookError::Io(_) | NotebookError::Json(_)) => {
62-
Diagnostic::new(
63-
IOError {
64-
message: err.to_string(),
65-
},
66-
TextRange::default(),
67-
)
62+
if settings.rules.enabled(Rule::IOError) {
63+
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
64+
let source_file = SourceFileBuilder::new(name, "").finish();
65+
Self::new(
66+
vec![Message::from_diagnostic(
67+
Diagnostic::new(
68+
IOError {
69+
message: err.to_string(),
70+
},
71+
TextRange::default(),
72+
),
73+
source_file,
74+
TextSize::default(),
75+
)],
76+
FxHashMap::default(),
77+
)
78+
} else {
79+
match path {
80+
Some(path) => {
81+
warn!(
82+
"{}{}{} {err}",
83+
"Failed to lint ".bold(),
84+
fs::relativize_path(path).bold(),
85+
":".bold()
86+
);
87+
}
88+
None => {
89+
warn!("{}{} {err}", "Failed to lint".bold(), ":".bold());
90+
}
91+
}
92+
93+
Self::default()
94+
}
6895
}
6996
// Syntax errors.
7097
SourceError::Notebook(
7198
NotebookError::InvalidJson(_)
7299
| NotebookError::InvalidSchema(_)
73100
| NotebookError::InvalidFormat(_),
74-
) => Diagnostic::new(
75-
SyntaxError {
76-
message: err.to_string(),
77-
},
78-
TextRange::default(),
79-
),
80-
};
81-
82-
if settings.rules.enabled(diagnostic.kind.rule()) {
83-
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
84-
let dummy = SourceFileBuilder::new(name, "").finish();
85-
Self::new(
86-
vec![Message::from_diagnostic(
87-
diagnostic,
88-
dummy,
89-
TextSize::default(),
90-
)],
91-
FxHashMap::default(),
92-
)
93-
} else {
94-
match path {
95-
Some(path) => {
96-
warn!(
97-
"{}{}{} {err}",
98-
"Failed to lint ".bold(),
99-
fs::relativize_path(path).bold(),
100-
":".bold()
101-
);
102-
}
103-
None => {
104-
warn!("{}{} {err}", "Failed to lint".bold(), ":".bold());
105-
}
101+
) => {
102+
let name = path.map_or_else(|| "-".into(), Path::to_string_lossy);
103+
let dummy = SourceFileBuilder::new(name, "").finish();
104+
Self::new(
105+
vec![Message::SyntaxError(SyntaxErrorMessage {
106+
message: err.to_string(),
107+
range: TextRange::default(),
108+
file: dummy,
109+
})],
110+
FxHashMap::default(),
111+
)
106112
}
107-
108-
Self::default()
109113
}
110114
}
111115
}

crates/ruff/src/printer.rs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ use ruff_linter::fs::relativize_path;
1313
use ruff_linter::logging::LogLevel;
1414
use ruff_linter::message::{
1515
AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter,
16-
JsonEmitter, JsonLinesEmitter, JunitEmitter, PylintEmitter, RdjsonEmitter, SarifEmitter,
17-
TextEmitter,
16+
JsonEmitter, JsonLinesEmitter, JunitEmitter, Message, MessageKind, PylintEmitter,
17+
RdjsonEmitter, SarifEmitter, TextEmitter,
1818
};
1919
use ruff_linter::notify_user;
20-
use ruff_linter::registry::{AsRule, Rule};
20+
use ruff_linter::registry::Rule;
2121
use ruff_linter::settings::flags::{self};
2222
use ruff_linter::settings::types::{OutputFormat, UnsafeFixes};
2323

@@ -37,12 +37,13 @@ bitflags! {
3737

3838
#[derive(Serialize)]
3939
struct ExpandedStatistics {
40-
code: SerializeRuleAsCode,
41-
name: SerializeRuleAsTitle,
40+
code: Option<SerializeRuleAsCode>,
41+
name: SerializeMessageKindAsTitle,
4242
count: usize,
4343
fixable: bool,
4444
}
4545

46+
#[derive(Copy, Clone)]
4647
struct SerializeRuleAsCode(Rule);
4748

4849
impl Serialize for SerializeRuleAsCode {
@@ -66,26 +67,26 @@ impl From<Rule> for SerializeRuleAsCode {
6667
}
6768
}
6869

69-
struct SerializeRuleAsTitle(Rule);
70+
struct SerializeMessageKindAsTitle(MessageKind);
7071

71-
impl Serialize for SerializeRuleAsTitle {
72+
impl Serialize for SerializeMessageKindAsTitle {
7273
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
7374
where
7475
S: serde::Serializer,
7576
{
76-
serializer.serialize_str(self.0.as_ref())
77+
serializer.serialize_str(self.0.as_str())
7778
}
7879
}
7980

80-
impl Display for SerializeRuleAsTitle {
81+
impl Display for SerializeMessageKindAsTitle {
8182
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82-
write!(f, "{}", self.0.as_ref())
83+
f.write_str(self.0.as_str())
8384
}
8485
}
8586

86-
impl From<Rule> for SerializeRuleAsTitle {
87-
fn from(rule: Rule) -> Self {
88-
Self(rule)
87+
impl From<MessageKind> for SerializeMessageKindAsTitle {
88+
fn from(kind: MessageKind) -> Self {
89+
Self(kind)
8990
}
9091
}
9192

@@ -341,24 +342,23 @@ impl Printer {
341342
let statistics: Vec<ExpandedStatistics> = diagnostics
342343
.messages
343344
.iter()
344-
.map(|message| (message.kind.rule(), message.fix.is_some()))
345-
.sorted()
346-
.fold(vec![], |mut acc, (rule, fixable)| {
347-
if let Some((prev_rule, _, count)) = acc.last_mut() {
348-
if *prev_rule == rule {
345+
.sorted_by_key(|message| (message.rule(), message.fixable()))
346+
.fold(vec![], |mut acc: Vec<(&Message, usize)>, message| {
347+
if let Some((prev_message, count)) = acc.last_mut() {
348+
if prev_message.rule() == message.rule() {
349349
*count += 1;
350350
return acc;
351351
}
352352
}
353-
acc.push((rule, fixable, 1));
353+
acc.push((message, 1));
354354
acc
355355
})
356356
.iter()
357-
.map(|(rule, fixable, count)| ExpandedStatistics {
358-
code: (*rule).into(),
359-
name: (*rule).into(),
360-
count: *count,
361-
fixable: *fixable,
357+
.map(|&(message, count)| ExpandedStatistics {
358+
code: message.rule().map(std::convert::Into::into),
359+
name: message.kind().into(),
360+
count,
361+
fixable: message.fixable(),
362362
})
363363
.sorted_by_key(|statistic| Reverse(statistic.count))
364364
.collect();
@@ -381,7 +381,12 @@ impl Printer {
381381
);
382382
let code_width = statistics
383383
.iter()
384-
.map(|statistic| statistic.code.to_string().len())
384+
.map(|statistic| {
385+
statistic
386+
.code
387+
.map_or_else(String::new, |rule| rule.to_string())
388+
.len()
389+
})
385390
.max()
386391
.unwrap();
387392
let any_fixable = statistics.iter().any(|statistic| statistic.fixable);
@@ -395,7 +400,11 @@ impl Printer {
395400
writer,
396401
"{:>count_width$}\t{:<code_width$}\t{}{}",
397402
statistic.count.to_string().bold(),
398-
statistic.code.to_string().red().bold(),
403+
statistic
404+
.code
405+
.map_or_else(String::new, |rule| rule.to_string())
406+
.red()
407+
.bold(),
399408
if any_fixable {
400409
if statistic.fixable {
401410
&fixable
@@ -545,7 +554,7 @@ impl FixableStatistics {
545554
let mut unapplicable_unsafe = 0;
546555

547556
for message in &diagnostics.messages {
548-
if let Some(fix) = &message.fix {
557+
if let Some(fix) = message.fix() {
549558
if fix.applies(unsafe_fixes.required_applicability()) {
550559
applicable += 1;
551560
} else {

0 commit comments

Comments
 (0)