use std::fmt::{Display, Formatter};
use std::{borrow::Cow, path::Path, sync::Arc};
use ruff_diagnostics::{Applicability, Fix};
use ruff_source_file::{LineColumn, SourceCode, SourceFile};
use annotate_snippets::Level as AnnotateLevel;
use ruff_text_size::{Ranged, TextRange, TextSize};
#[cfg(feature = "serde")]
use serde::Serialize;
pub use self::render::{
DisplayDiagnostic, DisplayDiagnostics, DummyFileResolver, FileResolver, Input,
};
pub use self::stylesheet::{DiagnosticStylesheet, fmt_with_hyperlink};
use crate::cancellation::CancellationToken;
use crate::{Db, files::File};
mod render;
mod stylesheet;
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
pub struct Diagnostic {
inner: Arc<DiagnosticInner>,
}
impl Diagnostic {
pub fn new<'a>(
id: DiagnosticId,
severity: Severity,
message: impl IntoDiagnosticMessage + 'a,
) -> Diagnostic {
let inner = Arc::new(DiagnosticInner {
id,
severity,
message: message.into_diagnostic_message(),
custom_concise_message: None,
documentation_url: None,
annotations: vec![],
subs: vec![],
fix: None,
parent: None,
noqa_offset: None,
secondary_code: None,
header_offset: 0,
});
Diagnostic { inner }
}
pub fn invalid_syntax(
span: impl Into<Span>,
message: impl IntoDiagnosticMessage,
range: impl Ranged,
) -> Diagnostic {
let mut diag = Diagnostic::new(DiagnosticId::InvalidSyntax, Severity::Error, message);
let span = span.into().with_range(range.range());
diag.annotate(Annotation::primary(span));
diag
}
pub fn add_bug_sub_diagnostics(&mut self, url_encoded_title: &str) {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
"This indicates a bug in ty.",
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format_args!(
"If you could open an issue at https://github.com/astral-sh/ty/issues/new?title={url_encoded_title}, we'd be very appreciative!"
),
));
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Platform: {os} {arch}",
os = std::env::consts::OS,
arch = std::env::consts::ARCH
),
));
if let Some(version) = crate::program_version() {
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!("Version: {version}"),
));
}
self.sub(SubDiagnostic::new(
SubDiagnosticSeverity::Info,
format!(
"Args: {args:?}",
args = std::env::args().collect::<Vec<_>>()
),
));
}
pub fn annotate(&mut self, ann: Annotation) {
Arc::make_mut(&mut self.inner).annotations.push(ann);
}
pub fn info<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Info, message));
}
pub fn prepend_info<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
Arc::make_mut(&mut self.inner)
.subs
.insert(0, SubDiagnostic::new(SubDiagnosticSeverity::Info, message));
}
pub fn help<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
self.sub(SubDiagnostic::new(SubDiagnosticSeverity::Help, message));
}
pub fn sub(&mut self, sub: SubDiagnostic) {
Arc::make_mut(&mut self.inner).subs.push(sub);
}
pub fn display<'a>(
&'a self,
resolver: &'a dyn FileResolver,
config: &'a DisplayDiagnosticConfig,
) -> DisplayDiagnostic<'a> {
DisplayDiagnostic::new(resolver, config, self)
}
pub fn id(&self) -> DiagnosticId {
self.inner.id
}
pub fn headline_message(&self) -> &str {
self.inner.message.as_str()
}
pub fn set_headline_message(&mut self, message: impl IntoDiagnosticMessage) {
Arc::make_mut(&mut self.inner).message = message.into_diagnostic_message();
}
pub fn concise_message(&self) -> ConciseMessage<'_> {
if let Some(custom_message) = &self.inner.custom_concise_message {
return ConciseMessage::Custom(custom_message.as_str());
}
let main = self.inner.message.as_str();
let annotation = self
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
}
}
pub fn set_concise_message(&mut self, message: impl IntoDiagnosticMessage) {
Arc::make_mut(&mut self.inner).custom_concise_message =
Some(message.into_diagnostic_message());
}
pub fn clear_concise_message(&mut self) {
Arc::make_mut(&mut self.inner).custom_concise_message = None;
}
pub fn severity(&self) -> Severity {
self.inner.severity
}
pub fn primary_annotation(&self) -> Option<&Annotation> {
self.inner.annotations.iter().find(|ann| ann.is_primary)
}
pub fn primary_annotation_mut(&mut self) -> Option<&mut Annotation> {
Arc::make_mut(&mut self.inner)
.annotations
.iter_mut()
.find(|ann| ann.is_primary)
}
pub fn annotations(&self) -> &[Annotation] {
&self.inner.annotations
}
pub fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Annotation> {
Arc::make_mut(&mut self.inner).annotations.iter_mut()
}
pub fn primary_span(&self) -> Option<Span> {
self.primary_annotation().map(|ann| ann.span.clone())
}
fn primary_span_ref(&self) -> Option<&Span> {
self.primary_annotation().map(|ann| &ann.span)
}
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
self.primary_annotation().map(|ann| ann.tags.as_slice())
}
pub fn expect_primary_span(&self) -> Span {
self.primary_span().expect("Expected a primary span")
}
pub fn rendering_sort_key<'a>(&'a self, db: &'a dyn Db) -> impl Ord + 'a {
RenderingSortKey {
db,
diagnostic: self,
}
}
pub fn secondary_annotations(&self) -> impl Iterator<Item = &Annotation> {
secondary_annotations(self.inner.annotations.iter())
}
pub fn sub_diagnostics(&self) -> &[SubDiagnostic] {
&self.inner.subs
}
pub fn sub_diagnostics_mut(&mut self) -> impl Iterator<Item = &mut SubDiagnostic> {
Arc::make_mut(&mut self.inner).subs.iter_mut()
}
pub fn fix(&self) -> Option<&Fix> {
self.inner.fix.as_ref()
}
#[cfg(test)]
fn fix_mut(&mut self) -> Option<&mut Fix> {
Arc::make_mut(&mut self.inner).fix.as_mut()
}
pub fn set_fix(&mut self, fix: Fix) {
debug_assert!(
self.primary_span().is_some(),
"Expected a source file for a diagnostic with a fix"
);
Arc::make_mut(&mut self.inner).fix = Some(fix);
}
pub fn set_optional_fix(&mut self, fix: Option<Fix>) {
if let Some(fix) = fix {
self.set_fix(fix);
}
}
pub fn remove_fix(&mut self) {
Arc::make_mut(&mut self.inner).fix = None;
}
pub fn has_applicable_fix(&self, fix_applicability: Applicability) -> bool {
self.fix().is_some_and(|fix| fix.applies(fix_applicability))
}
pub fn documentation_url(&self) -> Option<&str> {
self.inner.documentation_url.as_deref()
}
pub fn set_documentation_url(&mut self, url: Option<String>) {
Arc::make_mut(&mut self.inner).documentation_url = url;
}
pub fn parent(&self) -> Option<TextSize> {
self.inner.parent
}
pub fn set_parent(&mut self, parent: TextSize) {
Arc::make_mut(&mut self.inner).parent = Some(parent);
}
#[cfg(feature = "serde")]
fn noqa_offset(&self) -> Option<TextSize> {
self.inner.noqa_offset
}
pub fn set_noqa_offset(&mut self, noqa_offset: TextSize) {
Arc::make_mut(&mut self.inner).noqa_offset = Some(noqa_offset);
}
pub fn secondary_code(&self) -> Option<&SecondaryCode> {
self.inner.secondary_code.as_ref()
}
pub fn secondary_code_or_id(&self) -> &str {
self.secondary_code()
.map_or_else(|| self.inner.id.as_str(), SecondaryCode::as_str)
}
pub fn set_secondary_code(&mut self, code: SecondaryCode) {
Arc::make_mut(&mut self.inner).secondary_code = Some(code);
}
pub fn name(&self) -> &'static str {
self.id().as_str()
}
pub fn is_invalid_syntax(&self) -> bool {
self.id().is_invalid_syntax()
}
pub fn first_help_text(&self) -> Option<&str> {
self.sub_diagnostics()
.iter()
.find(|sub| matches!(sub.inner.severity, SubDiagnosticSeverity::Help))
.map(|sub| sub.inner.message.as_str())
}
pub fn expect_ruff_filename(&self) -> String {
self.expect_primary_span()
.expect_ruff_file()
.name()
.to_string()
}
pub fn ruff_start_location(&self) -> Option<LineColumn> {
Some(
self.ruff_source_file()?
.to_source_code()
.line_column(self.range()?.start()),
)
}
pub fn ruff_end_location(&self) -> Option<LineColumn> {
Some(
self.ruff_source_file()?
.to_source_code()
.line_column(self.range()?.end()),
)
}
pub fn ruff_source_file(&self) -> Option<&SourceFile> {
self.primary_span_ref()?.as_ruff_file()
}
fn expect_ruff_source_file(&self) -> &SourceFile {
self.ruff_source_file()
.expect("Expected a ruff source file")
}
pub fn range(&self) -> Option<TextRange> {
self.primary_span()?.range()
}
pub fn ruff_start_ordering(&self, other: &Self) -> std::cmp::Ordering {
let a = (
self.severity().is_fatal(),
self.expect_ruff_source_file(),
self.range().map(|r| r.start()),
);
let b = (
other.severity().is_fatal(),
other.expect_ruff_source_file(),
other.range().map(|r| r.start()),
);
a.cmp(&b)
}
pub fn set_header_offset(&mut self, offset: usize) {
Arc::make_mut(&mut self.inner).header_offset = offset;
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
struct DiagnosticInner {
id: DiagnosticId,
documentation_url: Option<String>,
severity: Severity,
message: DiagnosticMessage,
custom_concise_message: Option<DiagnosticMessage>,
annotations: Vec<Annotation>,
subs: Vec<SubDiagnostic>,
fix: Option<Fix>,
parent: Option<TextSize>,
noqa_offset: Option<TextSize>,
secondary_code: Option<SecondaryCode>,
header_offset: usize,
}
struct RenderingSortKey<'a> {
db: &'a dyn Db,
diagnostic: &'a Diagnostic,
}
impl Ord for RenderingSortKey<'_> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if let (Some(span1), Some(span2)) = (
self.diagnostic.primary_span(),
other.diagnostic.primary_span(),
) {
let file1 = span1.file();
let file2 = span2.file();
if file1 != file2 {
let order = file1.path(&self.db).cmp(file2.path(&self.db));
if order.is_ne() {
return order;
}
}
if let (Some(range1), Some(range2)) = (span1.range(), span2.range()) {
let order = range1.start().cmp(&range2.start());
if order.is_ne() {
return order;
}
}
}
let order = self
.diagnostic
.severity()
.cmp(&other.diagnostic.severity())
.reverse();
if order.is_ne() {
return order;
}
let order = self.diagnostic.id().cmp(&other.diagnostic.id());
if order.is_ne() {
return order;
}
self.diagnostic
.concise_message()
.to_str()
.cmp(&other.diagnostic.concise_message().to_str())
}
}
impl PartialOrd for RenderingSortKey<'_> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for RenderingSortKey<'_> {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}
impl Eq for RenderingSortKey<'_> {}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
pub struct SubDiagnostic {
inner: Box<SubDiagnosticInner>,
}
impl SubDiagnostic {
pub fn new<'a>(
severity: SubDiagnosticSeverity,
message: impl IntoDiagnosticMessage + 'a,
) -> SubDiagnostic {
let inner = Box::new(SubDiagnosticInner {
severity,
message: message.into_diagnostic_message(),
annotations: vec![],
});
SubDiagnostic { inner }
}
pub fn annotate(&mut self, ann: Annotation) {
self.inner.annotations.push(ann);
}
pub fn annotations(&self) -> &[Annotation] {
&self.inner.annotations
}
pub fn secondary_annotations(&self) -> impl Iterator<Item = &Annotation> {
secondary_annotations(self.inner.annotations.iter())
}
pub fn annotations_mut(&mut self) -> impl Iterator<Item = &mut Annotation> {
self.inner.annotations.iter_mut()
}
pub fn primary_annotation(&self) -> Option<&Annotation> {
self.inner.annotations.iter().find(|ann| ann.is_primary)
}
pub fn primary_span_ref(&self) -> Option<&Span> {
self.primary_annotation().map(Annotation::get_span)
}
pub fn headline_message(&self) -> &str {
self.inner.message.as_str()
}
pub fn concise_message(&self) -> ConciseMessage<'_> {
let main = self.headline_message();
let annotation = self
.primary_annotation()
.and_then(|ann| ann.get_message())
.unwrap_or_default();
if annotation.is_empty() {
ConciseMessage::MainDiagnostic(main)
} else {
ConciseMessage::Both { main, annotation }
}
}
pub fn severity(&self) -> SubDiagnosticSeverity {
self.inner.severity
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
struct SubDiagnosticInner {
severity: SubDiagnosticSeverity,
message: DiagnosticMessage,
annotations: Vec<Annotation>,
}
fn secondary_annotations<'a>(
annotations: impl Iterator<Item = &'a Annotation>,
) -> impl Iterator<Item = &'a Annotation> {
let mut seen_primary = false;
annotations.filter(move |ann| {
if seen_primary {
true
} else if ann.is_primary {
seen_primary = true;
false
} else {
true
}
})
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
pub struct Annotation {
span: Span,
message: Option<DiagnosticMessage>,
is_primary: bool,
tags: Vec<DiagnosticTag>,
hide_snippet: bool,
}
impl Annotation {
pub fn primary(span: Span) -> Annotation {
Annotation {
span,
message: None,
is_primary: true,
tags: Vec::new(),
hide_snippet: false,
}
}
pub fn secondary(span: Span) -> Annotation {
Annotation {
span,
message: None,
is_primary: false,
tags: Vec::new(),
hide_snippet: false,
}
}
pub fn message<'a>(self, message: impl IntoDiagnosticMessage + 'a) -> Annotation {
let message = Some(message.into_diagnostic_message());
Annotation { message, ..self }
}
pub fn set_message<'a>(&mut self, message: impl IntoDiagnosticMessage + 'a) {
self.message = Some(message.into_diagnostic_message());
}
pub fn get_message(&self) -> Option<&str> {
self.message.as_ref().map(|m| m.as_str())
}
pub fn get_span(&self) -> &Span {
&self.span
}
pub fn set_span(&mut self, span: Span) {
self.span = span;
}
pub fn push_tag(&mut self, tag: DiagnosticTag) {
self.tags.push(tag);
}
pub fn hide_snippet(&mut self, yes: bool) {
self.hide_snippet = yes;
}
pub fn is_primary(&self) -> bool {
self.is_primary
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, get_size2::GetSize)]
pub enum DiagnosticTag {
Unnecessary,
Deprecated,
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, get_size2::GetSize)]
pub struct LintName(&'static str);
impl LintName {
pub const fn of(name: &'static str) -> Self {
Self(name)
}
pub const fn as_str(&self) -> &'static str {
self.0
}
}
impl std::ops::Deref for LintName {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0
}
}
impl std::fmt::Display for LintName {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0)
}
}
impl PartialEq<str> for LintName {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl PartialEq<&str> for LintName {
fn eq(&self, other: &&str) -> bool {
self.0 == *other
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, get_size2::GetSize)]
pub enum DiagnosticId {
Panic,
Io,
InvalidSyntax,
Lint(LintName),
RevealedType,
UnknownRule,
InvalidGlob,
InvalidScriptMetadata,
EmptyInclude,
UnnecessaryOverridesSection,
UselessOverridesSection,
DeprecatedSetting,
UnsupportedPythonVersion,
Unformatted,
InvalidCliOption,
PreviewFeature,
InternalError,
}
impl DiagnosticId {
pub const fn lint(name: &'static str) -> Self {
Self::Lint(LintName::of(name))
}
pub fn is_lint(&self) -> bool {
matches!(self, DiagnosticId::Lint(_))
}
pub const fn as_lint(&self) -> Option<LintName> {
match self {
DiagnosticId::Lint(name) => Some(*name),
_ => None,
}
}
pub fn is_lint_named(&self, name: &str) -> bool {
matches!(self, DiagnosticId::Lint(self_name) if self_name == name)
}
pub fn strip_category(code: &str) -> Option<&str> {
code.split_once(':').map(|(_, rest)| rest)
}
pub fn as_str(&self) -> &'static str {
match self {
DiagnosticId::Panic => "panic",
DiagnosticId::Io => "io",
DiagnosticId::InvalidSyntax => "invalid-syntax",
DiagnosticId::Lint(name) => name.as_str(),
DiagnosticId::RevealedType => "revealed-type",
DiagnosticId::UnknownRule => "unknown-rule",
DiagnosticId::InvalidGlob => "invalid-glob",
DiagnosticId::InvalidScriptMetadata => "invalid-script-metadata",
DiagnosticId::EmptyInclude => "empty-include",
DiagnosticId::UnnecessaryOverridesSection => "unnecessary-overrides-section",
DiagnosticId::UselessOverridesSection => "useless-overrides-section",
DiagnosticId::DeprecatedSetting => "deprecated-setting",
DiagnosticId::UnsupportedPythonVersion => "unsupported-python-version",
DiagnosticId::Unformatted => "unformatted",
DiagnosticId::InvalidCliOption => "invalid-cli-option",
DiagnosticId::PreviewFeature => "preview-feature",
DiagnosticId::InternalError => "internal-error",
}
}
fn is_invalid_syntax(&self) -> bool {
matches!(self, Self::InvalidSyntax)
}
}
impl std::fmt::Display for DiagnosticId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub enum UnifiedFile {
Ty(File),
Ruff(SourceFile),
}
impl UnifiedFile {
fn path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a str {
match self {
UnifiedFile::Ty(file) => resolver.path(*file),
UnifiedFile::Ruff(file) => file.name(),
}
}
fn relative_path<'a>(&'a self, resolver: &'a dyn FileResolver) -> &'a Path {
let cwd = resolver.current_directory();
let path = Path::new(self.path(resolver));
if let Ok(path) = path.strip_prefix(cwd) {
return path;
}
path
}
fn diagnostic_source(&self, resolver: &dyn FileResolver) -> DiagnosticSource {
match self {
UnifiedFile::Ty(file) => DiagnosticSource::Ty(resolver.input(*file)),
UnifiedFile::Ruff(file) => DiagnosticSource::Ruff(file.clone()),
}
}
}
#[derive(Clone, Debug)]
enum DiagnosticSource {
Ty(Input),
Ruff(SourceFile),
}
impl DiagnosticSource {
fn as_source_code(&self) -> SourceCode<'_, '_> {
match self {
DiagnosticSource::Ty(input) => SourceCode::new(input.text.as_str(), &input.line_index),
DiagnosticSource::Ruff(source) => SourceCode::new(source.source_text(), source.index()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub struct Span {
file: UnifiedFile,
range: Option<TextRange>,
}
impl Span {
pub fn file(&self) -> &UnifiedFile {
&self.file
}
pub fn range(&self) -> Option<TextRange> {
self.range
}
pub fn with_range(self, range: TextRange) -> Span {
self.with_optional_range(Some(range))
}
pub fn with_optional_range(self, range: Option<TextRange>) -> Span {
Span { range, ..self }
}
pub fn expect_ty_file(&self) -> File {
match self.file {
UnifiedFile::Ty(file) => file,
UnifiedFile::Ruff(_) => panic!("Expected a ty `File`, found a ruff `SourceFile`"),
}
}
fn expect_ruff_file(&self) -> &SourceFile {
self.as_ruff_file()
.expect("Expected a ruff `SourceFile`, found a ty `File`")
}
pub fn as_ruff_file(&self) -> Option<&SourceFile> {
match &self.file {
UnifiedFile::Ty(_) => None,
UnifiedFile::Ruff(file) => Some(file),
}
}
}
impl From<File> for Span {
fn from(file: File) -> Span {
let file = UnifiedFile::Ty(file);
Span { file, range: None }
}
}
impl From<SourceFile> for Span {
fn from(file: SourceFile) -> Self {
let file = UnifiedFile::Ruff(file);
Span { file, range: None }
}
}
impl From<crate::files::FileRange> for Span {
fn from(file_range: crate::files::FileRange) -> Span {
Span::from(file_range.file()).with_range(file_range.range())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Severity {
Info,
Warning,
Error,
Fatal,
}
impl Severity {
fn to_annotate(self) -> AnnotateLevel<'static> {
match self {
Severity::Info => AnnotateLevel::INFO,
Severity::Warning => AnnotateLevel::WARNING,
Severity::Error => AnnotateLevel::ERROR,
Severity::Fatal => AnnotateLevel::ERROR,
}
}
pub const fn is_fatal(self) -> bool {
matches!(self, Severity::Fatal)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash, get_size2::GetSize)]
pub enum SubDiagnosticSeverity {
Help,
Info,
Warning,
Error,
Fatal,
}
impl SubDiagnosticSeverity {
fn to_annotate(self) -> AnnotateLevel<'static> {
match self {
SubDiagnosticSeverity::Help => AnnotateLevel::HELP,
SubDiagnosticSeverity::Info => AnnotateLevel::INFO,
SubDiagnosticSeverity::Warning => AnnotateLevel::WARNING,
SubDiagnosticSeverity::Error => AnnotateLevel::ERROR,
SubDiagnosticSeverity::Fatal => AnnotateLevel::ERROR,
}
}
}
impl Display for SubDiagnosticSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
SubDiagnosticSeverity::Help => "help",
SubDiagnosticSeverity::Info => "info",
SubDiagnosticSeverity::Warning => "warning",
SubDiagnosticSeverity::Error => "error",
SubDiagnosticSeverity::Fatal => "fatal",
};
f.write_str(s)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum HyperlinkMode {
#[default]
Auto,
Always,
Never,
}
#[derive(Clone, Debug)]
pub struct DisplayDiagnosticConfig {
program: &'static str,
format: DiagnosticFormat,
color: bool,
hyperlinks: HyperlinkMode,
anonymized_line_numbers: bool,
context: usize,
merge_window: usize,
preview: bool,
prefer_rule_codes: bool,
hide_severity: bool,
show_fix_status: bool,
fix_applicability: Applicability,
cancellation_token: Option<CancellationToken>,
}
impl DisplayDiagnosticConfig {
pub fn new(program: &'static str) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
program,
format: DiagnosticFormat::default(),
color: false,
hyperlinks: HyperlinkMode::Auto,
anonymized_line_numbers: false,
context: 2,
merge_window: 2,
preview: false,
prefer_rule_codes: false,
hide_severity: false,
show_fix_status: false,
fix_applicability: Applicability::Safe,
cancellation_token: None,
}
}
pub fn format(self, format: DiagnosticFormat) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig { format, ..self }
}
pub fn color(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig { color: yes, ..self }
}
pub fn hyperlinks(self, mode: HyperlinkMode) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
hyperlinks: mode,
..self
}
}
pub fn anonymized_line_numbers(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
anonymized_line_numbers: yes,
..self
}
}
pub fn context(self, lines: usize) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
context: lines,
..self
}
}
#[cfg(test)]
fn merge_window(self, lines: usize) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
merge_window: lines,
..self
}
}
pub fn preview(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
preview: yes,
..self
}
}
pub fn preview_enabled(&self) -> bool {
self.preview
}
pub fn prefer_rule_codes(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
prefer_rule_codes: yes,
..self
}
}
pub fn is_prefer_rule_codes_enabled(&self) -> bool {
self.prefer_rule_codes
}
pub fn hide_severity(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
hide_severity: yes,
..self
}
}
pub fn with_show_fix_status(self, yes: bool) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
show_fix_status: yes,
..self
}
}
pub fn with_fix_applicability(self, applicability: Applicability) -> DisplayDiagnosticConfig {
DisplayDiagnosticConfig {
fix_applicability: applicability,
..self
}
}
pub fn show_fix_status(&self) -> bool {
self.show_fix_status
}
pub fn fix_applicability(&self) -> Applicability {
self.fix_applicability
}
pub fn with_cancellation_token(
mut self,
token: Option<CancellationToken>,
) -> DisplayDiagnosticConfig {
self.cancellation_token = token;
self
}
fn is_canceled(&self) -> bool {
self.cancellation_token
.as_ref()
.is_some_and(|token| token.is_cancelled())
}
}
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DiagnosticFormat {
#[default]
Full,
Concise,
Azure,
#[cfg(feature = "serde")]
Json,
#[cfg(feature = "serde")]
JsonLines,
#[cfg(feature = "serde")]
Rdjson,
Pylint,
#[cfg(feature = "junit")]
Junit,
#[cfg(feature = "serde")]
Gitlab,
Github,
}
pub enum ConciseMessage<'a> {
MainDiagnostic(&'a str),
Both { main: &'a str, annotation: &'a str },
Custom(&'a str),
}
impl<'a> ConciseMessage<'a> {
pub fn to_str(&self) -> Cow<'a, str> {
match self {
ConciseMessage::MainDiagnostic(s) | ConciseMessage::Custom(s) => Cow::Borrowed(s),
ConciseMessage::Both { .. } => Cow::Owned(self.to_string()),
}
}
}
impl std::fmt::Display for ConciseMessage<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match *self {
ConciseMessage::MainDiagnostic(main) => {
write!(f, "{main}")
}
ConciseMessage::Both { main, annotation } => {
write!(f, "{main}: {annotation}")
}
ConciseMessage::Custom(message) => {
write!(f, "{message}")
}
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for ConciseMessage<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(self)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub struct DiagnosticMessage(Box<str>);
impl DiagnosticMessage {
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for DiagnosticMessage {
fn from(s: &str) -> DiagnosticMessage {
DiagnosticMessage(s.into())
}
}
impl From<String> for DiagnosticMessage {
fn from(s: String) -> DiagnosticMessage {
DiagnosticMessage(s.into())
}
}
impl From<Box<str>> for DiagnosticMessage {
fn from(s: Box<str>) -> DiagnosticMessage {
DiagnosticMessage(s)
}
}
impl IntoDiagnosticMessage for DiagnosticMessage {
fn into_diagnostic_message(self) -> DiagnosticMessage {
self
}
}
pub trait IntoDiagnosticMessage {
fn into_diagnostic_message(self) -> DiagnosticMessage;
}
impl<T: std::fmt::Display> IntoDiagnosticMessage for T {
fn into_diagnostic_message(self) -> DiagnosticMessage {
DiagnosticMessage::from(self.to_string())
}
}
#[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
}
}
impl std::fmt::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<SecondaryCode> for &str {
fn eq(&self, other: &SecondaryCode) -> bool {
other.eq(self)
}
}
impl From<&SecondaryCode> for SecondaryCode {
fn from(value: &SecondaryCode) -> Self {
value.clone()
}
}