From 77a5c5ac80cfe77def2397e80ed9a7d79e068b34 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 3 Jul 2025 13:01:09 -0400 Subject: [PATCH] Combine `OldDiagnostic` and `Diagnostic` (#19053) ## Summary This PR is a collaboration with @AlexWaygood from our pairing session last Friday. The main goal here is removing `ruff_linter::message::OldDiagnostic` in favor of using `ruff_db::diagnostic::Diagnostic` directly. This involved a few major steps: - Transferring the fields - Transferring the methods and trait implementations, where possible - Converting some constructor methods to free functions - Moving the `SecondaryCode` struct - Updating the method names I'm hoping that some of the methods, especially those in the `expect_ruff_*` family, won't be necessary long-term, but I avoided trying to replace them entirely for now to keep the already-large diff a bit smaller. ### Related refactors Alex and I noticed a few refactoring opportunities while looking at the code, specifically the very similar implementations for `create_parse_diagnostic`, `create_unsupported_syntax_diagnostic`, and `create_semantic_syntax_diagnostic`. We combined these into a single generic function, which I then copied into `ruff_linter::message` with some small changes and a TODO to combine them in the future. I also deleted the `DisplayParseErrorType` and `TruncateAtNewline` types for reporting parse errors. These were added in #4124, I believe to work around the error messages from LALRPOP. Removing these didn't affect any tests, so I think they were unnecessary now that we fully control the error messages from the parser. On a more minor note, I factored out some calls to the `OldDiagnostic::filename` (now `Diagnostic::expect_ruff_filename`) function to avoid repeatedly allocating `String`s in some places. ### Snapshot changes The `show_statistics_syntax_errors` integration test changed because the `OldDiagnostic::name` method used `syntax-error` instead of `invalid-syntax` like in ty. I think this (`--statistics`) is one of the only places we actually use this name for syntax errors, so I hope this is okay. An alternative is to use `syntax-error` in ty too. The other snapshot changes are from removing this code, as discussed on [Discord](https://discord.com/channels/1039017663004942429/1228460843033821285/1388252408848847069): https://github.com/astral-sh/ruff/blob/34052a1185392963c465737e9647f404abdfe8d5/crates/ruff_linter/src/message/mod.rs#L128-L135 I think both of these are technically breaking changes, but they only affect syntax errors and are very narrow in scope, while also pretty substantially simplifying the refactor, so I hope they're okay to include in a patch release. ## Test plan Existing tests, with the adjustments mentioned above --------- Co-authored-by: Alex Waygood --- Cargo.lock | 3 + crates/ruff/src/cache.rs | 25 +- crates/ruff/src/commands/check.rs | 4 +- crates/ruff/src/diagnostics.rs | 16 +- crates/ruff/src/printer.rs | 8 +- crates/ruff/tests/integration_test.rs | 6 +- crates/ruff_db/Cargo.toml | 1 + crates/ruff_db/src/diagnostic/mod.rs | 287 +++++++++-- crates/ruff_diagnostics/Cargo.toml | 1 + crates/ruff_diagnostics/src/edit.rs | 2 +- crates/ruff_diagnostics/src/fix.rs | 8 +- crates/ruff_linter/Cargo.toml | 2 +- crates/ruff_linter/src/checkers/ast/mod.rs | 43 +- crates/ruff_linter/src/checkers/noqa.rs | 4 +- crates/ruff_linter/src/codes.rs | 13 + crates/ruff_linter/src/fix/edits.rs | 7 +- crates/ruff_linter/src/fix/mod.rs | 15 +- crates/ruff_linter/src/lib.rs | 1 - crates/ruff_linter/src/linter.rs | 53 +- crates/ruff_linter/src/logging.rs | 60 +-- crates/ruff_linter/src/message/azure.rs | 11 +- crates/ruff_linter/src/message/diff.rs | 9 +- crates/ruff_linter/src/message/github.rs | 16 +- crates/ruff_linter/src/message/gitlab.rs | 21 +- crates/ruff_linter/src/message/grouped.rs | 9 +- crates/ruff_linter/src/message/json.rs | 20 +- crates/ruff_linter/src/message/json_lines.rs | 6 +- crates/ruff_linter/src/message/junit.rs | 9 +- crates/ruff_linter/src/message/mod.rs | 461 ++++-------------- crates/ruff_linter/src/message/pylint.rs | 12 +- crates/ruff_linter/src/message/rdjson.rs | 19 +- crates/ruff_linter/src/message/sarif.rs | 21 +- crates/ruff_linter/src/message/text.rs | 28 +- crates/ruff_linter/src/noqa.rs | 31 +- crates/ruff_linter/src/pyproject_toml.rs | 17 +- .../flake8_commas/rules/trailing_commas.rs | 2 +- ..._commas__tests__COM81_syntax_error.py.snap | 2 +- ...at__tests__ISC001_ISC_syntax_error.py.snap | 22 +- ...at__tests__ISC002_ISC_syntax_error.py.snap | 22 +- crates/ruff_linter/src/rules/isort/mod.rs | 23 +- .../logical_lines/extraneous_whitespace.rs | 14 +- ...ules__pycodestyle__tests__E111_E11.py.snap | 7 +- ...ules__pycodestyle__tests__E112_E11.py.snap | 6 +- ...ules__pycodestyle__tests__E113_E11.py.snap | 7 +- ...ules__pycodestyle__tests__E114_E11.py.snap | 7 +- ...ules__pycodestyle__tests__E115_E11.py.snap | 6 +- ...ules__pycodestyle__tests__E116_E11.py.snap | 7 +- ...ules__pycodestyle__tests__E117_E11.py.snap | 7 +- ...s__pycodestyle__tests__E501_E501_4.py.snap | Bin 8942 -> 8943 bytes crates/ruff_linter/src/rules/pyflakes/mod.rs | 8 +- ...10_invalid_characters_syntax_error.py.snap | 5 +- .../src/rules/ruff/rules/post_init_default.rs | 2 +- crates/ruff_linter/src/test.rs | 44 +- crates/ruff_python_parser/src/error.rs | 6 + .../ruff_python_parser/src/semantic_errors.rs | 6 + crates/ruff_server/Cargo.toml | 1 + crates/ruff_server/src/lint.rs | 6 +- crates/ruff_wasm/src/lib.rs | 4 +- crates/ty_project/src/lib.rs | 9 +- crates/ty_python_semantic/src/types.rs | 6 +- crates/ty_test/src/lib.rs | 9 +- 61 files changed, 715 insertions(+), 772 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f3250e448..1e4707c473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2854,6 +2854,7 @@ dependencies = [ "path-slash", "ruff_annotate_snippets", "ruff_cache", + "ruff_diagnostics", "ruff_notebook", "ruff_python_ast", "ruff_python_parser", @@ -2918,6 +2919,7 @@ dependencies = [ name = "ruff_diagnostics" version = "0.0.0" dependencies = [ + "get-size2", "is-macro", "ruff_text_size", "serde", @@ -3256,6 +3258,7 @@ dependencies = [ "lsp-server", "lsp-types", "regex", + "ruff_db", "ruff_diagnostics", "ruff_formatter", "ruff_linter", diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index fdaf6b4a74..20a8c9726b 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -18,14 +18,15 @@ use rustc_hash::FxHashMap; use tempfile::NamedTempFile; use ruff_cache::{CacheKey, CacheKeyHasher}; +use ruff_db::diagnostic::Diagnostic; use ruff_diagnostics::Fix; -use ruff_linter::message::OldDiagnostic; +use ruff_linter::message::create_lint_diagnostic; use ruff_linter::package::PackageRoot; use ruff_linter::{VERSION, warn_user}; use ruff_macros::CacheKey; use ruff_notebook::NotebookIndex; use ruff_source_file::SourceFileBuilder; -use ruff_text_size::{Ranged, TextRange, TextSize}; +use ruff_text_size::{TextRange, TextSize}; use ruff_workspace::Settings; use ruff_workspace::resolver::Resolver; @@ -348,7 +349,7 @@ impl FileCache { lint.messages .iter() .map(|msg| { - OldDiagnostic::lint( + create_lint_diagnostic( &msg.body, msg.suggestion.as_ref(), msg.range, @@ -428,11 +429,11 @@ pub(crate) struct LintCacheData { impl LintCacheData { pub(crate) fn from_diagnostics( - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], notebook_index: Option, ) -> Self { let source = if let Some(msg) = diagnostics.first() { - msg.source_file().source_text().to_owned() + msg.expect_ruff_source_file().source_text().to_owned() } else { String::new() // No messages, no need to keep the source! }; @@ -446,16 +447,16 @@ impl LintCacheData { .map(|(rule, msg)| { // Make sure that all message use the same source file. assert_eq!( - msg.source_file(), - diagnostics.first().unwrap().source_file(), + msg.expect_ruff_source_file(), + diagnostics.first().unwrap().expect_ruff_source_file(), "message uses a different source file" ); CacheMessage { rule, body: msg.body().to_string(), suggestion: msg.suggestion().map(ToString::to_string), - range: msg.range(), - parent: msg.parent, + range: msg.expect_range(), + parent: msg.parent(), fix: msg.fix().cloned(), noqa_offset: msg.noqa_offset(), } @@ -608,12 +609,12 @@ mod tests { use anyhow::Result; use filetime::{FileTime, set_file_mtime}; use itertools::Itertools; - use ruff_linter::settings::LinterSettings; use test_case::test_case; use ruff_cache::CACHE_DIR_NAME; - use ruff_linter::message::OldDiagnostic; + use ruff_db::diagnostic::Diagnostic; use ruff_linter::package::PackageRoot; + use ruff_linter::settings::LinterSettings; use ruff_linter::settings::flags; use ruff_linter::settings::types::UnsafeFixes; use ruff_python_ast::{PySourceType, PythonVersion}; @@ -680,7 +681,7 @@ mod tests { UnsafeFixes::Enabled, ) .unwrap(); - if diagnostics.inner.iter().any(OldDiagnostic::is_syntax_error) { + if diagnostics.inner.iter().any(Diagnostic::is_syntax_error) { parse_errors.push(path.clone()); } paths.push(path); diff --git a/crates/ruff/src/commands/check.rs b/crates/ruff/src/commands/check.rs index c3df35c1ed..8a7a71fc7b 100644 --- a/crates/ruff/src/commands/check.rs +++ b/crates/ruff/src/commands/check.rs @@ -9,10 +9,10 @@ use ignore::Error; use log::{debug, error, warn}; #[cfg(not(target_family = "wasm"))] use rayon::prelude::*; +use ruff_linter::message::diagnostic_from_violation; use rustc_hash::FxHashMap; use ruff_db::panic::catch_unwind; -use ruff_linter::OldDiagnostic; use ruff_linter::package::PackageRoot; use ruff_linter::registry::Rule; use ruff_linter::settings::types::UnsafeFixes; @@ -129,7 +129,7 @@ pub(crate) fn check( SourceFileBuilder::new(path.to_string_lossy().as_ref(), "").finish(); Diagnostics::new( - vec![OldDiagnostic::new( + vec![diagnostic_from_violation( IOError { message }, TextRange::default(), &dummy, diff --git a/crates/ruff/src/diagnostics.rs b/crates/ruff/src/diagnostics.rs index f2784d36a6..44c3263487 100644 --- a/crates/ruff/src/diagnostics.rs +++ b/crates/ruff/src/diagnostics.rs @@ -10,11 +10,10 @@ use std::path::Path; use anyhow::{Context, Result}; use colored::Colorize; use log::{debug, warn}; -use rustc_hash::FxHashMap; - -use ruff_linter::OldDiagnostic; +use ruff_db::diagnostic::Diagnostic; use ruff_linter::codes::Rule; use ruff_linter::linter::{FixTable, FixerResult, LinterResult, ParseSource, lint_fix, lint_only}; +use ruff_linter::message::{create_syntax_error_diagnostic, diagnostic_from_violation}; use ruff_linter::package::PackageRoot; use ruff_linter::pyproject_toml::lint_pyproject_toml; use ruff_linter::settings::types::UnsafeFixes; @@ -26,19 +25,20 @@ use ruff_python_ast::{PySourceType, SourceType, TomlSourceType}; use ruff_source_file::SourceFileBuilder; use ruff_text_size::TextRange; use ruff_workspace::Settings; +use rustc_hash::FxHashMap; use crate::cache::{Cache, FileCacheKey, LintCacheData}; #[derive(Debug, Default, PartialEq)] pub(crate) struct Diagnostics { - pub(crate) inner: Vec, + pub(crate) inner: Vec, pub(crate) fixed: FixMap, pub(crate) notebook_indexes: FxHashMap, } impl Diagnostics { pub(crate) fn new( - diagnostics: Vec, + diagnostics: Vec, notebook_indexes: FxHashMap, ) -> Self { Self { @@ -62,7 +62,7 @@ impl Diagnostics { let name = path.map_or_else(|| "-".into(), Path::to_string_lossy); let source_file = SourceFileBuilder::new(name, "").finish(); Self::new( - vec![OldDiagnostic::new( + vec![diagnostic_from_violation( IOError { message: err.to_string(), }, @@ -98,10 +98,10 @@ impl Diagnostics { let name = path.map_or_else(|| "-".into(), Path::to_string_lossy); let dummy = SourceFileBuilder::new(name, "").finish(); Self::new( - vec![OldDiagnostic::syntax_error( + vec![create_syntax_error_diagnostic( + dummy, err, TextRange::default(), - dummy, )], FxHashMap::default(), ) diff --git a/crates/ruff/src/printer.rs b/crates/ruff/src/printer.rs index 8052915cfb..ffead1dc0b 100644 --- a/crates/ruff/src/printer.rs +++ b/crates/ruff/src/printer.rs @@ -9,12 +9,13 @@ use itertools::{Itertools, iterate}; use ruff_linter::linter::FixTable; use serde::Serialize; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; use ruff_linter::fs::relativize_path; use ruff_linter::logging::LogLevel; use ruff_linter::message::{ AzureEmitter, Emitter, EmitterContext, GithubEmitter, GitlabEmitter, GroupedEmitter, - JsonEmitter, JsonLinesEmitter, JunitEmitter, OldDiagnostic, PylintEmitter, RdjsonEmitter, - SarifEmitter, SecondaryCode, TextEmitter, + JsonEmitter, JsonLinesEmitter, JunitEmitter, PylintEmitter, RdjsonEmitter, SarifEmitter, + TextEmitter, }; use ruff_linter::notify_user; use ruff_linter::settings::flags::{self}; @@ -306,8 +307,7 @@ impl Printer { .sorted_by_key(|(code, message)| (*code, message.fixable())) .fold( vec![], - |mut acc: Vec<((Option<&SecondaryCode>, &OldDiagnostic), usize)>, - (code, message)| { + |mut acc: Vec<((Option<&SecondaryCode>, &Diagnostic), usize)>, (code, message)| { if let Some(((prev_code, _prev_message), count)) = acc.last_mut() { if *prev_code == code { *count += 1; diff --git a/crates/ruff/tests/integration_test.rs b/crates/ruff/tests/integration_test.rs index 3e8f5497ed..2041040b95 100644 --- a/crates/ruff/tests/integration_test.rs +++ b/crates/ruff/tests/integration_test.rs @@ -1067,7 +1067,7 @@ fn show_statistics_syntax_errors() { success: false exit_code: 1 ----- stdout ----- - 1 syntax-error + 1 invalid-syntax Found 1 error. ----- stderr ----- @@ -1080,7 +1080,7 @@ fn show_statistics_syntax_errors() { success: false exit_code: 1 ----- stdout ----- - 1 syntax-error + 1 invalid-syntax Found 1 error. ----- stderr ----- @@ -1093,7 +1093,7 @@ fn show_statistics_syntax_errors() { success: false exit_code: 1 ----- stdout ----- - 1 syntax-error + 1 invalid-syntax Found 1 error. ----- stderr ----- diff --git a/crates/ruff_db/Cargo.toml b/crates/ruff_db/Cargo.toml index fa73022e93..c8588b4368 100644 --- a/crates/ruff_db/Cargo.toml +++ b/crates/ruff_db/Cargo.toml @@ -13,6 +13,7 @@ license = { workspace = true } [dependencies] ruff_annotate_snippets = { workspace = true } ruff_cache = { workspace = true, optional = true } +ruff_diagnostics = { workspace = true } ruff_notebook = { workspace = true } ruff_python_ast = { workspace = true, features = ["get-size"] } ruff_python_parser = { workspace = true } diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index f4d5d4fb9c..dadb037fdf 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -1,10 +1,11 @@ use std::{fmt::Formatter, sync::Arc}; use render::{FileResolver, Input}; -use ruff_source_file::{SourceCode, SourceFile}; +use ruff_diagnostics::Fix; +use ruff_source_file::{LineColumn, SourceCode, SourceFile}; use ruff_annotate_snippets::Level as AnnotateLevel; -use ruff_text_size::{Ranged, TextRange}; +use ruff_text_size::{Ranged, TextRange, TextSize}; pub use self::render::DisplayDiagnostic; use crate::{Db, files::File}; @@ -62,10 +63,37 @@ impl Diagnostic { message: message.into_diagnostic_message(), annotations: vec![], subs: vec![], + fix: None, + parent: None, + noqa_offset: None, + secondary_code: None, }); Diagnostic { inner } } + /// Creates a `Diagnostic` for a syntax error. + /// + /// Unlike the more general [`Diagnostic::new`], this requires a [`Span`] and a [`TextRange`] + /// attached to it. + /// + /// This should _probably_ be a method on the syntax errors, but + /// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of + /// the other way around. And since we want to do this conversion in a couple + /// places, it makes sense to centralize it _somewhere_. So it's here for now. + /// + /// Note that `message` is stored in the primary annotation, _not_ in the primary diagnostic + /// message. + pub fn syntax_error( + span: impl Into, + message: impl IntoDiagnosticMessage, + range: impl Ranged, + ) -> Diagnostic { + let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, ""); + let span = span.into().with_range(range.range()); + diag.annotate(Annotation::primary(span).message(message)); + diag + } + /// Add an annotation to this diagnostic. /// /// Annotations for a diagnostic are optional, but if any are added, @@ -226,6 +254,11 @@ impl Diagnostic { self.primary_annotation().map(|ann| ann.span.clone()) } + /// Returns a reference to the primary span of this diagnostic. + pub fn primary_span_ref(&self) -> Option<&Span> { + self.primary_annotation().map(|ann| &ann.span) + } + /// Returns the tags from the primary annotation of this diagnostic if it exists. pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> { self.primary_annotation().map(|ann| ann.tags.as_slice()) @@ -268,6 +301,167 @@ impl Diagnostic { pub fn sub_diagnostics(&self) -> &[SubDiagnostic] { &self.inner.subs } + + /// Returns the fix for this diagnostic if it exists. + pub fn fix(&self) -> Option<&Fix> { + self.inner.fix.as_ref() + } + + /// Set the fix for this diagnostic. + pub fn set_fix(&mut self, fix: Fix) { + Arc::make_mut(&mut self.inner).fix = Some(fix); + } + + /// Remove the fix for this diagnostic. + pub fn remove_fix(&mut self) { + Arc::make_mut(&mut self.inner).fix = None; + } + + /// Returns `true` if the diagnostic contains a [`Fix`]. + pub fn fixable(&self) -> bool { + self.fix().is_some() + } + + /// Returns the offset of the parent statement for this diagnostic if it exists. + /// + /// This is primarily used for checking noqa/secondary code suppressions. + pub fn parent(&self) -> Option { + self.inner.parent + } + + /// Set the offset of the diagnostic's parent statement. + pub fn set_parent(&mut self, parent: TextSize) { + Arc::make_mut(&mut self.inner).parent = Some(parent); + } + + /// Returns the remapped offset for a suppression comment if it exists. + /// + /// Like [`Diagnostic::parent`], this is used for noqa code suppression comments in Ruff. + pub fn noqa_offset(&self) -> Option { + self.inner.noqa_offset + } + + /// Set the remapped offset for a suppression comment. + pub fn set_noqa_offset(&mut self, noqa_offset: TextSize) { + Arc::make_mut(&mut self.inner).noqa_offset = Some(noqa_offset); + } + + /// Returns the secondary code for the diagnostic if it exists. + /// + /// The "primary" code for the diagnostic is its lint name. Diagnostics in ty don't have + /// secondary codes (yet), but in Ruff the noqa code is used. + pub fn secondary_code(&self) -> Option<&SecondaryCode> { + self.inner.secondary_code.as_ref() + } + + /// Set the secondary code for this diagnostic. + pub fn set_secondary_code(&mut self, code: SecondaryCode) { + Arc::make_mut(&mut self.inner).secondary_code = Some(code); + } + + /// Returns the name used to represent the diagnostic. + pub fn name(&self) -> &'static str { + self.id().as_str() + } + + /// Returns `true` if `self` is a syntax error message. + pub fn is_syntax_error(&self) -> bool { + self.id().is_invalid_syntax() + } + + /// Returns the message body to display to the user. + pub fn body(&self) -> &str { + self.primary_message() + } + + /// Returns the fix suggestion for the violation. + pub fn suggestion(&self) -> Option<&str> { + self.primary_annotation()?.get_message() + } + + /// Returns the URL for the rule documentation, if it exists. + pub fn to_url(&self) -> Option { + if self.is_syntax_error() { + None + } else { + Some(format!( + "{}/rules/{}", + env!("CARGO_PKG_HOMEPAGE"), + self.name() + )) + } + } + + /// Returns the filename for the message. + /// + /// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`. + pub fn expect_ruff_filename(&self) -> String { + self.expect_primary_span() + .expect_ruff_file() + .name() + .to_string() + } + + /// Computes the start source location for the message. + /// + /// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the + /// span has no range. + pub fn expect_ruff_start_location(&self) -> LineColumn { + self.expect_primary_span() + .expect_ruff_file() + .to_source_code() + .line_column(self.expect_range().start()) + } + + /// Computes the end source location for the message. + /// + /// Panics if the diagnostic has no primary span, if its file is not a `SourceFile`, or if the + /// span has no range. + pub fn expect_ruff_end_location(&self) -> LineColumn { + self.expect_primary_span() + .expect_ruff_file() + .to_source_code() + .line_column(self.expect_range().end()) + } + + /// Returns the [`SourceFile`] which the message belongs to. + pub fn ruff_source_file(&self) -> Option<&SourceFile> { + self.primary_span_ref()?.as_ruff_file() + } + + /// Returns the [`SourceFile`] which the message belongs to. + /// + /// Panics if the diagnostic has no primary span, or if its file is not a `SourceFile`. + pub fn expect_ruff_source_file(&self) -> SourceFile { + self.expect_primary_span().expect_ruff_file().clone() + } + + /// Returns the [`TextRange`] for the diagnostic. + pub fn range(&self) -> Option { + self.primary_span()?.range() + } + + /// Returns the [`TextRange`] for the diagnostic. + /// + /// Panics if the diagnostic has no primary span or if the span has no range. + pub fn expect_range(&self) -> TextRange { + self.range().expect("Expected a range for the primary span") + } +} + +impl Ord for Diagnostic { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal) + } +} + +impl PartialOrd for Diagnostic { + fn partial_cmp(&self, other: &Self) -> Option { + Some( + (self.ruff_source_file()?, self.range()?.start()) + .cmp(&(other.ruff_source_file()?, other.range()?.start())), + ) + } } #[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)] @@ -277,6 +471,10 @@ struct DiagnosticInner { message: DiagnosticMessage, annotations: Vec, subs: Vec, + fix: Option, + parent: Option, + noqa_offset: Option, + secondary_code: Option, } struct RenderingSortKey<'a> { @@ -897,9 +1095,15 @@ impl Span { /// /// Panics if the file is a [`UnifiedFile::Ty`] instead of a [`UnifiedFile::Ruff`]. pub fn expect_ruff_file(&self) -> &SourceFile { + self.as_ruff_file() + .expect("Expected a ruff `SourceFile`, found a ty `File`") + } + + /// Returns the [`SourceFile`] attached to this [`Span`]. + pub fn as_ruff_file(&self) -> Option<&SourceFile> { match &self.file { - UnifiedFile::Ty(_) => panic!("Expected a ruff `SourceFile`, found a ty `File`"), - UnifiedFile::Ruff(file) => file, + UnifiedFile::Ty(_) => None, + UnifiedFile::Ruff(file) => Some(file), } } } @@ -1147,41 +1351,52 @@ impl IntoDiagnosticMessage for T { } } -/// Creates a `Diagnostic` from a parse error. +/// A secondary identifier for a lint diagnostic. /// -/// This should _probably_ be a method on `ruff_python_parser::ParseError`, but -/// at time of writing, `ruff_db` depends on `ruff_python_parser` instead of -/// the other way around. And since we want to do this conversion in a couple -/// places, it makes sense to centralize it _somewhere_. So it's here for now. -pub fn create_parse_diagnostic(file: File, err: &ruff_python_parser::ParseError) -> Diagnostic { - let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, ""); - let span = Span::from(file).with_range(err.location); - diag.annotate(Annotation::primary(span).message(&err.error)); - diag +/// For Ruff rules this means the noqa code. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, get_size2::GetSize)] +#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(transparent))] +pub struct SecondaryCode(String); + +impl SecondaryCode { + pub fn new(code: String) -> Self { + Self(code) + } + + pub fn as_str(&self) -> &str { + &self.0 + } } -/// Creates a `Diagnostic` from an unsupported syntax error. -/// -/// See [`create_parse_diagnostic`] for more details. -pub fn create_unsupported_syntax_diagnostic( - file: File, - err: &ruff_python_parser::UnsupportedSyntaxError, -) -> Diagnostic { - let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, ""); - let span = Span::from(file).with_range(err.range); - diag.annotate(Annotation::primary(span).message(err.to_string())); - diag +impl std::fmt::Display for SecondaryCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } } -/// Creates a `Diagnostic` from a semantic syntax error. -/// -/// See [`create_parse_diagnostic`] for more details. -pub fn create_semantic_syntax_diagnostic( - file: File, - err: &ruff_python_parser::semantic_errors::SemanticSyntaxError, -) -> Diagnostic { - let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, ""); - let span = Span::from(file).with_range(err.range); - diag.annotate(Annotation::primary(span).message(err.to_string())); - diag +impl std::ops::Deref for SecondaryCode { + type Target = str; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq<&str> for SecondaryCode { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + +impl PartialEq for &str { + fn eq(&self, other: &SecondaryCode) -> bool { + other.eq(self) + } +} + +// for `hashbrown::EntryRef` +impl From<&SecondaryCode> for SecondaryCode { + fn from(value: &SecondaryCode) -> Self { + value.clone() + } } diff --git a/crates/ruff_diagnostics/Cargo.toml b/crates/ruff_diagnostics/Cargo.toml index 7fc5865b1e..810364a844 100644 --- a/crates/ruff_diagnostics/Cargo.toml +++ b/crates/ruff_diagnostics/Cargo.toml @@ -16,5 +16,6 @@ doctest = false [dependencies] ruff_text_size = { workspace = true } +get-size2 = { workspace = true } is-macro = { workspace = true } serde = { workspace = true, optional = true, features = [] } diff --git a/crates/ruff_diagnostics/src/edit.rs b/crates/ruff_diagnostics/src/edit.rs index 1195fdc540..194b4e4494 100644 --- a/crates/ruff_diagnostics/src/edit.rs +++ b/crates/ruff_diagnostics/src/edit.rs @@ -7,7 +7,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; /// A text edit to be applied to a source file. Inserts, deletes, or replaces /// content at a given location. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, get_size2::GetSize)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Edit { /// The start location of the edit. diff --git a/crates/ruff_diagnostics/src/fix.rs b/crates/ruff_diagnostics/src/fix.rs index 2db9d799fe..2fbc152253 100644 --- a/crates/ruff_diagnostics/src/fix.rs +++ b/crates/ruff_diagnostics/src/fix.rs @@ -6,7 +6,9 @@ use ruff_text_size::{Ranged, TextSize}; use crate::edit::Edit; /// Indicates if a fix can be applied. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is)] +#[derive( + Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, is_macro::Is, get_size2::GetSize, +)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))] pub enum Applicability { @@ -30,7 +32,7 @@ pub enum Applicability { } /// Indicates the level of isolation required to apply a fix. -#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Default, Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, get_size2::GetSize)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum IsolationLevel { /// The fix should be applied as long as no other fixes in the same group have been applied. @@ -41,7 +43,7 @@ pub enum IsolationLevel { } /// A collection of [`Edit`] elements to be applied to a source file. -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone, get_size2::GetSize)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Fix { /// The [`Edit`] elements to be applied, sorted by [`Edit::start`] in ascending order. diff --git a/crates/ruff_linter/Cargo.toml b/crates/ruff_linter/Cargo.toml index 9cf5672720..d65f31d1f5 100644 --- a/crates/ruff_linter/Cargo.toml +++ b/crates/ruff_linter/Cargo.toml @@ -15,7 +15,7 @@ license = { workspace = true } [dependencies] ruff_annotate_snippets = { workspace = true } ruff_cache = { workspace = true } -ruff_db = { workspace = true } +ruff_db = { workspace = true, features = ["serde"] } ruff_diagnostics = { workspace = true, features = ["serde"] } ruff_notebook = { workspace = true } ruff_macros = { workspace = true } diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 759fbb074d..e1def841e5 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -28,6 +28,7 @@ use itertools::Itertools; use log::debug; use rustc_hash::{FxHashMap, FxHashSet}; +use ruff_db::diagnostic::Diagnostic; use ruff_diagnostics::{Applicability, Fix, IsolationLevel}; use ruff_notebook::{CellOffsets, NotebookIndex}; use ruff_python_ast::helpers::{collect_import_from_member, is_docstring_stmt, to_module_path}; @@ -63,6 +64,7 @@ use ruff_text_size::{Ranged, TextRange, TextSize}; use crate::checkers::ast::annotation::AnnotationContext; use crate::docstrings::extraction::ExtractionTarget; use crate::importer::{ImportRequest, Importer, ResolutionError}; +use crate::message::diagnostic_from_violation; use crate::noqa::NoqaMapping; use crate::package::PackageRoot; use crate::preview::is_undefined_export_in_dunder_init_enabled; @@ -74,7 +76,7 @@ use crate::rules::pylint::rules::{AwaitOutsideAsync, LoadBeforeGlobalDeclaration use crate::rules::{flake8_pyi, flake8_type_checking, pyflakes, pyupgrade}; use crate::settings::rule_table::RuleTable; use crate::settings::{LinterSettings, TargetVersion, flags}; -use crate::{Edit, OldDiagnostic, Violation}; +use crate::{Edit, Violation}; use crate::{Locator, docstrings, noqa}; mod analyze; @@ -388,7 +390,7 @@ impl<'a> Checker<'a> { /// Return a [`DiagnosticGuard`] for reporting a diagnostic. /// - /// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic + /// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic /// before it is added to the collection in the checker on `Drop`. pub(crate) fn report_diagnostic<'chk, T: Violation>( &'chk self, @@ -401,7 +403,7 @@ impl<'a> Checker<'a> { /// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is /// enabled. /// - /// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic + /// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic /// before it is added to the collection in the checker on `Drop`. pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>( &'chk self, @@ -3116,9 +3118,9 @@ pub(crate) fn check_ast( /// A type for collecting diagnostics in a given file. /// /// [`LintContext::report_diagnostic`] can be used to obtain a [`DiagnosticGuard`], which will push -/// a [`Violation`] to the contained [`OldDiagnostic`] collection on `Drop`. +/// a [`Violation`] to the contained [`Diagnostic`] collection on `Drop`. pub(crate) struct LintContext<'a> { - diagnostics: RefCell>, + diagnostics: RefCell>, source_file: SourceFile, rules: RuleTable, settings: &'a LinterSettings, @@ -3126,7 +3128,7 @@ pub(crate) struct LintContext<'a> { impl<'a> LintContext<'a> { /// Create a new collector with the given `source_file` and an empty collection of - /// `OldDiagnostic`s. + /// `Diagnostic`s. pub(crate) fn new(path: &Path, contents: &str, settings: &'a LinterSettings) -> Self { let source_file = SourceFileBuilder::new(path.to_string_lossy().as_ref(), contents).finish(); @@ -3147,7 +3149,7 @@ impl<'a> LintContext<'a> { /// Return a [`DiagnosticGuard`] for reporting a diagnostic. /// - /// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic + /// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic /// before it is added to the collection in the context on `Drop`. pub(crate) fn report_diagnostic<'chk, T: Violation>( &'chk self, @@ -3156,7 +3158,7 @@ impl<'a> LintContext<'a> { ) -> DiagnosticGuard<'chk, 'a> { DiagnosticGuard { context: self, - diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)), + diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)), rule: T::rule(), } } @@ -3164,7 +3166,7 @@ impl<'a> LintContext<'a> { /// Return a [`DiagnosticGuard`] for reporting a diagnostic if the corresponding rule is /// enabled. /// - /// The guard derefs to an [`OldDiagnostic`], so it can be used to further modify the diagnostic + /// The guard derefs to a [`Diagnostic`], so it can be used to further modify the diagnostic /// before it is added to the collection in the context on `Drop`. pub(crate) fn report_diagnostic_if_enabled<'chk, T: Violation>( &'chk self, @@ -3175,7 +3177,7 @@ impl<'a> LintContext<'a> { if self.is_rule_enabled(rule) { Some(DiagnosticGuard { context: self, - diagnostic: Some(OldDiagnostic::new(kind, range, &self.source_file)), + diagnostic: Some(diagnostic_from_violation(kind, range, &self.source_file)), rule, }) } else { @@ -3199,17 +3201,17 @@ impl<'a> LintContext<'a> { } #[inline] - pub(crate) fn into_parts(self) -> (Vec, SourceFile) { + pub(crate) fn into_parts(self) -> (Vec, SourceFile) { (self.diagnostics.into_inner(), self.source_file) } #[inline] - pub(crate) fn as_mut_vec(&mut self) -> &mut Vec { + pub(crate) fn as_mut_vec(&mut self) -> &mut Vec { self.diagnostics.get_mut() } #[inline] - pub(crate) fn iter(&mut self) -> impl Iterator { + pub(crate) fn iter(&mut self) -> impl Iterator { self.diagnostics.get_mut().iter() } } @@ -3227,7 +3229,7 @@ pub(crate) struct DiagnosticGuard<'a, 'b> { /// The diagnostic that we want to report. /// /// This is always `Some` until the `Drop` (or `defuse`) call. - diagnostic: Option, + diagnostic: Option, rule: Rule, } @@ -3253,11 +3255,14 @@ impl DiagnosticGuard<'_, '_> { #[inline] pub(crate) fn set_fix(&mut self, fix: Fix) { if !self.context.rules.should_fix(self.rule) { - self.fix = None; + self.diagnostic.as_mut().unwrap().remove_fix(); return; } let applicability = self.resolve_applicability(&fix); - self.fix = Some(fix.with_applicability(applicability)); + self.diagnostic + .as_mut() + .unwrap() + .set_fix(fix.with_applicability(applicability)); } /// Set the [`Fix`] used to fix the diagnostic, if the provided function returns `Ok`. @@ -3286,9 +3291,9 @@ impl DiagnosticGuard<'_, '_> { } impl std::ops::Deref for DiagnosticGuard<'_, '_> { - type Target = OldDiagnostic; + type Target = Diagnostic; - fn deref(&self) -> &OldDiagnostic { + fn deref(&self) -> &Diagnostic { // OK because `self.diagnostic` is only `None` within `Drop`. self.diagnostic.as_ref().unwrap() } @@ -3296,7 +3301,7 @@ impl std::ops::Deref for DiagnosticGuard<'_, '_> { /// Return a mutable borrow of the diagnostic in this guard. impl std::ops::DerefMut for DiagnosticGuard<'_, '_> { - fn deref_mut(&mut self) -> &mut OldDiagnostic { + fn deref_mut(&mut self) -> &mut Diagnostic { // OK because `self.diagnostic` is only `None` within `Drop`. self.diagnostic.as_mut().unwrap() } diff --git a/crates/ruff_linter/src/checkers/noqa.rs b/crates/ruff_linter/src/checkers/noqa.rs index 5758ab0fc4..bf411511ce 100644 --- a/crates/ruff_linter/src/checkers/noqa.rs +++ b/crates/ruff_linter/src/checkers/noqa.rs @@ -66,9 +66,9 @@ pub(crate) fn check_noqa( } let noqa_offsets = diagnostic - .parent + .parent() .into_iter() - .chain(std::iter::once(diagnostic.start())) + .chain(std::iter::once(diagnostic.expect_range().start())) .map(|position| noqa_line_for.resolve(position)) .unique(); diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index dd3b86d25c..2cbdaead9a 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -4,6 +4,7 @@ /// `--select`. For pylint this is e.g. C0414 and E0118 but also C and E01. use std::fmt::Formatter; +use ruff_db::diagnostic::SecondaryCode; use strum_macros::EnumIter; use crate::registry::Linter; @@ -52,6 +53,18 @@ impl PartialEq for &str { } } +impl PartialEq for SecondaryCode { + fn eq(&self, other: &NoqaCode) -> bool { + &self.as_str() == other + } +} + +impl PartialEq for NoqaCode { + fn eq(&self, other: &SecondaryCode) -> bool { + other.eq(self) + } +} + impl serde::Serialize for NoqaCode { fn serialize(&self, serializer: S) -> std::result::Result where diff --git a/crates/ruff_linter/src/fix/edits.rs b/crates/ruff_linter/src/fix/edits.rs index dc2b7b68ac..f17cbc3290 100644 --- a/crates/ruff_linter/src/fix/edits.rs +++ b/crates/ruff_linter/src/fix/edits.rs @@ -618,7 +618,8 @@ mod tests { use crate::fix::edits::{ add_to_dunder_all, make_redundant_alias, next_stmt_break, trailing_semicolon, }; - use crate::{Edit, Fix, Locator, OldDiagnostic}; + use crate::message::diagnostic_from_violation; + use crate::{Edit, Fix, Locator}; /// Parse the given source using [`Mode::Module`] and return the first statement. fn parse_first_stmt(source: &str) -> Result { @@ -749,12 +750,12 @@ x = 1 \ let diag = { use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; let mut iter = edits.into_iter(); - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = diagnostic_from_violation( MissingNewlineAtEndOfFile, // The choice of rule here is arbitrary. TextRange::default(), &SourceFileBuilder::new("", "").finish(), ); - diagnostic.fix = Some(Fix::safe_edits( + diagnostic.set_fix(Fix::safe_edits( iter.next().ok_or(anyhow!("expected edits nonempty"))?, iter, )); diff --git a/crates/ruff_linter/src/fix/mod.rs b/crates/ruff_linter/src/fix/mod.rs index f61b26e786..e68898a5e6 100644 --- a/crates/ruff_linter/src/fix/mod.rs +++ b/crates/ruff_linter/src/fix/mod.rs @@ -3,12 +3,12 @@ use std::collections::BTreeSet; use itertools::Itertools; use rustc_hash::FxHashSet; +use ruff_db::diagnostic::Diagnostic; use ruff_diagnostics::{IsolationLevel, SourceMap}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; use crate::Locator; use crate::linter::FixTable; -use crate::message::OldDiagnostic; use crate::registry::Rule; use crate::settings::types::UnsafeFixes; use crate::{Edit, Fix}; @@ -28,7 +28,7 @@ pub(crate) struct FixResult { /// Fix errors in a file, and write the fixed source code to disk. pub(crate) fn fix_file( - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], locator: &Locator, unsafe_fixes: UnsafeFixes, ) -> Option { @@ -52,7 +52,7 @@ pub(crate) fn fix_file( /// Apply a series of fixes. fn apply_fixes<'a>( - diagnostics: impl Iterator, + diagnostics: impl Iterator, locator: &'a Locator<'a>, ) -> FixResult { let mut output = String::with_capacity(locator.len()); @@ -173,25 +173,26 @@ mod tests { use ruff_text_size::{Ranged, TextSize}; use crate::Locator; - use crate::OldDiagnostic; use crate::fix::{FixResult, apply_fixes}; + use crate::message::diagnostic_from_violation; use crate::rules::pycodestyle::rules::MissingNewlineAtEndOfFile; use crate::{Edit, Fix}; + use ruff_db::diagnostic::Diagnostic; fn create_diagnostics( filename: &str, source: &str, edit: impl IntoIterator, - ) -> Vec { + ) -> Vec { edit.into_iter() .map(|edit| { // The choice of rule here is arbitrary. - let mut diagnostic = OldDiagnostic::new( + let mut diagnostic = diagnostic_from_violation( MissingNewlineAtEndOfFile, edit.range(), &SourceFileBuilder::new(filename, source).finish(), ); - diagnostic.fix = Some(Fix::safe_edit(edit)); + diagnostic.set_fix(Fix::safe_edit(edit)); diagnostic }) .collect() diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index dfb97381db..eaafd7a526 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -14,7 +14,6 @@ pub use rule_selector::RuleSelector; pub use rule_selector::clap_completion::RuleSelectorParser; pub use rules::pycodestyle::rules::IOError; -pub use message::OldDiagnostic; pub(crate) use ruff_diagnostics::{Applicability, Edit, Fix}; pub use violation::{AlwaysFixableViolation, FixAvailability, Violation, ViolationMetadata}; diff --git a/crates/ruff_linter/src/linter.rs b/crates/ruff_linter/src/linter.rs index 09132a0f6f..ddb82c7d20 100644 --- a/crates/ruff_linter/src/linter.rs +++ b/crates/ruff_linter/src/linter.rs @@ -7,15 +7,14 @@ use itertools::Itertools; use ruff_python_parser::semantic_errors::SemanticSyntaxError; use rustc_hash::FxBuildHasher; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; use ruff_notebook::Notebook; use ruff_python_ast::{ModModule, PySourceType, PythonVersion}; use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_parser::{ParseError, ParseOptions, Parsed, UnsupportedSyntaxError}; use ruff_source_file::SourceFile; -use ruff_text_size::Ranged; -use crate::OldDiagnostic; use crate::checkers::ast::{LintContext, check_ast}; use crate::checkers::filesystem::check_file_path; use crate::checkers::imports::check_imports; @@ -25,7 +24,7 @@ use crate::checkers::tokens::check_tokens; use crate::directives::Directives; use crate::doc_lines::{doc_lines_from_ast, doc_lines_from_tokens}; use crate::fix::{FixResult, fix_file}; -use crate::message::SecondaryCode; +use crate::message::create_syntax_error_diagnostic; use crate::noqa::add_noqa; use crate::package::PackageRoot; use crate::preview::is_py314_support_enabled; @@ -41,7 +40,7 @@ pub(crate) mod float; pub struct LinterResult { /// A collection of diagnostic messages generated by the linter. - pub diagnostics: Vec, + pub diagnostics: Vec, /// Flag indicating that the parsed source code does not contain any /// [`ParseError`]s has_valid_syntax: bool, @@ -145,7 +144,7 @@ pub struct FixerResult<'a> { pub fixed: FixTable, } -/// Generate [`OldDiagnostic`]s from the source code contents at the given `Path`. +/// Generate [`Diagnostic`]s from the source code contents at the given `Path`. #[expect(clippy::too_many_arguments)] pub fn check_path( path: &Path, @@ -160,7 +159,7 @@ pub fn check_path( source_type: PySourceType, parsed: &Parsed, target_version: TargetVersion, -) -> Vec { +) -> Vec { // Aggregate all diagnostics. let mut context = LintContext::new(path, locator.contents(), settings); @@ -382,7 +381,7 @@ pub fn check_path( if !parsed.has_valid_syntax() { // Avoid fixing in case the source code contains syntax errors. for diagnostic in &mut diagnostics { - diagnostic.fix = None; + diagnostic.remove_fix(); } } @@ -393,7 +392,6 @@ pub fn check_path( parsed.errors(), syntax_errors, &semantic_syntax_errors, - locator, directives, &source_file, ) @@ -459,7 +457,7 @@ pub fn add_noqa_to_path( ) } -/// Generate an [`OldDiagnostic`] for each diagnostic triggered by the given source code. +/// Generate a [`Diagnostic`] for each diagnostic triggered by the given source code. pub fn lint_only( path: &Path, package: Option>, @@ -516,7 +514,7 @@ pub fn lint_only( LinterResult { has_valid_syntax: parsed.has_valid_syntax(), - has_no_syntax_errors: !diagnostics.iter().any(OldDiagnostic::is_syntax_error), + has_no_syntax_errors: !diagnostics.iter().any(Diagnostic::is_syntax_error), diagnostics, } } @@ -525,30 +523,32 @@ pub fn lint_only( /// /// Also use `directives` to attach noqa offsets to lint diagnostics. fn diagnostics_to_messages( - diagnostics: Vec, + diagnostics: Vec, parse_errors: &[ParseError], unsupported_syntax_errors: &[UnsupportedSyntaxError], semantic_syntax_errors: &[SemanticSyntaxError], - locator: &Locator, directives: &Directives, source_file: &SourceFile, -) -> Vec { +) -> Vec { parse_errors .iter() .map(|parse_error| { - OldDiagnostic::from_parse_error(parse_error, locator, source_file.clone()) + create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error) }) .chain(unsupported_syntax_errors.iter().map(|syntax_error| { - OldDiagnostic::from_unsupported_syntax_error(syntax_error, source_file.clone()) + create_syntax_error_diagnostic(source_file.clone(), syntax_error, syntax_error) })) .chain( semantic_syntax_errors .iter() - .map(|error| OldDiagnostic::from_semantic_syntax_error(error, source_file.clone())), + .map(|error| create_syntax_error_diagnostic(source_file.clone(), error, error)), ) - .chain(diagnostics.into_iter().map(|diagnostic| { - let noqa_offset = directives.noqa_line_for.resolve(diagnostic.start()); - diagnostic.with_noqa_offset(noqa_offset) + .chain(diagnostics.into_iter().map(|mut diagnostic| { + let noqa_offset = directives + .noqa_line_for + .resolve(diagnostic.expect_range().start()); + diagnostic.set_noqa_offset(noqa_offset); + diagnostic })) .collect() } @@ -629,7 +629,7 @@ pub fn lint_fix<'a>( if iterations == 0 { has_valid_syntax = parsed.has_valid_syntax(); - has_no_syntax_errors = !diagnostics.iter().any(OldDiagnostic::is_syntax_error); + has_no_syntax_errors = !diagnostics.iter().any(Diagnostic::is_syntax_error); } else { // If the source code had no syntax errors on the first pass, but // does on a subsequent pass, then we've introduced a @@ -687,8 +687,8 @@ where } #[expect(clippy::print_stderr)] -fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[OldDiagnostic]) { - let codes = collect_rule_codes(diagnostics.iter().filter_map(OldDiagnostic::secondary_code)); +fn report_failed_to_converge_error(path: &Path, transformed: &str, diagnostics: &[Diagnostic]) { + let codes = collect_rule_codes(diagnostics.iter().filter_map(Diagnostic::secondary_code)); if cfg!(debug_assertions) { eprintln!( "{}{} Failed to converge after {} iterations in `{}` with rule codes {}:---\n{}\n---", @@ -806,13 +806,12 @@ mod tests { use ruff_python_index::Indexer; use ruff_python_parser::ParseOptions; use ruff_python_trivia::textwrap::dedent; - use ruff_text_size::Ranged; use test_case::test_case; + use ruff_db::diagnostic::Diagnostic; use ruff_notebook::{Notebook, NotebookError}; use crate::linter::check_path; - use crate::message::OldDiagnostic; use crate::registry::Rule; use crate::settings::LinterSettings; use crate::source_kind::SourceKind; @@ -970,7 +969,7 @@ mod tests { /// Wrapper around `test_contents_syntax_errors` for testing a snippet of code instead of a /// file. - fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec { + fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec { let contents = dedent(contents); test_contents_syntax_errors( &SourceKind::Python(contents.to_string()), @@ -985,7 +984,7 @@ mod tests { source_kind: &SourceKind, path: &Path, settings: &LinterSettings, - ) -> Vec { + ) -> Vec { let source_type = PySourceType::from(path); let target_version = settings.resolve_target_version(path); let options = @@ -1016,7 +1015,7 @@ mod tests { &parsed, target_version, ); - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); diagnostics } diff --git a/crates/ruff_linter/src/logging.rs b/crates/ruff_linter/src/logging.rs index dadf6ce251..5b293c797b 100644 --- a/crates/ruff_linter/src/logging.rs +++ b/crates/ruff_linter/src/logging.rs @@ -1,4 +1,4 @@ -use std::fmt::{Display, Formatter, Write}; +use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; use std::sync::{LazyLock, Mutex}; @@ -6,7 +6,7 @@ use anyhow::Result; use colored::Colorize; use fern; use log::Level; -use ruff_python_parser::{ParseError, ParseErrorType}; +use ruff_python_parser::ParseError; use rustc_hash::FxHashSet; use ruff_source_file::{LineColumn, LineIndex, OneIndexed, SourceCode}; @@ -248,7 +248,7 @@ impl Display for DisplayParseError { row = location.line, column = location.column, colon = ":".cyan(), - inner = &DisplayParseErrorType(&self.error.error) + inner = self.error.error ) } ErrorLocation::Cell(cell, location) => { @@ -259,27 +259,13 @@ impl Display for DisplayParseError { row = location.line, column = location.column, colon = ":".cyan(), - inner = &DisplayParseErrorType(&self.error.error) + inner = self.error.error ) } } } } -pub(crate) struct DisplayParseErrorType<'a>(&'a ParseErrorType); - -impl<'a> DisplayParseErrorType<'a> { - pub(crate) fn new(error: &'a ParseErrorType) -> Self { - Self(error) - } -} - -impl Display for DisplayParseErrorType<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", TruncateAtNewline(&self.0)) - } -} - #[derive(Debug)] enum ErrorLocation { /// The error occurred in a Python file. @@ -288,44 +274,6 @@ enum ErrorLocation { Cell(OneIndexed, LineColumn), } -/// Truncates the display text before the first newline character to avoid line breaks. -struct TruncateAtNewline<'a>(&'a dyn Display); - -impl Display for TruncateAtNewline<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - struct TruncateAdapter<'a> { - inner: &'a mut dyn Write, - after_new_line: bool, - } - - impl Write for TruncateAdapter<'_> { - fn write_str(&mut self, s: &str) -> std::fmt::Result { - if self.after_new_line { - Ok(()) - } else { - if let Some(end) = s.find(['\n', '\r']) { - self.inner.write_str(&s[..end])?; - self.inner.write_str("\u{23ce}...")?; - self.after_new_line = true; - Ok(()) - } else { - self.inner.write_str(s) - } - } - } - } - - write!( - TruncateAdapter { - inner: f, - after_new_line: false, - }, - "{}", - self.0 - ) - } -} - #[cfg(test)] mod tests { use crate::logging::LogLevel; diff --git a/crates/ruff_linter/src/message/azure.rs b/crates/ruff_linter/src/message/azure.rs index 20f5d74d4c..96433b61ef 100644 --- a/crates/ruff_linter/src/message/azure.rs +++ b/crates/ruff_linter/src/message/azure.rs @@ -1,8 +1,9 @@ use std::io::Write; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::LineColumn; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; /// Generate error logging commands for Azure Pipelines format. /// See [documentation](https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#logissue-log-an-error-or-warning) @@ -13,23 +14,23 @@ impl Emitter for AzureEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for diagnostic in diagnostics { - let location = if context.is_notebook(&diagnostic.filename()) { + let filename = diagnostic.expect_ruff_filename(); + let location = if context.is_notebook(&filename) { // We can't give a reasonable location for the structured formats, // so we show one that's clearly a fallback LineColumn::default() } else { - diagnostic.compute_start_location() + diagnostic.expect_ruff_start_location() }; writeln!( writer, "##vso[task.logissue type=error\ ;sourcepath={filename};linenumber={line};columnnumber={col};{code}]{body}", - filename = diagnostic.filename(), line = location.line, col = location.column, code = diagnostic diff --git a/crates/ruff_linter/src/message/diff.rs b/crates/ruff_linter/src/message/diff.rs index 41b6e114e0..289a4beffe 100644 --- a/crates/ruff_linter/src/message/diff.rs +++ b/crates/ruff_linter/src/message/diff.rs @@ -2,13 +2,12 @@ use std::fmt::{Display, Formatter}; use std::num::NonZeroUsize; use colored::{Color, ColoredString, Colorize, Styles}; - -use ruff_text_size::{Ranged, TextRange, TextSize}; use similar::{ChangeTag, TextDiff}; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::{OneIndexed, SourceFile}; +use ruff_text_size::{Ranged, TextRange, TextSize}; -use crate::message::OldDiagnostic; use crate::text_helpers::ShowNonprinting; use crate::{Applicability, Fix}; @@ -26,9 +25,9 @@ pub(super) struct Diff<'a> { } impl<'a> Diff<'a> { - pub(crate) fn from_message(message: &'a OldDiagnostic) -> Option> { + pub(crate) fn from_message(message: &'a Diagnostic) -> Option> { message.fix().map(|fix| Diff { - source_code: message.source_file(), + source_code: message.expect_ruff_source_file(), fix, }) } diff --git a/crates/ruff_linter/src/message/github.rs b/crates/ruff_linter/src/message/github.rs index e54dbb81ce..37a384d75e 100644 --- a/crates/ruff_linter/src/message/github.rs +++ b/crates/ruff_linter/src/message/github.rs @@ -1,9 +1,10 @@ use std::io::Write; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::LineColumn; use crate::fs::relativize_path; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; /// Generate error workflow command in GitHub Actions format. /// See: [GitHub documentation](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message) @@ -14,12 +15,13 @@ impl Emitter for GithubEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for diagnostic in diagnostics { - let source_location = diagnostic.compute_start_location(); - let location = if context.is_notebook(&diagnostic.filename()) { + let source_location = diagnostic.expect_ruff_start_location(); + let filename = diagnostic.expect_ruff_filename(); + let location = if context.is_notebook(&filename) { // We can't give a reasonable location for the structured formats, // so we show one that's clearly a fallback LineColumn::default() @@ -27,7 +29,7 @@ impl Emitter for GithubEmitter { source_location }; - let end_location = diagnostic.compute_end_location(); + let end_location = diagnostic.expect_ruff_end_location(); write!( writer, @@ -35,7 +37,7 @@ impl Emitter for GithubEmitter { code = diagnostic .secondary_code() .map_or_else(String::new, |code| format!(" ({code})")), - file = diagnostic.filename(), + file = filename, row = source_location.line, column = source_location.column, end_row = end_location.line, @@ -45,7 +47,7 @@ impl Emitter for GithubEmitter { write!( writer, "{path}:{row}:{column}:", - path = relativize_path(&*diagnostic.filename()), + path = relativize_path(&filename), row = location.line, column = location.column, )?; diff --git a/crates/ruff_linter/src/message/gitlab.rs b/crates/ruff_linter/src/message/gitlab.rs index 0eb77e2ed2..1fe27051b1 100644 --- a/crates/ruff_linter/src/message/gitlab.rs +++ b/crates/ruff_linter/src/message/gitlab.rs @@ -7,8 +7,10 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use serde_json::json; +use ruff_db::diagnostic::Diagnostic; + use crate::fs::{relativize_path, relativize_path_to}; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; /// Generate JSON with violations in GitLab CI format // https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-custom-tool @@ -28,7 +30,7 @@ impl Emitter for GitlabEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { serde_json::to_writer_pretty( @@ -45,7 +47,7 @@ impl Emitter for GitlabEmitter { } struct SerializedMessages<'a> { - diagnostics: &'a [OldDiagnostic], + diagnostics: &'a [Diagnostic], context: &'a EmitterContext<'a>, project_dir: Option<&'a str>, } @@ -59,10 +61,11 @@ impl Serialize for SerializedMessages<'_> { let mut fingerprints = HashSet::::with_capacity(self.diagnostics.len()); for diagnostic in self.diagnostics { - let start_location = diagnostic.compute_start_location(); - let end_location = diagnostic.compute_end_location(); + let start_location = diagnostic.expect_ruff_start_location(); + let end_location = diagnostic.expect_ruff_end_location(); - let lines = if self.context.is_notebook(&diagnostic.filename()) { + let filename = diagnostic.expect_ruff_filename(); + let lines = if self.context.is_notebook(&filename) { // We can't give a reasonable location for the structured formats, // so we show one that's clearly a fallback json!({ @@ -77,8 +80,8 @@ impl Serialize for SerializedMessages<'_> { }; let path = self.project_dir.as_ref().map_or_else( - || relativize_path(&*diagnostic.filename()), - |project_dir| relativize_path_to(&*diagnostic.filename(), project_dir), + || relativize_path(&filename), + |project_dir| relativize_path_to(&filename, project_dir), ); let mut message_fingerprint = fingerprint(diagnostic, &path, 0); @@ -120,7 +123,7 @@ impl Serialize for SerializedMessages<'_> { } /// Generate a unique fingerprint to identify a violation. -fn fingerprint(message: &OldDiagnostic, project_path: &str, salt: u64) -> u64 { +fn fingerprint(message: &Diagnostic, project_path: &str, salt: u64) -> u64 { let mut hasher = DefaultHasher::new(); salt.hash(&mut hasher); diff --git a/crates/ruff_linter/src/message/grouped.rs b/crates/ruff_linter/src/message/grouped.rs index 56d93b3417..7e75134891 100644 --- a/crates/ruff_linter/src/message/grouped.rs +++ b/crates/ruff_linter/src/message/grouped.rs @@ -4,15 +4,14 @@ use std::num::NonZeroUsize; use colored::Colorize; +use ruff_db::diagnostic::Diagnostic; use ruff_notebook::NotebookIndex; use ruff_source_file::OneIndexed; use crate::fs::relativize_path; use crate::message::diff::calculate_print_width; use crate::message::text::{MessageCodeFrame, RuleCodeAndBody}; -use crate::message::{ - Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename, -}; +use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename}; use crate::settings::types::UnsafeFixes; #[derive(Default)] @@ -46,7 +45,7 @@ impl Emitter for GroupedEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for (filename, messages) in group_diagnostics_by_filename(diagnostics) { @@ -73,7 +72,7 @@ impl Emitter for GroupedEmitter { writer, "{}", DisplayGroupedMessage { - notebook_index: context.notebook_index(&message.filename()), + notebook_index: context.notebook_index(&message.expect_ruff_filename()), message, show_fix_status: self.show_fix_status, unsafe_fixes: self.unsafe_fixes, diff --git a/crates/ruff_linter/src/message/json.rs b/crates/ruff_linter/src/message/json.rs index 88b646e68b..8fd2a20003 100644 --- a/crates/ruff_linter/src/message/json.rs +++ b/crates/ruff_linter/src/message/json.rs @@ -4,12 +4,13 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use serde_json::{Value, json}; +use ruff_db::diagnostic::Diagnostic; use ruff_notebook::NotebookIndex; use ruff_source_file::{LineColumn, OneIndexed, SourceCode}; use ruff_text_size::Ranged; use crate::Edit; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; #[derive(Default)] pub struct JsonEmitter; @@ -18,7 +19,7 @@ impl Emitter for JsonEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { serde_json::to_writer_pretty( @@ -34,7 +35,7 @@ impl Emitter for JsonEmitter { } struct ExpandedMessages<'a> { - diagnostics: &'a [OldDiagnostic], + diagnostics: &'a [Diagnostic], context: &'a EmitterContext<'a>, } @@ -54,10 +55,11 @@ impl Serialize for ExpandedMessages<'_> { } } -pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterContext) -> Value { - let source_file = message.source_file(); +pub(crate) fn message_to_json_value(message: &Diagnostic, context: &EmitterContext) -> Value { + let source_file = message.expect_ruff_source_file(); let source_code = source_file.to_source_code(); - let notebook_index = context.notebook_index(&message.filename()); + let filename = message.expect_ruff_filename(); + let notebook_index = context.notebook_index(&filename); let fix = message.fix().map(|fix| { json!({ @@ -67,8 +69,8 @@ pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterCo }) }); - let mut start_location = source_code.line_column(message.start()); - let mut end_location = source_code.line_column(message.end()); + let mut start_location = source_code.line_column(message.expect_range().start()); + let mut end_location = source_code.line_column(message.expect_range().end()); let mut noqa_location = message .noqa_offset() .map(|offset| source_code.line_column(offset)); @@ -94,7 +96,7 @@ pub(crate) fn message_to_json_value(message: &OldDiagnostic, context: &EmitterCo "cell": notebook_cell_index, "location": location_to_json(start_location), "end_location": location_to_json(end_location), - "filename": message.filename(), + "filename": filename, "noqa_row": noqa_location.map(|location| location.line) }) } diff --git a/crates/ruff_linter/src/message/json_lines.rs b/crates/ruff_linter/src/message/json_lines.rs index c0591c54f9..fc9bd8625a 100644 --- a/crates/ruff_linter/src/message/json_lines.rs +++ b/crates/ruff_linter/src/message/json_lines.rs @@ -1,7 +1,9 @@ use std::io::Write; +use ruff_db::diagnostic::Diagnostic; + use crate::message::json::message_to_json_value; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; #[derive(Default)] pub struct JsonLinesEmitter; @@ -10,7 +12,7 @@ impl Emitter for JsonLinesEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for diagnostic in diagnostics { diff --git a/crates/ruff_linter/src/message/junit.rs b/crates/ruff_linter/src/message/junit.rs index a9d99f98b6..bec60249d0 100644 --- a/crates/ruff_linter/src/message/junit.rs +++ b/crates/ruff_linter/src/message/junit.rs @@ -3,11 +3,10 @@ use std::path::Path; use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite, XmlString}; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::LineColumn; -use crate::message::{ - Emitter, EmitterContext, MessageWithLocation, OldDiagnostic, group_diagnostics_by_filename, -}; +use crate::message::{Emitter, EmitterContext, MessageWithLocation, group_diagnostics_by_filename}; #[derive(Default)] pub struct JunitEmitter; @@ -16,7 +15,7 @@ impl Emitter for JunitEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { let mut report = Report::new("ruff"); @@ -44,7 +43,7 @@ impl Emitter for JunitEmitter { } = message; let mut status = TestCaseStatus::non_success(NonSuccessKind::Failure); status.set_message(message.body()); - let location = if context.is_notebook(&message.filename()) { + let location = if context.is_notebook(&message.expect_ruff_filename()) { // We can't give a reasonable location for the structured formats, // so we show one that's clearly a fallback LineColumn::default() diff --git a/crates/ruff_linter/src/message/mod.rs b/crates/ruff_linter/src/message/mod.rs index 0ec82d6d55..85897a8a3d 100644 --- a/crates/ruff_linter/src/message/mod.rs +++ b/crates/ruff_linter/src/message/mod.rs @@ -1,11 +1,11 @@ -use std::cmp::Ordering; use std::collections::BTreeMap; use std::fmt::Display; use std::io::Write; use std::ops::Deref; -use ruff_db::diagnostic::{self as db, Annotation, DiagnosticId, LintName, Severity, Span}; -use ruff_python_parser::semantic_errors::SemanticSyntaxError; +use ruff_db::diagnostic::{ + Annotation, Diagnostic, DiagnosticId, LintName, SecondaryCode, Severity, Span, +}; use rustc_hash::FxHashMap; pub use azure::AzureEmitter; @@ -18,17 +18,14 @@ pub use junit::JunitEmitter; pub use pylint::PylintEmitter; pub use rdjson::RdjsonEmitter; use ruff_notebook::NotebookIndex; -use ruff_python_parser::{ParseError, UnsupportedSyntaxError}; use ruff_source_file::{LineColumn, SourceFile}; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use ruff_text_size::{Ranged, TextRange, TextSize}; pub use sarif::SarifEmitter; pub use text::TextEmitter; use crate::Fix; -use crate::codes::NoqaCode; -use crate::logging::DisplayParseErrorType; +use crate::Violation; use crate::registry::Rule; -use crate::{Locator, Violation}; mod azure; mod diff; @@ -43,292 +40,103 @@ mod rdjson; mod sarif; mod text; -/// `OldDiagnostic` represents either a diagnostic message corresponding to a rule violation or a -/// syntax error message. +/// Creates a `Diagnostic` from a syntax error, with the format expected by Ruff. /// -/// All of the information for syntax errors is captured in the underlying [`db::Diagnostic`], while -/// rule violations can have the additional optional fields like fixes, suggestions, and (parent) -/// `noqa` offsets. +/// This is almost identical to `ruff_db::diagnostic::create_syntax_error_diagnostic`, except the +/// `message` is stored as the primary diagnostic message instead of on the primary annotation, and +/// `SyntaxError: ` is prepended to the message. /// -/// For diagnostic messages, the [`db::Diagnostic`]'s primary message contains the -/// [`OldDiagnostic::body`], and the primary annotation optionally contains the suggestion -/// accompanying a fix. The `db::Diagnostic::id` field contains the kebab-case lint name derived -/// from the `Rule`. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct OldDiagnostic { - pub diagnostic: db::Diagnostic, - - // these fields are specific to rule violations - pub fix: Option, - pub parent: Option, - pub(crate) noqa_offset: Option, - pub(crate) secondary_code: Option, +/// TODO(brent) These should be unified at some point, but we keep them separate for now to avoid a +/// ton of snapshot changes while combining ruff's diagnostic type with `Diagnostic`. +pub fn create_syntax_error_diagnostic( + span: impl Into, + message: impl std::fmt::Display, + range: impl Ranged, +) -> Diagnostic { + let mut diag = Diagnostic::new( + DiagnosticId::InvalidSyntax, + Severity::Error, + format_args!("SyntaxError: {message}"), + ); + let span = span.into().with_range(range.range()); + diag.annotate(Annotation::primary(span)); + diag } -impl OldDiagnostic { - pub fn syntax_error( - message: impl Display, - range: TextRange, - file: SourceFile, - ) -> OldDiagnostic { - let mut diag = db::Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message); - let span = Span::from(file).with_range(range); - diag.annotate(Annotation::primary(span)); - Self { - diagnostic: diag, - fix: None, - parent: None, - noqa_offset: None, - secondary_code: None, - } +#[expect(clippy::too_many_arguments)] +pub fn create_lint_diagnostic( + body: B, + suggestion: Option, + range: TextRange, + fix: Option, + parent: Option, + file: SourceFile, + noqa_offset: Option, + rule: Rule, +) -> Diagnostic +where + B: Display, + S: Display, +{ + let mut diagnostic = Diagnostic::new( + DiagnosticId::Lint(LintName::of(rule.into())), + Severity::Error, + body, + ); + + if let Some(fix) = fix { + diagnostic.set_fix(fix); } - #[expect(clippy::too_many_arguments)] - pub fn lint( - body: B, - suggestion: Option, - range: TextRange, - fix: Option, - parent: Option, - file: SourceFile, - noqa_offset: Option, - rule: Rule, - ) -> OldDiagnostic - where - B: Display, - S: Display, - { - let mut diagnostic = db::Diagnostic::new( - DiagnosticId::Lint(LintName::of(rule.into())), - Severity::Error, - body, - ); - let span = Span::from(file).with_range(range); - let mut annotation = Annotation::primary(span); - if let Some(suggestion) = suggestion { - annotation = annotation.message(suggestion); - } - diagnostic.annotate(annotation); - - OldDiagnostic { - diagnostic, - fix, - parent, - noqa_offset, - secondary_code: Some(SecondaryCode(rule.noqa_code().to_string())), - } + if let Some(parent) = parent { + diagnostic.set_parent(parent); } - /// Create an [`OldDiagnostic`] from the given [`ParseError`]. - pub fn from_parse_error( - parse_error: &ParseError, - locator: &Locator, - file: SourceFile, - ) -> OldDiagnostic { - // Try to create a non-empty range so that the diagnostic can print a caret at the right - // position. This requires that we retrieve the next character, if any, and take its length - // to maintain char-boundaries. - let len = locator - .after(parse_error.location.start()) - .chars() - .next() - .map_or(TextSize::new(0), TextLen::text_len); - - OldDiagnostic::syntax_error( - format_args!( - "SyntaxError: {}", - DisplayParseErrorType::new(&parse_error.error) - ), - TextRange::at(parse_error.location.start(), len), - file, - ) + if let Some(noqa_offset) = noqa_offset { + diagnostic.set_noqa_offset(noqa_offset); } - /// Create an [`OldDiagnostic`] from the given [`UnsupportedSyntaxError`]. - pub fn from_unsupported_syntax_error( - unsupported_syntax_error: &UnsupportedSyntaxError, - file: SourceFile, - ) -> OldDiagnostic { - OldDiagnostic::syntax_error( - format_args!("SyntaxError: {unsupported_syntax_error}"), - unsupported_syntax_error.range, - file, - ) + let span = Span::from(file).with_range(range); + let mut annotation = Annotation::primary(span); + if let Some(suggestion) = suggestion { + annotation = annotation.message(suggestion); } + diagnostic.annotate(annotation); - /// Create an [`OldDiagnostic`] from the given [`SemanticSyntaxError`]. - pub fn from_semantic_syntax_error( - semantic_syntax_error: &SemanticSyntaxError, - file: SourceFile, - ) -> OldDiagnostic { - OldDiagnostic::syntax_error( - format_args!("SyntaxError: {semantic_syntax_error}"), - semantic_syntax_error.range, - file, - ) - } + diagnostic.set_secondary_code(SecondaryCode::new(rule.noqa_code().to_string())); - // TODO(brent) We temporarily allow this to avoid updating all of the call sites to add - // references. I expect this method to go away or change significantly with the rest of the - // diagnostic refactor, but if it still exists in this form at the end of the refactor, we - // should just update the call sites. - #[expect(clippy::needless_pass_by_value)] - pub fn new(kind: T, range: TextRange, file: &SourceFile) -> Self { - Self::lint( - Violation::message(&kind), - Violation::fix_title(&kind), - range, - None, - None, - file.clone(), - None, - T::rule(), - ) - } - - /// Consumes `self` and returns a new `Diagnostic` with the given parent node. - #[inline] - #[must_use] - pub fn with_parent(mut self, parent: TextSize) -> Self { - self.set_parent(parent); - self - } - - /// Set the location of the diagnostic's parent node. - #[inline] - pub fn set_parent(&mut self, parent: TextSize) { - self.parent = Some(parent); - } - - /// Consumes `self` and returns a new `Diagnostic` with the given noqa offset. - #[inline] - #[must_use] - pub fn with_noqa_offset(mut self, noqa_offset: TextSize) -> Self { - self.noqa_offset = Some(noqa_offset); - self - } - - /// Returns `true` if `self` is a syntax error message. - pub fn is_syntax_error(&self) -> bool { - self.diagnostic.id().is_invalid_syntax() - } - - /// Returns the name used to represent the diagnostic. - pub fn name(&self) -> &'static str { - if self.is_syntax_error() { - "syntax-error" - } else { - self.diagnostic.id().as_str() - } - } - - /// Returns the message body to display to the user. - pub fn body(&self) -> &str { - self.diagnostic.primary_message() - } - - /// Returns the fix suggestion for the violation. - pub fn suggestion(&self) -> Option<&str> { - self.diagnostic.primary_annotation()?.get_message() - } - - /// Returns the offset at which the `noqa` comment will be placed if it's a diagnostic message. - pub fn noqa_offset(&self) -> Option { - self.noqa_offset - } - - /// Returns the [`Fix`] for the diagnostic, if there is any. - pub fn fix(&self) -> Option<&Fix> { - self.fix.as_ref() - } - - /// Returns `true` if the diagnostic contains a [`Fix`]. - pub fn fixable(&self) -> bool { - self.fix().is_some() - } - - /// Returns the noqa code for the diagnostic message as a string. - pub fn secondary_code(&self) -> Option<&SecondaryCode> { - self.secondary_code.as_ref() - } - - /// Returns the URL for the rule documentation, if it exists. - pub fn to_url(&self) -> Option { - if self.is_syntax_error() { - None - } else { - Some(format!( - "{}/rules/{}", - env!("CARGO_PKG_HOMEPAGE"), - self.name() - )) - } - } - - /// Returns the filename for the message. - pub fn filename(&self) -> String { - self.diagnostic - .expect_primary_span() - .expect_ruff_file() - .name() - .to_string() - } - - /// Computes the start source location for the message. - pub fn compute_start_location(&self) -> LineColumn { - self.diagnostic - .expect_primary_span() - .expect_ruff_file() - .to_source_code() - .line_column(self.start()) - } - - /// Computes the end source location for the message. - pub fn compute_end_location(&self) -> LineColumn { - self.diagnostic - .expect_primary_span() - .expect_ruff_file() - .to_source_code() - .line_column(self.end()) - } - - /// Returns the [`SourceFile`] which the message belongs to. - pub fn source_file(&self) -> SourceFile { - self.diagnostic - .expect_primary_span() - .expect_ruff_file() - .clone() - } + diagnostic } -impl Ord for OldDiagnostic { - fn cmp(&self, other: &Self) -> Ordering { - (self.source_file(), self.start()).cmp(&(other.source_file(), other.start())) - } -} - -impl PartialOrd for OldDiagnostic { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ranged for OldDiagnostic { - fn range(&self) -> TextRange { - self.diagnostic - .expect_primary_span() - .range() - .expect("Expected range for ruff span") - } +// TODO(brent) We temporarily allow this to avoid updating all of the call sites to add +// references. I expect this method to go away or change significantly with the rest of the +// diagnostic refactor, but if it still exists in this form at the end of the refactor, we +// should just update the call sites. +#[expect(clippy::needless_pass_by_value)] +pub fn diagnostic_from_violation( + kind: T, + range: TextRange, + file: &SourceFile, +) -> Diagnostic { + create_lint_diagnostic( + Violation::message(&kind), + Violation::fix_title(&kind), + range, + None, + None, + file.clone(), + None, + T::rule(), + ) } struct MessageWithLocation<'a> { - message: &'a OldDiagnostic, + message: &'a Diagnostic, start_location: LineColumn, } impl Deref for MessageWithLocation<'_> { - type Target = OldDiagnostic; + type Target = Diagnostic; fn deref(&self) -> &Self::Target { self.message @@ -336,30 +144,30 @@ impl Deref for MessageWithLocation<'_> { } fn group_diagnostics_by_filename( - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], ) -> BTreeMap> { let mut grouped_messages = BTreeMap::default(); for diagnostic in diagnostics { grouped_messages - .entry(diagnostic.filename().to_string()) + .entry(diagnostic.expect_ruff_filename()) .or_insert_with(Vec::new) .push(MessageWithLocation { message: diagnostic, - start_location: diagnostic.compute_start_location(), + start_location: diagnostic.expect_ruff_start_location(), }); } grouped_messages } -/// Display format for [`OldDiagnostic`]s. +/// Display format for [`Diagnostic`]s. /// -/// The emitter serializes a slice of [`OldDiagnostic`]s and writes them to a [`Write`]. +/// The emitter serializes a slice of [`Diagnostic`]s and writes them to a [`Write`]. pub trait Emitter { /// Serializes the `diagnostics` and writes the output to `writer`. fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()>; } @@ -384,101 +192,40 @@ impl<'a> EmitterContext<'a> { } } -/// A secondary identifier for a lint diagnostic. -/// -/// For Ruff rules this means the noqa code. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Hash, serde::Serialize)] -#[serde(transparent)] -pub struct SecondaryCode(String); - -impl SecondaryCode { - pub fn new(code: String) -> Self { - Self(code) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl Display for SecondaryCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl std::ops::Deref for SecondaryCode { - type Target = str; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl PartialEq<&str> for SecondaryCode { - fn eq(&self, other: &&str) -> bool { - self.0 == *other - } -} - -impl PartialEq for &str { - fn eq(&self, other: &SecondaryCode) -> bool { - other.eq(self) - } -} - -impl PartialEq for SecondaryCode { - fn eq(&self, other: &NoqaCode) -> bool { - &self.as_str() == other - } -} - -impl PartialEq for NoqaCode { - fn eq(&self, other: &SecondaryCode) -> bool { - other.eq(self) - } -} - -// for `hashbrown::EntryRef` -impl From<&SecondaryCode> for SecondaryCode { - fn from(value: &SecondaryCode) -> Self { - value.clone() - } -} - #[cfg(test)] mod tests { use rustc_hash::FxHashMap; - use crate::codes::Rule; - use crate::{Edit, Fix}; + use ruff_db::diagnostic::Diagnostic; use ruff_notebook::NotebookIndex; use ruff_python_parser::{Mode, ParseOptions, parse_unchecked}; use ruff_source_file::{OneIndexed, SourceFileBuilder}; use ruff_text_size::{TextRange, TextSize}; - use crate::Locator; - use crate::message::{Emitter, EmitterContext, OldDiagnostic}; + use crate::codes::Rule; + use crate::message::{Emitter, EmitterContext, create_lint_diagnostic}; + use crate::{Edit, Fix}; - pub(super) fn create_syntax_error_diagnostics() -> Vec { + use super::create_syntax_error_diagnostic; + + pub(super) fn create_syntax_error_diagnostics() -> Vec { let source = r"from os import if call(foo def bar(): pass "; - let locator = Locator::new(source); let source_file = SourceFileBuilder::new("syntax_errors.py", source).finish(); parse_unchecked(source, ParseOptions::from(Mode::Module)) .errors() .iter() .map(|parse_error| { - OldDiagnostic::from_parse_error(parse_error, &locator, source_file.clone()) + create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error) }) .collect() } - pub(super) fn create_diagnostics() -> Vec { + pub(super) fn create_diagnostics() -> Vec { let fib = r#"import os @@ -496,7 +243,7 @@ def fibonacci(n): let fib_source = SourceFileBuilder::new("fib.py", fib).finish(); let unused_import_start = TextSize::from(7); - let unused_import = OldDiagnostic::lint( + let unused_import = create_lint_diagnostic( "`os` imported but unused", Some("Remove unused import: `os`"), TextRange::new(unused_import_start, TextSize::from(9)), @@ -511,7 +258,7 @@ def fibonacci(n): ); let unused_variable_start = TextSize::from(94); - let unused_variable = OldDiagnostic::lint( + let unused_variable = create_lint_diagnostic( "Local variable `x` is assigned to but never used", Some("Remove assignment to unused variable `x`"), TextRange::new(unused_variable_start, TextSize::from(95)), @@ -528,7 +275,7 @@ def fibonacci(n): let file_2 = r"if a == 1: pass"; let undefined_name_start = TextSize::from(3); - let undefined_name = OldDiagnostic::lint( + let undefined_name = create_lint_diagnostic( "Undefined name `a`", Option::<&'static str>::None, TextRange::new(undefined_name_start, TextSize::from(4)), @@ -543,7 +290,7 @@ def fibonacci(n): } pub(super) fn create_notebook_diagnostics() - -> (Vec, FxHashMap) { + -> (Vec, FxHashMap) { let notebook = r"# cell 1 import os # cell 2 @@ -559,7 +306,7 @@ def foo(): let notebook_source = SourceFileBuilder::new("notebook.ipynb", notebook).finish(); let unused_import_os_start = TextSize::from(16); - let unused_import_os = OldDiagnostic::lint( + let unused_import_os = create_lint_diagnostic( "`os` imported but unused", Some("Remove unused import: `os`"), TextRange::new(unused_import_os_start, TextSize::from(18)), @@ -574,7 +321,7 @@ def foo(): ); let unused_import_math_start = TextSize::from(35); - let unused_import_math = OldDiagnostic::lint( + let unused_import_math = create_lint_diagnostic( "`math` imported but unused", Some("Remove unused import: `math`"), TextRange::new(unused_import_math_start, TextSize::from(39)), @@ -589,7 +336,7 @@ def foo(): ); let unused_variable_start = TextSize::from(98); - let unused_variable = OldDiagnostic::lint( + let unused_variable = create_lint_diagnostic( "Local variable `x` is assigned to but never used", Some("Remove assignment to unused variable `x`"), TextRange::new(unused_variable_start, TextSize::from(99)), @@ -642,7 +389,7 @@ def foo(): pub(super) fn capture_emitter_output( emitter: &mut dyn Emitter, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], ) -> String { let notebook_indexes = FxHashMap::default(); let context = EmitterContext::new(¬ebook_indexes); @@ -654,7 +401,7 @@ def foo(): pub(super) fn capture_emitter_notebook_output( emitter: &mut dyn Emitter, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], notebook_indexes: &FxHashMap, ) -> String { let context = EmitterContext::new(notebook_indexes); diff --git a/crates/ruff_linter/src/message/pylint.rs b/crates/ruff_linter/src/message/pylint.rs index e6d37f90cc..5e6cbe5a6a 100644 --- a/crates/ruff_linter/src/message/pylint.rs +++ b/crates/ruff_linter/src/message/pylint.rs @@ -1,9 +1,10 @@ use std::io::Write; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::OneIndexed; use crate::fs::relativize_path; -use crate::message::{Emitter, EmitterContext, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext}; /// Generate violations in Pylint format. /// See: [Flake8 documentation](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter) @@ -14,16 +15,17 @@ impl Emitter for PylintEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for diagnostic in diagnostics { - let row = if context.is_notebook(&diagnostic.filename()) { + let filename = diagnostic.expect_ruff_filename(); + let row = if context.is_notebook(&filename) { // We can't give a reasonable location for the structured formats, // so we show one that's clearly a fallback OneIndexed::from_zero_indexed(0) } else { - diagnostic.compute_start_location().line + diagnostic.expect_ruff_start_location().line }; let body = if let Some(code) = diagnostic.secondary_code() { @@ -35,7 +37,7 @@ impl Emitter for PylintEmitter { writeln!( writer, "{path}:{row}: {body}", - path = relativize_path(&*diagnostic.filename()), + path = relativize_path(&filename), )?; } diff --git a/crates/ruff_linter/src/message/rdjson.rs b/crates/ruff_linter/src/message/rdjson.rs index 19bc2038b8..8be2c56a54 100644 --- a/crates/ruff_linter/src/message/rdjson.rs +++ b/crates/ruff_linter/src/message/rdjson.rs @@ -4,11 +4,12 @@ use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; use serde_json::{Value, json}; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::SourceCode; use ruff_text_size::Ranged; use crate::Edit; -use crate::message::{Emitter, EmitterContext, LineColumn, OldDiagnostic}; +use crate::message::{Emitter, EmitterContext, LineColumn}; #[derive(Default)] pub struct RdjsonEmitter; @@ -17,7 +18,7 @@ impl Emitter for RdjsonEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], _context: &EmitterContext, ) -> anyhow::Result<()> { serde_json::to_writer_pretty( @@ -37,7 +38,7 @@ impl Emitter for RdjsonEmitter { } struct ExpandedMessages<'a> { - diagnostics: &'a [OldDiagnostic], + diagnostics: &'a [Diagnostic], } impl Serialize for ExpandedMessages<'_> { @@ -56,18 +57,18 @@ impl Serialize for ExpandedMessages<'_> { } } -fn message_to_rdjson_value(message: &OldDiagnostic) -> Value { - let source_file = message.source_file(); +fn message_to_rdjson_value(message: &Diagnostic) -> Value { + let source_file = message.expect_ruff_source_file(); let source_code = source_file.to_source_code(); - let start_location = source_code.line_column(message.start()); - let end_location = source_code.line_column(message.end()); + let start_location = source_code.line_column(message.expect_range().start()); + let end_location = source_code.line_column(message.expect_range().end()); if let Some(fix) = message.fix() { json!({ "message": message.body(), "location": { - "path": message.filename(), + "path": message.expect_ruff_filename(), "range": rdjson_range(start_location, end_location), }, "code": { @@ -80,7 +81,7 @@ fn message_to_rdjson_value(message: &OldDiagnostic) -> Value { json!({ "message": message.body(), "location": { - "path": message.filename(), + "path": message.expect_ruff_filename(), "range": rdjson_range(start_location, end_location), }, "code": { diff --git a/crates/ruff_linter/src/message/sarif.rs b/crates/ruff_linter/src/message/sarif.rs index d2aac8f2c4..4b7b80e61c 100644 --- a/crates/ruff_linter/src/message/sarif.rs +++ b/crates/ruff_linter/src/message/sarif.rs @@ -5,11 +5,12 @@ use anyhow::Result; use serde::{Serialize, Serializer}; use serde_json::json; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; use ruff_source_file::OneIndexed; use crate::VERSION; use crate::fs::normalize_path; -use crate::message::{Emitter, EmitterContext, OldDiagnostic, SecondaryCode}; +use crate::message::{Emitter, EmitterContext}; use crate::registry::{Linter, RuleNamespace}; pub struct SarifEmitter; @@ -18,7 +19,7 @@ impl Emitter for SarifEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], _context: &EmitterContext, ) -> Result<()> { let results = diagnostics @@ -122,10 +123,10 @@ struct SarifResult<'a> { impl<'a> SarifResult<'a> { #[cfg(not(target_arch = "wasm32"))] - fn from_message(message: &'a OldDiagnostic) -> Result { - let start_location = message.compute_start_location(); - let end_location = message.compute_end_location(); - let path = normalize_path(&*message.filename()); + fn from_message(message: &'a Diagnostic) -> Result { + let start_location = message.expect_ruff_start_location(); + let end_location = message.expect_ruff_end_location(); + let path = normalize_path(&*message.expect_ruff_filename()); Ok(Self { code: message.secondary_code(), level: "error".to_string(), @@ -142,10 +143,10 @@ impl<'a> SarifResult<'a> { #[cfg(target_arch = "wasm32")] #[expect(clippy::unnecessary_wraps)] - fn from_message(message: &'a OldDiagnostic) -> Result { - let start_location = message.compute_start_location(); - let end_location = message.compute_end_location(); - let path = normalize_path(&*message.filename()); + fn from_message(message: &'a Diagnostic) -> Result { + let start_location = message.expect_ruff_start_location(); + let end_location = message.expect_ruff_end_location(); + let path = normalize_path(&*message.expect_ruff_filename()); Ok(Self { code: message.secondary_code(), level: "error".to_string(), diff --git a/crates/ruff_linter/src/message/text.rs b/crates/ruff_linter/src/message/text.rs index ad1242744f..0578fd7731 100644 --- a/crates/ruff_linter/src/message/text.rs +++ b/crates/ruff_linter/src/message/text.rs @@ -6,15 +6,16 @@ use bitflags::bitflags; use colored::Colorize; use ruff_annotate_snippets::{Level, Renderer, Snippet}; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; use ruff_notebook::NotebookIndex; use ruff_source_file::{LineColumn, OneIndexed}; -use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; +use ruff_text_size::{TextLen, TextRange, TextSize}; use crate::Locator; use crate::fs::relativize_path; use crate::line_width::{IndentWidth, LineWidthBuilder}; use crate::message::diff::Diff; -use crate::message::{Emitter, EmitterContext, OldDiagnostic, SecondaryCode}; +use crate::message::{Emitter, EmitterContext}; use crate::settings::types::UnsafeFixes; bitflags! { @@ -66,19 +67,20 @@ impl Emitter for TextEmitter { fn emit( &mut self, writer: &mut dyn Write, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], context: &EmitterContext, ) -> anyhow::Result<()> { for message in diagnostics { + let filename = message.expect_ruff_filename(); write!( writer, "{path}{sep}", - path = relativize_path(&*message.filename()).bold(), + path = relativize_path(&filename).bold(), sep = ":".cyan(), )?; - let start_location = message.compute_start_location(); - let notebook_index = context.notebook_index(&message.filename()); + let start_location = message.expect_ruff_start_location(); + let notebook_index = context.notebook_index(&filename); // Check if we're working on a jupyter notebook and translate positions with cell accordingly let diagnostic_location = if let Some(notebook_index) = notebook_index { @@ -116,7 +118,7 @@ impl Emitter for TextEmitter { if self.flags.intersects(EmitterFlags::SHOW_SOURCE) { // The `0..0` range is used to highlight file-level diagnostics. - if message.range() != TextRange::default() { + if message.expect_range() != TextRange::default() { writeln!( writer, "{}", @@ -140,7 +142,7 @@ impl Emitter for TextEmitter { } pub(super) struct RuleCodeAndBody<'a> { - pub(crate) message: &'a OldDiagnostic, + pub(crate) message: &'a Diagnostic, pub(crate) show_fix_status: bool, pub(crate) unsafe_fixes: UnsafeFixes, } @@ -178,7 +180,7 @@ impl Display for RuleCodeAndBody<'_> { } pub(super) struct MessageCodeFrame<'a> { - pub(crate) message: &'a OldDiagnostic, + pub(crate) message: &'a Diagnostic, pub(crate) notebook_index: Option<&'a NotebookIndex>, } @@ -191,10 +193,10 @@ impl Display for MessageCodeFrame<'_> { Vec::new() }; - let source_file = self.message.source_file(); + let source_file = self.message.expect_ruff_source_file(); let source_code = source_file.to_source_code(); - let content_start_index = source_code.line_index(self.message.start()); + let content_start_index = source_code.line_index(self.message.expect_range().start()); let mut start_index = content_start_index.saturating_sub(2); // If we're working with a Jupyter Notebook, skip the lines which are @@ -217,7 +219,7 @@ impl Display for MessageCodeFrame<'_> { start_index = start_index.saturating_add(1); } - let content_end_index = source_code.line_index(self.message.end()); + let content_end_index = source_code.line_index(self.message.expect_range().end()); let mut end_index = content_end_index .saturating_add(2) .min(OneIndexed::from_zero_indexed(source_code.line_count())); @@ -248,7 +250,7 @@ impl Display for MessageCodeFrame<'_> { let source = replace_whitespace_and_unprintable( source_code.slice(TextRange::new(start_offset, end_offset)), - self.message.range() - start_offset, + self.message.expect_range() - start_offset, ) .fix_up_empty_spans_after_line_terminator(); diff --git a/crates/ruff_linter/src/noqa.rs b/crates/ruff_linter/src/noqa.rs index 026715aca9..832aceb6ce 100644 --- a/crates/ruff_linter/src/noqa.rs +++ b/crates/ruff_linter/src/noqa.rs @@ -9,6 +9,7 @@ use anyhow::Result; use itertools::Itertools; use log::warn; +use ruff_db::diagnostic::{Diagnostic, SecondaryCode}; use ruff_python_trivia::{CommentRanges, Cursor, indentation_at_offset}; use ruff_source_file::{LineEnding, LineRanges}; use ruff_text_size::{Ranged, TextLen, TextRange, TextSize}; @@ -17,7 +18,6 @@ use rustc_hash::FxHashSet; use crate::Edit; use crate::Locator; use crate::fs::relativize_path; -use crate::message::{OldDiagnostic, SecondaryCode}; use crate::registry::Rule; use crate::rule_redirects::get_redirect_target; @@ -28,7 +28,7 @@ use crate::rule_redirects::get_redirect_target; /// simultaneously. pub fn generate_noqa_edits( path: &Path, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], locator: &Locator, comment_ranges: &CommentRanges, external: &[String], @@ -717,7 +717,7 @@ impl Error for LexicalError {} /// Adds noqa comments to suppress all messages of a file. pub(crate) fn add_noqa( path: &Path, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], locator: &Locator, comment_ranges: &CommentRanges, external: &[String], @@ -740,7 +740,7 @@ pub(crate) fn add_noqa( fn add_noqa_inner( path: &Path, - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], locator: &Locator, comment_ranges: &CommentRanges, external: &[String], @@ -845,7 +845,7 @@ struct NoqaComment<'a> { } fn find_noqa_comments<'a>( - diagnostics: &'a [OldDiagnostic], + diagnostics: &'a [Diagnostic], locator: &'a Locator, exemption: &'a FileExemption, directives: &'a NoqaDirectives, @@ -867,7 +867,7 @@ fn find_noqa_comments<'a>( } // Is the violation ignored by a `noqa` directive on the parent line? - if let Some(parent) = message.parent { + if let Some(parent) = message.parent() { if let Some(directive_line) = directives.find_line_with_directive(noqa_line_for.resolve(parent)) { @@ -886,7 +886,7 @@ fn find_noqa_comments<'a>( } } - let noqa_offset = noqa_line_for.resolve(message.range().start()); + let noqa_offset = noqa_line_for.resolve(message.expect_range().start()); // Or ignored by the directive itself? if let Some(directive_line) = directives.find_line_with_directive(noqa_offset) { @@ -1225,6 +1225,8 @@ mod tests { use ruff_source_file::{LineEnding, SourceFileBuilder}; use ruff_text_size::{TextLen, TextRange, TextSize}; + use crate::Edit; + use crate::message::diagnostic_from_violation; use crate::noqa::{ Directive, LexicalError, NoqaLexerOutput, NoqaMapping, add_noqa_inner, lex_codes, lex_file_exemption, lex_inline_noqa, @@ -1232,7 +1234,6 @@ mod tests { use crate::rules::pycodestyle::rules::{AmbiguousVariableName, UselessSemicolon}; use crate::rules::pyflakes::rules::UnusedVariable; use crate::rules::pyupgrade::rules::PrintfStringFormatting; - use crate::{Edit, OldDiagnostic}; use crate::{Locator, generate_noqa_edits}; fn assert_lexed_ranges_match_slices( @@ -2831,7 +2832,7 @@ mod tests { assert_eq!(output, format!("{contents}")); let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); - let messages = [OldDiagnostic::new( + let messages = [diagnostic_from_violation( UnusedVariable { name: "x".to_string(), }, @@ -2855,12 +2856,12 @@ mod tests { let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); let messages = [ - OldDiagnostic::new( + diagnostic_from_violation( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), &source_file, ), - OldDiagnostic::new( + diagnostic_from_violation( UnusedVariable { name: "x".to_string(), }, @@ -2886,12 +2887,12 @@ mod tests { let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish(); let messages = [ - OldDiagnostic::new( + diagnostic_from_violation( AmbiguousVariableName("x".to_string()), TextRange::new(TextSize::from(0), TextSize::from(0)), &source_file, ), - OldDiagnostic::new( + diagnostic_from_violation( UnusedVariable { name: "x".to_string(), }, @@ -2930,7 +2931,7 @@ print( "#; let noqa_line_for = [TextRange::new(8.into(), 68.into())].into_iter().collect(); let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish(); - let messages = [OldDiagnostic::new( + let messages = [diagnostic_from_violation( PrintfStringFormatting, TextRange::new(12.into(), 79.into()), &source_file, @@ -2963,7 +2964,7 @@ foo; bar = "; let source_file = SourceFileBuilder::new(path.to_string_lossy(), source).finish(); - let messages = [OldDiagnostic::new( + let messages = [diagnostic_from_violation( UselessSemicolon, TextRange::new(4.into(), 5.into()), &source_file, diff --git a/crates/ruff_linter/src/pyproject_toml.rs b/crates/ruff_linter/src/pyproject_toml.rs index 96d1af0937..a45c0b5036 100644 --- a/crates/ruff_linter/src/pyproject_toml.rs +++ b/crates/ruff_linter/src/pyproject_toml.rs @@ -3,19 +3,17 @@ use log::warn; use pyproject_toml::PyProjectToml; use ruff_text_size::{TextRange, TextSize}; +use ruff_db::diagnostic::Diagnostic; use ruff_source_file::SourceFile; use crate::IOError; -use crate::OldDiagnostic; +use crate::message::diagnostic_from_violation; use crate::registry::Rule; use crate::rules::ruff::rules::InvalidPyprojectToml; use crate::settings::LinterSettings; /// RUF200 -pub fn lint_pyproject_toml( - source_file: &SourceFile, - settings: &LinterSettings, -) -> Vec { +pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings) -> Vec { let Some(err) = toml::from_str::(source_file.source_text()).err() else { return Vec::default(); }; @@ -32,8 +30,11 @@ pub fn lint_pyproject_toml( source_file.name(), ); if settings.rules.enabled(Rule::IOError) { - let diagnostic = - OldDiagnostic::new(IOError { message }, TextRange::default(), source_file); + let diagnostic = diagnostic_from_violation( + IOError { message }, + TextRange::default(), + source_file, + ); messages.push(diagnostic); } else { warn!( @@ -55,7 +56,7 @@ pub fn lint_pyproject_toml( if settings.rules.enabled(Rule::InvalidPyprojectToml) { let toml_err = err.message().to_string(); - let diagnostic = OldDiagnostic::new( + let diagnostic = diagnostic_from_violation( InvalidPyprojectToml { message: toml_err }, range, source_file, diff --git a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs index 1d98e4e91c..74b5e3fb4e 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs +++ b/crates/ruff_linter/src/rules/flake8_commas/rules/trailing_commas.rs @@ -355,7 +355,7 @@ fn check_token( if let Some(mut diagnostic) = lint_context.report_diagnostic_if_enabled(ProhibitedTrailingComma, prev.range()) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); return; } diff --git a/crates/ruff_linter/src/rules/flake8_commas/snapshots/ruff_linter__rules__flake8_commas__tests__COM81_syntax_error.py.snap b/crates/ruff_linter/src/rules/flake8_commas/snapshots/ruff_linter__rules__flake8_commas__tests__COM81_syntax_error.py.snap index 580392a499..8105e635a6 100644 --- a/crates/ruff_linter/src/rules/flake8_commas/snapshots/ruff_linter__rules__flake8_commas__tests__COM81_syntax_error.py.snap +++ b/crates/ruff_linter/src/rules/flake8_commas/snapshots/ruff_linter__rules__flake8_commas__tests__COM81_syntax_error.py.snap @@ -6,7 +6,7 @@ COM81_syntax_error.py:3:5: SyntaxError: Starred expression cannot be used here 1 | # Check for `flake8-commas` violation for a file containing syntax errors. 2 | ( 3 | *args - | ^ + | ^^^^^ 4 | ) | diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC_syntax_error.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC_syntax_error.py.snap index a14e63a629..0dab8744b5 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC_syntax_error.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC001_ISC_syntax_error.py.snap @@ -5,7 +5,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal | 1 | # The lexer doesn't emit a string token if it's unterminated 2 | "a" "b - | ^ + | ^^ 3 | "a" "b" "c 4 | "a" """b | @@ -36,7 +36,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal 1 | # The lexer doesn't emit a string token if it's unterminated 2 | "a" "b 3 | "a" "b" "c - | ^ + | ^^ 4 | "a" """b 5 | c""" "d | @@ -68,7 +68,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal 3 | "a" "b" "c 4 | "a" """b 5 | c""" "d - | ^ + | ^^ 6 | 7 | # For f-strings, the `FStringRanges` won't contain the range for | @@ -153,19 +153,21 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal 14 | ( 15 | "a" 16 | "b - | ^ + | ^^ 17 | "c" 18 | "d" | ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string | -24 | ( -25 | """abc""" -26 | f"""def - | ^ -27 | "g" "h" -28 | "i" "j" +24 | ( +25 | """abc""" +26 | f"""def + | _________^ +27 | | "g" "h" +28 | | "i" "j" +29 | | ) + | |__^ | ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing diff --git a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC002_ISC_syntax_error.py.snap b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC002_ISC_syntax_error.py.snap index 46d99bae67..99f0f7d157 100644 --- a/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC002_ISC_syntax_error.py.snap +++ b/crates/ruff_linter/src/rules/flake8_implicit_str_concat/snapshots/ruff_linter__rules__flake8_implicit_str_concat__tests__ISC002_ISC_syntax_error.py.snap @@ -5,7 +5,7 @@ ISC_syntax_error.py:2:5: SyntaxError: missing closing quote in string literal | 1 | # The lexer doesn't emit a string token if it's unterminated 2 | "a" "b - | ^ + | ^^ 3 | "a" "b" "c 4 | "a" """b | @@ -25,7 +25,7 @@ ISC_syntax_error.py:3:9: SyntaxError: missing closing quote in string literal 1 | # The lexer doesn't emit a string token if it's unterminated 2 | "a" "b 3 | "a" "b" "c - | ^ + | ^^ 4 | "a" """b 5 | c""" "d | @@ -45,7 +45,7 @@ ISC_syntax_error.py:5:6: SyntaxError: missing closing quote in string literal 3 | "a" "b" "c 4 | "a" """b 5 | c""" "d - | ^ + | ^^ 6 | 7 | # For f-strings, the `FStringRanges` won't contain the range for | @@ -107,19 +107,21 @@ ISC_syntax_error.py:16:5: SyntaxError: missing closing quote in string literal 14 | ( 15 | "a" 16 | "b - | ^ + | ^^ 17 | "c" 18 | "d" | ISC_syntax_error.py:26:9: SyntaxError: f-string: unterminated triple-quoted string | -24 | ( -25 | """abc""" -26 | f"""def - | ^ -27 | "g" "h" -28 | "i" "j" +24 | ( +25 | """abc""" +26 | f"""def + | _________^ +27 | | "g" "h" +28 | | "i" "j" +29 | | ) + | |__^ | ISC_syntax_error.py:30:1: SyntaxError: unexpected EOF while parsing diff --git a/crates/ruff_linter/src/rules/isort/mod.rs b/crates/ruff_linter/src/rules/isort/mod.rs index c9ffc49a6b..5b822e825d 100644 --- a/crates/ruff_linter/src/rules/isort/mod.rs +++ b/crates/ruff_linter/src/rules/isort/mod.rs @@ -290,7 +290,6 @@ mod tests { use test_case::test_case; use ruff_python_semantic::{MemberNameImport, ModuleNameImport, NameImport}; - use ruff_text_size::Ranged; use crate::assert_diagnostics; use crate::registry::Rule; @@ -658,7 +657,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -686,7 +685,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -716,7 +715,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -744,7 +743,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -766,7 +765,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -786,7 +785,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -1130,7 +1129,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -1155,7 +1154,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -1177,7 +1176,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } @@ -1198,7 +1197,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(&*snapshot, diagnostics); Ok(()) } @@ -1217,7 +1216,7 @@ mod tests { ..LinterSettings::for_rule(Rule::UnsortedImports) }, )?; - diagnostics.sort_by_key(Ranged::start); + diagnostics.sort_by_key(|diagnostic| diagnostic.expect_range().start()); assert_diagnostics!(snapshot, diagnostics); Ok(()) } diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs index 40a447d216..2044af0ba5 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs @@ -168,7 +168,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { WhitespaceAfterOpenBracket { symbol }, TextRange::at(token.end(), trailing_len), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); } } @@ -182,7 +182,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { WhitespaceBeforeCloseBracket { symbol }, TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); } } @@ -210,7 +210,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic .set_fix(Fix::safe_edit(Edit::range_deletion(range))); } @@ -227,7 +227,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit( Edit::range_deletion(range), )); @@ -255,7 +255,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edits( Edit::range_deletion(range), [Edit::insertion( @@ -278,7 +278,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit( Edit::range_deletion(range), )); @@ -297,7 +297,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { WhitespaceBeforePunctuation { symbol }, TextRange::at(token.start() - offset, offset), ) { - let range = diagnostic.range(); + let range = diagnostic.expect_range(); diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(range))); } } diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E111_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E111_E11.py.snap index 7410af0c31..4b92dd6823 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E111_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E111_E11.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs -snapshot_kind: text --- E11.py:3:1: E111 Indentation is not a multiple of 4 | @@ -27,7 +26,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -37,7 +36,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -57,7 +56,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap index 032451e9ba..5ade4346e0 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap @@ -16,7 +16,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -26,7 +26,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -56,7 +56,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E113_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E113_E11.py.snap index 09150ce64f..533d2ee8d1 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E113_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E113_E11.py.snap @@ -1,13 +1,12 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs -snapshot_kind: text --- E11.py:9:1: SyntaxError: Expected an indented block after `if` statement | 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -27,7 +26,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -47,7 +46,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E114_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E114_E11.py.snap index 2d93426972..0ce72700a4 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E114_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E114_E11.py.snap @@ -1,13 +1,12 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs -snapshot_kind: text --- E11.py:9:1: SyntaxError: Expected an indented block after `if` statement | 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -17,7 +16,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -47,7 +46,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E115_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E115_E11.py.snap index e0ce2a827c..a8b570fb33 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E115_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E115_E11.py.snap @@ -6,7 +6,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -16,7 +16,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -96,7 +96,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E116_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E116_E11.py.snap index e968a2526d..01db13d269 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E116_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E116_E11.py.snap @@ -1,13 +1,12 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs -snapshot_kind: text --- E11.py:9:1: SyntaxError: Expected an indented block after `if` statement | 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -17,7 +16,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -77,7 +76,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E117_E11.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E117_E11.py.snap index 73be2664e6..65c7dd99c8 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E117_E11.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E117_E11.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pycodestyle/mod.rs -snapshot_kind: text --- E11.py:6:1: E117 Over-indented | @@ -17,7 +16,7 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement 7 | #: E112 8 | if False: 9 | print() - | ^ + | ^^^^^ 10 | #: E113 11 | print() | @@ -27,7 +26,7 @@ E11.py:12:1: SyntaxError: Unexpected indentation 10 | #: E113 11 | print() 12 | print() - | ^ + | ^^^^ 13 | #: E114 E116 14 | mimetype = 'application/x-directory' | @@ -67,7 +66,7 @@ E11.py:45:1: SyntaxError: Expected an indented block after `if` statement 43 | #: E112 44 | if False: # 45 | print() - | ^ + | ^^^^^ 46 | #: 47 | if False: | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E501_E501_4.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E501_E501_4.py.snap index 8af342273e06a2536fe80f1b4449c2effa2d15ee..4893167012931039fd3596ea068a84e4652bc5dc 100644 GIT binary patch delta 16 XcmaFo`rdUzEaT*fio%=o8S51RK|ltP delta 16 XcmaFw`p$JjEaT({VnUk>80!@QLu&@r diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 413e7dcff0..d78c8217c6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -11,6 +11,7 @@ mod tests { use anyhow::Result; use regex::Regex; + use ruff_db::diagnostic::Diagnostic; use ruff_python_parser::ParseOptions; use rustc_hash::FxHashMap; use test_case::test_case; @@ -19,7 +20,6 @@ mod tests { use ruff_python_codegen::Stylist; use ruff_python_index::Indexer; use ruff_python_trivia::textwrap::dedent; - use ruff_text_size::Ranged; use crate::linter::check_path; use crate::registry::{Linter, Rule}; @@ -29,7 +29,7 @@ mod tests { use crate::settings::{LinterSettings, flags}; use crate::source_kind::SourceKind; use crate::test::{test_contents, test_path, test_snippet}; - use crate::{Locator, OldDiagnostic, assert_diagnostics, directives}; + use crate::{Locator, assert_diagnostics, directives}; #[test_case(Rule::UnusedImport, Path::new("F401_0.py"))] #[test_case(Rule::UnusedImport, Path::new("F401_1.py"))] @@ -771,11 +771,11 @@ mod tests { &parsed, target_version, ); - messages.sort_by_key(Ranged::start); + messages.sort_by_key(|diagnostic| diagnostic.expect_range().start()); let actual = messages .iter() .filter(|msg| !msg.is_syntax_error()) - .map(OldDiagnostic::name) + .map(Diagnostic::name) .collect::>(); let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect(); assert_eq!(actual, expected); diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters_syntax_error.py.snap b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters_syntax_error.py.snap index 55449038ed..191bff373d 100644 --- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters_syntax_error.py.snap +++ b/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2510_invalid_characters_syntax_error.py.snap @@ -1,6 +1,5 @@ --- source: crates/ruff_linter/src/rules/pylint/mod.rs -snapshot_kind: text --- invalid_characters_syntax_error.py:5:6: PLE2510 Invalid unescaped character backspace, use "\b" instead | @@ -17,7 +16,7 @@ invalid_characters_syntax_error.py:7:5: SyntaxError: missing closing quote in st 5 | b = '␈' 6 | # Unterminated string 7 | b = '␈ - | ^ + | ^^ 8 | b = '␈' 9 | # Unterminated f-string | @@ -99,7 +98,7 @@ invalid_characters_syntax_error.py:13:14: SyntaxError: missing closing quote in 11 | b = f'␈' 12 | # Implicitly concatenated 13 | b = '␈' f'␈' '␈ - | ^ + | ^^ | invalid_characters_syntax_error.py:13:16: SyntaxError: Expected a statement diff --git a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs index d2335ac50e..390c6bb067 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/post_init_default.rs @@ -128,7 +128,7 @@ pub(crate) fn post_init_default(checker: &Checker, function_def: &ast::StmtFunct // Need to stop fixes as soon as there is a parameter we cannot fix. // Otherwise, we risk a syntax error (a parameter without a default // following parameter with a default). - stopped_fixes |= diagnostic.fix.is_none(); + stopped_fixes |= diagnostic.fix().is_none(); } } } diff --git a/crates/ruff_linter/src/test.rs b/crates/ruff_linter/src/test.rs index d72bea69c5..c34faaf413 100644 --- a/crates/ruff_linter/src/test.rs +++ b/crates/ruff_linter/src/test.rs @@ -7,9 +7,9 @@ use std::path::Path; #[cfg(not(fuzzing))] use anyhow::Result; use itertools::Itertools; -use ruff_text_size::Ranged; use rustc_hash::FxHashMap; +use ruff_db::diagnostic::Diagnostic; use ruff_notebook::Notebook; #[cfg(not(fuzzing))] use ruff_notebook::NotebookError; @@ -23,7 +23,7 @@ use ruff_source_file::SourceFileBuilder; use crate::codes::Rule; use crate::fix::{FixResult, fix_file}; use crate::linter::check_path; -use crate::message::{Emitter, EmitterContext, OldDiagnostic, TextEmitter}; +use crate::message::{Emitter, EmitterContext, TextEmitter, create_syntax_error_diagnostic}; use crate::package::PackageRoot; use crate::packaging::detect_package_root; use crate::settings::types::UnsafeFixes; @@ -42,7 +42,7 @@ pub(crate) fn test_resource_path(path: impl AsRef) -> std::path::PathBuf { pub(crate) fn test_path( path: impl AsRef, settings: &LinterSettings, -) -> Result> { +) -> Result> { let path = test_resource_path("fixtures").join(path); let source_type = PySourceType::from(&path); let source_kind = SourceKind::from_path(path.as_ref(), source_type)?.expect("valid source"); @@ -51,7 +51,7 @@ pub(crate) fn test_path( #[cfg(not(fuzzing))] pub(crate) struct TestedNotebook { - pub(crate) diagnostics: Vec, + pub(crate) diagnostics: Vec, pub(crate) source_notebook: Notebook, pub(crate) linted_notebook: Notebook, } @@ -87,7 +87,7 @@ pub(crate) fn assert_notebook_path( } /// Run [`check_path`] on a snippet of Python code. -pub fn test_snippet(contents: &str, settings: &LinterSettings) -> Vec { +pub fn test_snippet(contents: &str, settings: &LinterSettings) -> Vec { let path = Path::new(""); let contents = dedent(contents); test_contents(&SourceKind::Python(contents.into_owned()), path, settings).0 @@ -111,7 +111,7 @@ pub(crate) fn test_contents<'a>( source_kind: &'a SourceKind, path: &Path, settings: &LinterSettings, -) -> (Vec, Cow<'a, SourceKind>) { +) -> (Vec, Cow<'a, SourceKind>) { let source_type = PySourceType::from(path); let target_version = settings.resolve_target_version(path); let options = @@ -211,8 +211,7 @@ pub(crate) fn test_contents<'a>( if parsed.has_invalid_syntax() && !source_has_errors { // Previous fix introduced a syntax error, abort let fixes = print_diagnostics(messages, path, source_kind); - let syntax_errors = - print_syntax_errors(parsed.errors(), path, &locator, &transformed); + let syntax_errors = print_syntax_errors(parsed.errors(), path, &transformed); panic!( "Fixed source has a syntax error where the source document does not. This is a bug in one of the generated fixes: @@ -280,9 +279,9 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e // Not strictly necessary but adds some coverage for this code path by overriding the // noqa offset and the source file - let range = diagnostic.range(); - diagnostic.noqa_offset = Some(directives.noqa_line_for.resolve(range.start())); - if let Some(annotation) = diagnostic.diagnostic.primary_annotation_mut() { + let range = diagnostic.expect_range(); + diagnostic.set_noqa_offset(directives.noqa_line_for.resolve(range.start())); + if let Some(annotation) = diagnostic.primary_annotation_mut() { annotation.set_span( ruff_db::diagnostic::Span::from(source_code.clone()).with_range(range), ); @@ -291,26 +290,21 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e diagnostic }) .chain(parsed.errors().iter().map(|parse_error| { - OldDiagnostic::from_parse_error(parse_error, &locator, source_code.clone()) + create_syntax_error_diagnostic(source_code.clone(), &parse_error.error, parse_error) })) .sorted() .collect(); (messages, transformed) } -fn print_syntax_errors( - errors: &[ParseError], - path: &Path, - locator: &Locator, - source: &SourceKind, -) -> String { +fn print_syntax_errors(errors: &[ParseError], path: &Path, source: &SourceKind) -> String { let filename = path.file_name().unwrap().to_string_lossy(); let source_file = SourceFileBuilder::new(filename.as_ref(), source.source_code()).finish(); let messages: Vec<_> = errors .iter() .map(|parse_error| { - OldDiagnostic::from_parse_error(parse_error, locator, source_file.clone()) + create_syntax_error_diagnostic(source_file.clone(), &parse_error.error, parse_error) }) .collect(); @@ -321,12 +315,8 @@ fn print_syntax_errors( } } -/// Print the [`Message::Diagnostic`]s in `messages`. -fn print_diagnostics( - mut diagnostics: Vec, - path: &Path, - source: &SourceKind, -) -> String { +/// Print the lint diagnostics in `diagnostics`. +fn print_diagnostics(mut diagnostics: Vec, path: &Path, source: &SourceKind) -> String { diagnostics.retain(|msg| !msg.is_syntax_error()); if let Some(notebook) = source.as_ipy_notebook() { @@ -337,7 +327,7 @@ fn print_diagnostics( } pub(crate) fn print_jupyter_messages( - diagnostics: &[OldDiagnostic], + diagnostics: &[Diagnostic], path: &Path, notebook: &Notebook, ) -> String { @@ -361,7 +351,7 @@ pub(crate) fn print_jupyter_messages( String::from_utf8(output).unwrap() } -pub(crate) fn print_messages(diagnostics: &[OldDiagnostic]) -> String { +pub(crate) fn print_messages(diagnostics: &[Diagnostic]) -> String { let mut output = Vec::new(); TextEmitter::default() diff --git a/crates/ruff_python_parser/src/error.rs b/crates/ruff_python_parser/src/error.rs index 8b3fc463e1..2c2baa8dd7 100644 --- a/crates/ruff_python_parser/src/error.rs +++ b/crates/ruff_python_parser/src/error.rs @@ -42,6 +42,12 @@ impl From for ParseError { } } +impl Ranged for ParseError { + fn range(&self) -> TextRange { + self.location + } +} + impl ParseError { pub fn error(self) -> ParseErrorType { self.error diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index 73368fce77..0bade7b9a6 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -981,6 +981,12 @@ impl Display for SemanticSyntaxError { } } +impl Ranged for SemanticSyntaxError { + fn range(&self) -> TextRange { + self.range + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)] pub enum SemanticSyntaxErrorKind { /// Represents the use of a `__future__` import after the beginning of a file. diff --git a/crates/ruff_server/Cargo.toml b/crates/ruff_server/Cargo.toml index 5b7e682d07..4bfaf47663 100644 --- a/crates/ruff_server/Cargo.toml +++ b/crates/ruff_server/Cargo.toml @@ -13,6 +13,7 @@ license = { workspace = true } [lib] [dependencies] +ruff_db = { workspace = true } ruff_diagnostics = { workspace = true } ruff_formatter = { workspace = true } ruff_linter = { workspace = true } diff --git a/crates/ruff_server/src/lint.rs b/crates/ruff_server/src/lint.rs index a2c48e0fed..7ee2dcc4dc 100644 --- a/crates/ruff_server/src/lint.rs +++ b/crates/ruff_server/src/lint.rs @@ -9,13 +9,13 @@ use crate::{ resolve::is_document_excluded_for_linting, session::DocumentQuery, }; +use ruff_db::diagnostic::Diagnostic; use ruff_diagnostics::{Applicability, Edit, Fix}; use ruff_linter::{ Locator, directives::{Flags, extract_directives}, generate_noqa_edits, linter::check_path, - message::OldDiagnostic, package::PackageRoot, packaging::detect_package_root, settings::flags, @@ -228,13 +228,13 @@ pub(crate) fn fixes_for_diagnostics( /// Generates an LSP diagnostic with an associated cell index for the diagnostic to go in. /// If the source kind is a text document, the cell index will always be `0`. fn to_lsp_diagnostic( - diagnostic: &OldDiagnostic, + diagnostic: &Diagnostic, noqa_edit: Option, source_kind: &SourceKind, index: &LineIndex, encoding: PositionEncoding, ) -> (usize, lsp_types::Diagnostic) { - let diagnostic_range = diagnostic.range(); + let diagnostic_range = diagnostic.expect_range(); let name = diagnostic.name(); let body = diagnostic.body().to_string(); let fix = diagnostic.fix(); diff --git a/crates/ruff_wasm/src/lib.rs b/crates/ruff_wasm/src/lib.rs index aee4a093de..9066d17df8 100644 --- a/crates/ruff_wasm/src/lib.rs +++ b/crates/ruff_wasm/src/lib.rs @@ -210,8 +210,8 @@ impl Workspace { .map(|msg| ExpandedMessage { code: msg.secondary_code().map(ToString::to_string), message: msg.body().to_string(), - start_location: source_code.line_column(msg.start()).into(), - end_location: source_code.line_column(msg.end()).into(), + start_location: source_code.line_column(msg.expect_range().start()).into(), + end_location: source_code.line_column(msg.expect_range().end()).into(), fix: msg.fix().map(|fix| ExpandedFix { message: msg.suggestion().map(ToString::to_string), edits: fix diff --git a/crates/ty_project/src/lib.rs b/crates/ty_project/src/lib.rs index e34afa0374..f829c6b85a 100644 --- a/crates/ty_project/src/lib.rs +++ b/crates/ty_project/src/lib.rs @@ -5,10 +5,7 @@ pub use db::{CheckMode, Db, ProjectDatabase, SalsaMemoryDump}; use files::{Index, Indexed, IndexedFiles}; use metadata::settings::Settings; pub use metadata::{ProjectMetadata, ProjectMetadataError}; -use ruff_db::diagnostic::{ - Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic, create_parse_diagnostic, - create_unsupported_syntax_diagnostic, -}; +use ruff_db::diagnostic::{Annotation, Diagnostic, DiagnosticId, Severity, Span, SubDiagnostic}; use ruff_db::files::File; use ruff_db::parsed::parsed_module; use ruff_db::source::{SourceTextError, source_text}; @@ -503,11 +500,11 @@ impl Project { parsed_ref .errors() .iter() - .map(|error| create_parse_diagnostic(file, error)), + .map(|error| Diagnostic::syntax_error(file, &error.error, error)), ); diagnostics.extend(parsed_ref.unsupported_syntax_errors().iter().map(|error| { - let mut error = create_unsupported_syntax_diagnostic(file, error); + let mut error = Diagnostic::syntax_error(file, error, error); add_inferred_python_version_hint_to_diagnostic(db, &mut error, "parsing syntax"); error })); diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index 42db465d39..eb2321767c 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -11,9 +11,7 @@ use diagnostic::{ INVALID_CONTEXT_MANAGER, INVALID_SUPER_ARGUMENT, NOT_ITERABLE, POSSIBLY_UNBOUND_IMPLICIT_CALL, UNAVAILABLE_IMPLICIT_SUPER_ARGUMENTS, }; -use ruff_db::diagnostic::{ - Annotation, Severity, Span, SubDiagnostic, create_semantic_syntax_diagnostic, -}; +use ruff_db::diagnostic::{Annotation, Diagnostic, Severity, Span, SubDiagnostic}; use ruff_db::files::File; use ruff_python_ast::name::Name; use ruff_python_ast::{self as ast, AnyNodeRef}; @@ -104,7 +102,7 @@ pub fn check_types(db: &dyn Db, file: File) -> TypeCheckDiagnostics { index .semantic_syntax_errors() .iter() - .map(|error| create_semantic_syntax_diagnostic(file, error)), + .map(|error| Diagnostic::syntax_error(file, error, error)), ); check_suppressions(db, file, &mut diagnostics); diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index c6cd656c0c..97d36b072f 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -6,10 +6,7 @@ use colored::Colorize; use config::SystemKind; use parser as test_parser; use ruff_db::Db as _; -use ruff_db::diagnostic::{ - Diagnostic, DisplayDiagnosticConfig, create_parse_diagnostic, - create_unsupported_syntax_diagnostic, -}; +use ruff_db::diagnostic::{Diagnostic, DisplayDiagnosticConfig}; use ruff_db::files::{File, system_path_to_file}; use ruff_db::panic::catch_unwind; use ruff_db::parsed::parsed_module; @@ -325,14 +322,14 @@ fn run_test( let mut diagnostics: Vec = parsed .errors() .iter() - .map(|error| create_parse_diagnostic(test_file.file, error)) + .map(|error| Diagnostic::syntax_error(test_file.file, &error.error, error)) .collect(); diagnostics.extend( parsed .unsupported_syntax_errors() .iter() - .map(|error| create_unsupported_syntax_diagnostic(test_file.file, error)), + .map(|error| Diagnostic::syntax_error(test_file.file, error, error)), ); let mdtest_result = attempt_test(db, check_types, test_file, "run mdtest", None);