diff --git a/crates/ruff/src/cache.rs b/crates/ruff/src/cache.rs index 228662f42b..6eaef9382d 100644 --- a/crates/ruff/src/cache.rs +++ b/crates/ruff/src/cache.rs @@ -13,7 +13,6 @@ use itertools::Itertools; use log::{debug, error}; use rayon::iter::ParallelIterator; use rayon::iter::{IntoParallelIterator, ParallelBridge}; -use ruff_db::diagnostic::serde_impls::DiagnosticsSerializer; use ruff_linter::codes::Rule; use rustc_hash::FxHashMap; use tempfile::NamedTempFile; @@ -21,10 +20,12 @@ use tempfile::NamedTempFile; use ruff_cache::{CacheKey, CacheKeyHasher}; use ruff_db::diagnostic::Diagnostic; use ruff_diagnostics::Fix; +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::{TextRange, TextSize}; use ruff_workspace::Settings; use ruff_workspace::resolver::Resolver; @@ -344,7 +345,22 @@ impl FileCache { let diagnostics = if lint.messages.is_empty() { Vec::new() } else { - lint.messages.diagnostics().to_vec() + let file = SourceFileBuilder::new(path.to_string_lossy(), &*lint.source).finish(); + lint.messages + .iter() + .map(|msg| { + create_lint_diagnostic( + &msg.body, + msg.suggestion.as_ref(), + msg.range, + msg.fix.clone(), + msg.parent, + file.clone(), + msg.noqa_offset, + msg.rule, + ) + }) + .collect() }; let notebook_indexes = if let Some(notebook_index) = lint.notebook_index.as_ref() { FxHashMap::from_iter([(path.to_string_lossy().to_string(), notebook_index.clone())]) @@ -399,8 +415,7 @@ pub(crate) struct LintCacheData { /// Imports made. // pub(super) imports: ImportMap, /// Diagnostic messages. - #[bincode(with_serde)] - pub(super) messages: DiagnosticsSerializer, + pub(super) messages: Vec, /// Source code of the file. /// /// # Notes @@ -423,7 +438,30 @@ impl LintCacheData { String::new() // No messages, no need to keep the source! }; - let messages = DiagnosticsSerializer::new(diagnostics.to_vec()); + let messages = diagnostics + .iter() + // Parse the kebab-case rule name into a `Rule`. This will fail for syntax errors, so + // this also serves to filter them out, but we shouldn't be caching files with syntax + // errors anyway. + .filter_map(|msg| Some((msg.name().parse().ok()?, msg))) + .map(|(rule, msg)| { + // Make sure that all message use the same source file. + assert_eq!( + 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.expect_range(), + parent: msg.parent(), + fix: msg.fix().cloned(), + noqa_offset: msg.noqa_offset(), + } + }) + .collect(); Self { messages, diff --git a/crates/ruff_db/src/diagnostic/mod.rs b/crates/ruff_db/src/diagnostic/mod.rs index 8ffca34839..947075becb 100644 --- a/crates/ruff_db/src/diagnostic/mod.rs +++ b/crates/ruff_db/src/diagnostic/mod.rs @@ -790,7 +790,6 @@ impl Annotation { /// These tags are used to provide additional information about the annotation. /// and are passed through to the language server protocol. #[derive(Debug, Clone, Eq, PartialEq, get_size2::GetSize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DiagnosticTag { /// Unused or unnecessary code. Used for unused parameters, unreachable code, etc. Unnecessary, @@ -805,7 +804,6 @@ pub enum DiagnosticTag { /// /// Rules use kebab case, e.g. `no-foo`. #[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct LintName(&'static str); impl LintName { @@ -846,7 +844,6 @@ impl PartialEq<&str> for LintName { /// Uniquely identifies the kind of a diagnostic. #[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum DiagnosticId { Panic, @@ -1144,7 +1141,6 @@ impl From for Span { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, get_size2::GetSize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Severity { Info, Warning, @@ -1348,7 +1344,6 @@ impl std::fmt::Display for ConciseMessage<'_> { /// a blanket trait implementation for `IntoDiagnosticMessage` for /// anything that implements `std::fmt::Display`. #[derive(Clone, Debug, Eq, PartialEq, get_size2::GetSize)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DiagnosticMessage(Box); impl DiagnosticMessage { @@ -1457,271 +1452,3 @@ impl From<&SecondaryCode> for SecondaryCode { value.clone() } } - -#[cfg(feature = "serde")] -pub mod serde_impls { - use std::{collections::HashMap, sync::Mutex}; - - use rustc_hash::FxHashMap; - use serde::{ - Deserialize, Serialize, - ser::{SerializeSeq, SerializeStruct}, - }; - - use super::{Annotation, Diagnostic, LintName, Span, SubDiagnostic}; - - #[derive(Debug)] - pub struct DiagnosticsSerializer<'a> { - source_files: Mutex>, - diagnostics: &'a [Diagnostic], - } - - impl PartialEq for DiagnosticsSerializer<'_> { - fn eq(&self, other: &Self) -> bool { - self.diagnostics == other.diagnostics - } - } - - impl<'a> DiagnosticsSerializer<'a> { - pub fn new(diagnostics: &'a [Diagnostic]) -> Self { - Self { - diagnostics, - source_files: Mutex::default(), - } - } - - pub fn is_empty(&self) -> bool { - self.diagnostics.is_empty() - } - - pub fn diagnostics(&self) -> &[Diagnostic] { - &self.diagnostics - } - } - - impl Serialize for DiagnosticsSerializer { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("DiagnosticSerializer", 2)?; - s.serialize_field( - "diagnostics", - &DiagnosticSerializer { - diagnostics: &self.diagnostics, - source_files: &self.source_files, - }, - )?; - s.serialize_field("source_files", &*self.source_files.lock().unwrap())?; - - s.end() - } - } - - impl<'de> Deserialize<'de> for DiagnosticsSerializer { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Ok(Self { - source_files: Mutex::new(HashMap::deserialize(_deserializer)?), - diagnostics: todo!(), - }) - } - } - - struct DiagnosticSerializer<'a> { - source_files: &'a Mutex>, - diagnostics: &'a [Diagnostic], - } - - impl Serialize for DiagnosticSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_seq(Some(self.diagnostics.len()))?; - - for diag in self.diagnostics { - s.serialize_element(&DiagnosticSerializerInner { - diagnostic: diag, - source_files: self.source_files, - })?; - } - - s.end() - } - } - - struct DiagnosticSerializerInner<'a> { - source_files: &'a Mutex>, - diagnostic: &'a Diagnostic, - } - - impl Serialize for DiagnosticSerializerInner<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("Diagnostic", 7)?; - - s.serialize_field("id", &self.diagnostic.inner.id)?; - s.serialize_field("severity", &self.diagnostic.inner.severity)?; - s.serialize_field("message", &self.diagnostic.inner.message)?; - s.serialize_field( - "annotations", - &AnnotationsSerializer { - annotations: &self.diagnostic.inner.annotations, - source_files: self.source_files, - }, - )?; - s.serialize_field( - "subs", - &SubDiagnosticsSerializer { - subdiagnostics: &self.diagnostic.inner.subs, - source_files: self.source_files, - }, - )?; - s.serialize_field("fix", &self.diagnostic.inner.fix)?; - s.serialize_field("parent", &self.diagnostic.inner.parent)?; - s.serialize_field("noqa_offset", &self.diagnostic.inner.noqa_offset)?; - s.serialize_field("secondary_code", &self.diagnostic.inner.secondary_code)?; - - s.end() - } - } - - struct AnnotationsSerializer<'a> { - source_files: &'a Mutex>, - annotations: &'a [Annotation], - } - - impl Serialize for AnnotationsSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_seq(Some(self.annotations.len()))?; - - for annotation in self.annotations { - s.serialize_element(&AnnotationSerializer { - annotation, - source_files: self.source_files, - })?; - } - - s.end() - } - } - - struct AnnotationSerializer<'a> { - source_files: &'a Mutex>, - annotation: &'a Annotation, - } - - impl Serialize for AnnotationSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("AnnotationSerializer", 4)?; - - s.serialize_field( - "span", - &SpanSerializer { - span: &self.annotation.span, - source_files: self.source_files, - }, - )?; - s.serialize_field("message", &self.annotation.message)?; - s.serialize_field("is_primary", &self.annotation.is_primary)?; - s.serialize_field("tags", &self.annotation.tags)?; - - s.end() - } - } - - struct SpanSerializer<'a> { - source_files: &'a Mutex>, - span: &'a Span, - } - - impl Serialize for SpanSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("SpanSerializer", 2)?; - - let file = self.span.expect_ruff_file(); - let name = file.name(); - let mut source_files = self.source_files.lock().unwrap(); - source_files.insert(name.to_string(), file.source_text().to_string()); - - s.serialize_field("file", name)?; - s.serialize_field("range", &self.span.range)?; - - s.end() - } - } - - struct SubDiagnosticsSerializer<'a> { - source_files: &'a Mutex>, - subdiagnostics: &'a [SubDiagnostic], - } - - impl Serialize for SubDiagnosticsSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_seq(Some(self.subdiagnostics.len()))?; - - for subdiagnostic in self.subdiagnostics { - s.serialize_element(&SubDiagnosticSerializer { - subdiagnostic, - source_files: self.source_files, - })?; - } - - s.end() - } - } - - struct SubDiagnosticSerializer<'a> { - source_files: &'a Mutex>, - subdiagnostic: &'a SubDiagnostic, - } - - impl Serialize for SubDiagnosticSerializer<'_> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut s = serializer.serialize_struct("SubdiagnosticSerializer", 4)?; - - s.serialize_field("severity", &self.subdiagnostic.inner.severity)?; - s.serialize_field("message", &self.subdiagnostic.inner.message)?; - s.serialize_field( - "annotations", - &AnnotationsSerializer { - annotations: &self.subdiagnostic.inner.annotations, - source_files: self.source_files, - }, - )?; - - s.end() - } - } - - impl<'de> Deserialize<'de> for LintName { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?.into_boxed_str(); - // TODO not really how we want to handle this, probably intern again - Ok(LintName::of(Box::leak(s))) - } - } -}