Compare commits
6 Commits
0.12.2
...
gankra/got
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97af9d8466 | ||
|
|
5cc6762c23 | ||
|
|
1bee527cb2 | ||
|
|
d7b7b835e1 | ||
|
|
333191b7f7 | ||
|
|
77a5c5ac80 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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<NotebookIndex>,
|
||||
) -> 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<OldDiagnostic>,
|
||||
pub(crate) inner: Vec<Diagnostic>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub(crate) fn new(
|
||||
diagnostics: Vec<OldDiagnostic>,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
) -> 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(),
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 -----
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<Span>,
|
||||
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<TextSize> {
|
||||
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<TextSize> {
|
||||
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<String> {
|
||||
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<TextRange> {
|
||||
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<std::cmp::Ordering> {
|
||||
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<Annotation>,
|
||||
subs: Vec<SubDiagnostic>,
|
||||
fix: Option<Fix>,
|
||||
parent: Option<TextSize>,
|
||||
noqa_offset: Option<TextSize>,
|
||||
secondary_code: Option<SecondaryCode>,
|
||||
}
|
||||
|
||||
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<T: std::fmt::Display> 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<SecondaryCode> 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = [] }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<Vec<OldDiagnostic>>,
|
||||
diagnostics: RefCell<Vec<Diagnostic>>,
|
||||
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<OldDiagnostic>, SourceFile) {
|
||||
pub(crate) fn into_parts(self) -> (Vec<Diagnostic>, SourceFile) {
|
||||
(self.diagnostics.into_inner(), self.source_file)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn as_mut_vec(&mut self) -> &mut Vec<OldDiagnostic> {
|
||||
pub(crate) fn as_mut_vec(&mut self) -> &mut Vec<Diagnostic> {
|
||||
self.diagnostics.get_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &OldDiagnostic> {
|
||||
pub(crate) fn iter(&mut self) -> impl Iterator<Item = &Diagnostic> {
|
||||
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<OldDiagnostic>,
|
||||
diagnostic: Option<Diagnostic>,
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<NoqaCode> for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NoqaCode> for SecondaryCode {
|
||||
fn eq(&self, other: &NoqaCode) -> bool {
|
||||
&self.as_str() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SecondaryCode> for NoqaCode {
|
||||
fn eq(&self, other: &SecondaryCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for NoqaCode {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
|
||||
@@ -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<Stmt> {
|
||||
@@ -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("<filename>", "<code>").finish(),
|
||||
);
|
||||
diagnostic.fix = Some(Fix::safe_edits(
|
||||
diagnostic.set_fix(Fix::safe_edits(
|
||||
iter.next().ok_or(anyhow!("expected edits nonempty"))?,
|
||||
iter,
|
||||
));
|
||||
|
||||
@@ -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<FixResult> {
|
||||
@@ -52,7 +52,7 @@ pub(crate) fn fix_file(
|
||||
|
||||
/// Apply a series of fixes.
|
||||
fn apply_fixes<'a>(
|
||||
diagnostics: impl Iterator<Item = &'a OldDiagnostic>,
|
||||
diagnostics: impl Iterator<Item = &'a Diagnostic>,
|
||||
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<Item = Edit>,
|
||||
) -> Vec<OldDiagnostic> {
|
||||
) -> Vec<Diagnostic> {
|
||||
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()
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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<OldDiagnostic>,
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
/// 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<ModModule>,
|
||||
target_version: TargetVersion,
|
||||
) -> Vec<OldDiagnostic> {
|
||||
) -> Vec<Diagnostic> {
|
||||
// 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<PackageRoot<'_>>,
|
||||
@@ -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<OldDiagnostic>,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
parse_errors: &[ParseError],
|
||||
unsupported_syntax_errors: &[UnsupportedSyntaxError],
|
||||
semantic_syntax_errors: &[SemanticSyntaxError],
|
||||
locator: &Locator,
|
||||
directives: &Directives,
|
||||
source_file: &SourceFile,
|
||||
) -> Vec<OldDiagnostic> {
|
||||
) -> Vec<Diagnostic> {
|
||||
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<OldDiagnostic> {
|
||||
fn test_snippet_syntax_errors(contents: &str, settings: &LinterSettings) -> Vec<Diagnostic> {
|
||||
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<OldDiagnostic> {
|
||||
) -> Vec<Diagnostic> {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Diff<'a>> {
|
||||
pub(crate) fn from_message(message: &'a Diagnostic) -> Option<Diff<'a>> {
|
||||
message.fix().map(|fix| Diff {
|
||||
source_code: message.source_file(),
|
||||
source_code: message.expect_ruff_source_file(),
|
||||
fix,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)?;
|
||||
|
||||
@@ -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::<u64>::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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<Fix>,
|
||||
pub parent: Option<TextSize>,
|
||||
pub(crate) noqa_offset: Option<TextSize>,
|
||||
pub(crate) secondary_code: Option<SecondaryCode>,
|
||||
/// 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<Span>,
|
||||
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<B, S>(
|
||||
body: B,
|
||||
suggestion: Option<S>,
|
||||
range: TextRange,
|
||||
fix: Option<Fix>,
|
||||
parent: Option<TextSize>,
|
||||
file: SourceFile,
|
||||
noqa_offset: Option<TextSize>,
|
||||
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<B, S>(
|
||||
body: B,
|
||||
suggestion: Option<S>,
|
||||
range: TextRange,
|
||||
fix: Option<Fix>,
|
||||
parent: Option<TextSize>,
|
||||
file: SourceFile,
|
||||
noqa_offset: Option<TextSize>,
|
||||
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<T: Violation>(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<TextSize> {
|
||||
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<String> {
|
||||
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<Ordering> {
|
||||
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<T: 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<String, Vec<MessageWithLocation>> {
|
||||
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<SecondaryCode> for &str {
|
||||
fn eq(&self, other: &SecondaryCode) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<NoqaCode> for SecondaryCode {
|
||||
fn eq(&self, other: &NoqaCode) -> bool {
|
||||
&self.as_str() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SecondaryCode> 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<OldDiagnostic> {
|
||||
use super::create_syntax_error_diagnostic;
|
||||
|
||||
pub(super) fn create_syntax_error_diagnostics() -> Vec<Diagnostic> {
|
||||
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<OldDiagnostic> {
|
||||
pub(super) fn create_diagnostics() -> Vec<Diagnostic> {
|
||||
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<OldDiagnostic>, FxHashMap<String, NotebookIndex>) {
|
||||
-> (Vec<Diagnostic>, FxHashMap<String, NotebookIndex>) {
|
||||
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, NotebookIndex>,
|
||||
) -> String {
|
||||
let context = EmitterContext::new(notebook_indexes);
|
||||
|
||||
@@ -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),
|
||||
)?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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(),
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<OldDiagnostic> {
|
||||
pub fn lint_pyproject_toml(source_file: &SourceFile, settings: &LinterSettings) -> Vec<Diagnostic> {
|
||||
let Some(err) = toml::from_str::<PyProjectToml>(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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 | )
|
||||
|
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
@@ -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:
|
||||
|
|
||||
|
||||
Binary file not shown.
@@ -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::<Vec<_>>();
|
||||
let expected: Vec<_> = expected.iter().map(|rule| rule.name().as_str()).collect();
|
||||
assert_eq!(actual, expected);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Path>) -> std::path::PathBuf {
|
||||
pub(crate) fn test_path(
|
||||
path: impl AsRef<Path>,
|
||||
settings: &LinterSettings,
|
||||
) -> Result<Vec<OldDiagnostic>> {
|
||||
) -> Result<Vec<Diagnostic>> {
|
||||
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<OldDiagnostic>,
|
||||
pub(crate) diagnostics: Vec<Diagnostic>,
|
||||
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<OldDiagnostic> {
|
||||
pub fn test_snippet(contents: &str, settings: &LinterSettings) -> Vec<Diagnostic> {
|
||||
let path = Path::new("<filename>");
|
||||
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<OldDiagnostic>, Cow<'a, SourceKind>) {
|
||||
) -> (Vec<Diagnostic>, 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<OldDiagnostic>,
|
||||
path: &Path,
|
||||
source: &SourceKind,
|
||||
) -> String {
|
||||
/// Print the lint diagnostics in `diagnostics`.
|
||||
fn print_diagnostics(mut diagnostics: Vec<Diagnostic>, 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()
|
||||
|
||||
@@ -42,6 +42,12 @@ impl From<LexicalError> for ParseError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for ParseError {
|
||||
fn range(&self) -> TextRange {
|
||||
self.location
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn error(self) -> ParseErrorType {
|
||||
self.error
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<Edit>,
|
||||
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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::find_node::covering_node;
|
||||
use crate::{Db, HasNavigationTargets, NavigationTargets, RangedValue};
|
||||
use crate::{Db, HasNavigationTargets, NavigationTarget, NavigationTargets, RangedValue};
|
||||
use ruff_db::files::{File, FileRange};
|
||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_python_parser::TokenKind;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ty_python_semantic::semantic_index::definition::Definition;
|
||||
use ty_python_semantic::types::Type;
|
||||
use ty_python_semantic::{HasType, SemanticModel};
|
||||
use ty_python_semantic::{HasDefinition, HasType, SemanticModel};
|
||||
|
||||
pub fn goto_type_definition(
|
||||
db: &dyn Db,
|
||||
@@ -29,6 +30,35 @@ pub fn goto_type_definition(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn goto_definition(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
offset: TextSize,
|
||||
) -> Option<RangedValue<NavigationTargets>> {
|
||||
let module = parsed_module(db, file).load(db);
|
||||
let goto_target = find_goto_target(&module, offset)?;
|
||||
|
||||
let model = SemanticModel::new(db, file);
|
||||
let definitions = goto_target.definitions(&model)?;
|
||||
|
||||
tracing::debug!("Definitions of covering node is found");
|
||||
|
||||
let targets = definitions.into_iter().map(|definition| {
|
||||
let full_range = definition.full_range(db, &module);
|
||||
|
||||
NavigationTarget {
|
||||
file: full_range.file(),
|
||||
focus_range: definition.focus_range(db, &module).range(),
|
||||
full_range: full_range.range(),
|
||||
}
|
||||
});
|
||||
|
||||
Some(RangedValue {
|
||||
range: FileRange::new(file, goto_target.range()),
|
||||
value: NavigationTargets::unique(targets),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(crate) enum GotoTarget<'a> {
|
||||
Expression(ast::ExprRef<'a>),
|
||||
@@ -154,6 +184,16 @@ impl GotoTarget<'_> {
|
||||
|
||||
Some(ty)
|
||||
}
|
||||
|
||||
pub(crate) fn definitions<'db>(
|
||||
self,
|
||||
model: &SemanticModel<'db>,
|
||||
) -> Option<Vec<Definition<'db>>> {
|
||||
match self {
|
||||
GotoTarget::Expression(expr_ref) => expr_ref.definitions(model),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for GotoTarget<'_> {
|
||||
@@ -254,7 +294,7 @@ pub(crate) fn find_goto_target(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{CursorTest, IntoDiagnostic, cursor_test};
|
||||
use crate::{NavigationTarget, goto_type_definition};
|
||||
use crate::{NavigationTarget, goto_definition, goto_type_definition};
|
||||
use insta::assert_snapshot;
|
||||
use ruff_db::diagnostic::{
|
||||
Annotation, Diagnostic, DiagnosticId, LintName, Severity, Span, SubDiagnostic,
|
||||
@@ -828,6 +868,516 @@ f(**kwargs<CURSOR>)
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_function_call() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
def ab(a, b): ...
|
||||
|
||||
a<CURSOR>b(1, 2)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:17
|
||||
|
|
||||
2 | def ab(a, b): ...
|
||||
| ^^
|
||||
3 |
|
||||
4 | ab(1, 2)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:13
|
||||
|
|
||||
2 | def ab(a, b): ...
|
||||
3 |
|
||||
4 | ab(1, 2)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | ab = 1
|
||||
| ^^
|
||||
3 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:3:19
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_rebind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
ab = 2
|
||||
ab = 3
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:13
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | ab = 2
|
||||
4 | ab = 3
|
||||
| ^^
|
||||
5 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | ab = 2
|
||||
4 | ab = 3
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_cond_rebind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
if cond:
|
||||
ab = 2
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:13
|
||||
|
|
||||
2 | ab = 1
|
||||
| ^^
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:17
|
||||
|
|
||||
2 | ab = 1
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
| ^^
|
||||
5 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:5:19
|
||||
|
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
5 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_exhaustive_bind() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
if cond:
|
||||
ab = 2
|
||||
else:
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:3:17
|
||||
|
|
||||
2 | if cond:
|
||||
3 | ab = 2
|
||||
| ^^
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:19
|
||||
|
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
6 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:5:17
|
||||
|
|
||||
3 | ab = 2
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
| ^^
|
||||
6 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:19
|
||||
|
|
||||
4 | else:
|
||||
5 | ab = 1
|
||||
6 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_only_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_exhaustive_bind_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
if cond:
|
||||
ab = 2
|
||||
else:
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:4:17
|
||||
|
|
||||
2 | ab: int
|
||||
3 | if cond:
|
||||
4 | ab = 2
|
||||
| ^^
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
7 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | ab = 2
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
| ^^
|
||||
7 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
5 | else:
|
||||
6 | ab = 1
|
||||
7 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_load_bind_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab: int
|
||||
ab = 1
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:3:13
|
||||
|
|
||||
2 | ab: int
|
||||
3 | ab = 1
|
||||
| ^^
|
||||
4 | print(ab)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:4:19
|
||||
|
|
||||
2 | ab: int
|
||||
3 | ab = 1
|
||||
4 | print(ab)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_first_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
a<CURSOR>b = 1
|
||||
print(ab)
|
||||
ab = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_second_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(ab)
|
||||
a<CURSOR>b = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_local_loadstore() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
print(ab)
|
||||
a<CURSOR>b += 2
|
||||
print(ab)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = A<CURSOR>B(5)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:19
|
||||
|
|
||||
2 | class AB:
|
||||
| ^^
|
||||
3 | def __init__(self, val: int):
|
||||
4 | self.myval = val
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | self.myval = val
|
||||
5 |
|
||||
6 | x = AB(5)
|
||||
| ^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_implicit_instance_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = AB(5)
|
||||
print(x.my<CURSOR>val)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_explicit_instance_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
myval: int
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
x = AB(5)
|
||||
print(x.my<CURSOR>val)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_path_parent() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
def __init__(self, val: int):
|
||||
self.myval = val
|
||||
|
||||
xyz = AB(5)
|
||||
print(x<CURSOR>yz.myval)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:6:13
|
||||
|
|
||||
4 | self.myval = val
|
||||
5 |
|
||||
6 | xyz = AB(5)
|
||||
| ^^^
|
||||
7 | print(xyz.myval)
|
||||
|
|
||||
info: Source
|
||||
--> main.py:7:19
|
||||
|
|
||||
6 | xyz = AB(5)
|
||||
7 | print(xyz.myval)
|
||||
| ^^^
|
||||
|
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_class_variable() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
RED = "red"
|
||||
BLUE = "blue"
|
||||
|
||||
x = AB.RE<CURSOR>D
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_class_path_parent() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
class AB:
|
||||
RED = "red"
|
||||
BLUE = "blue"
|
||||
|
||||
x = A<CURSOR>B.RED
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @r#"
|
||||
info[goto-type-definition]: Type definition
|
||||
--> main.py:2:19
|
||||
|
|
||||
2 | class AB:
|
||||
| ^^
|
||||
3 | RED = "red"
|
||||
4 | BLUE = "blue"
|
||||
|
|
||||
info: Source
|
||||
--> main.py:6:17
|
||||
|
|
||||
4 | BLUE = "blue"
|
||||
5 |
|
||||
6 | x = AB.RED
|
||||
| ^^
|
||||
|
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_decl() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global a<CURSOR>b
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_load() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global ab
|
||||
print(a<CURSOR>b)
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No definitions found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goto_def_global_store() {
|
||||
let test = cursor_test(
|
||||
r#"
|
||||
ab = 1
|
||||
def myfunc():
|
||||
global ab
|
||||
a<CURSOR>b = 2
|
||||
"#,
|
||||
);
|
||||
|
||||
assert_snapshot!(test.goto_definition(), @"No goto target found");
|
||||
}
|
||||
|
||||
impl CursorTest {
|
||||
fn goto_type_definition(&self) -> String {
|
||||
let Some(targets) =
|
||||
@@ -847,6 +1397,24 @@ f(**kwargs<CURSOR>)
|
||||
.map(|target| GotoTypeDefinitionDiagnostic::new(source, &target)),
|
||||
)
|
||||
}
|
||||
|
||||
fn goto_definition(&self) -> String {
|
||||
let Some(targets) = goto_definition(&self.db, self.cursor.file, self.cursor.offset)
|
||||
else {
|
||||
return "No goto target found".to_string();
|
||||
};
|
||||
|
||||
if targets.is_empty() {
|
||||
return "No definitions found".to_string();
|
||||
}
|
||||
|
||||
let source = targets.range;
|
||||
self.render_diagnostics(
|
||||
targets
|
||||
.into_iter()
|
||||
.map(|target| GotoTypeDefinitionDiagnostic::new(source, &target)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct GotoTypeDefinitionDiagnostic {
|
||||
|
||||
@@ -8,7 +8,7 @@ mod markup;
|
||||
|
||||
pub use completion::completion;
|
||||
pub use db::Db;
|
||||
pub use goto::goto_type_definition;
|
||||
pub use goto::{goto_definition, goto_type_definition};
|
||||
pub use hover::hover;
|
||||
pub use inlay_hints::inlay_hints;
|
||||
pub use markup::MarkupKind;
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
|
||||
@@ -15,7 +15,7 @@ pub use program::{
|
||||
PythonVersionWithSource, SearchPathSettings,
|
||||
};
|
||||
pub use python_platform::PythonPlatform;
|
||||
pub use semantic_model::{Completion, HasType, NameKind, SemanticModel};
|
||||
pub use semantic_model::{Completion, HasDefinition, HasType, NameKind, SemanticModel};
|
||||
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
||||
pub use util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
|
||||
@@ -43,6 +43,7 @@ pub mod pull_types;
|
||||
|
||||
type FxOrderSet<V> = ordermap::set::OrderSet<V, BuildHasherDefault<FxHasher>>;
|
||||
type FxIndexMap<K, V> = indexmap::IndexMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
type FxIndexSet<V> = indexmap::IndexSet<V, BuildHasherDefault<FxHasher>>;
|
||||
|
||||
/// Returns the default registry with all known semantic lints.
|
||||
pub fn default_lint_registry() -> &'static LintRegistry {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use ruff_db::files::{File, FilePath};
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_python_ast as ast;
|
||||
use ruff_python_ast::{self as ast, ExprContext};
|
||||
use ruff_python_ast::{Expr, ExprRef, name::Name};
|
||||
use ruff_source_file::LineIndex;
|
||||
|
||||
use crate::Db;
|
||||
use crate::module_name::ModuleName;
|
||||
use crate::module_resolver::{KnownModule, Module, resolve_module};
|
||||
use crate::semantic_index::ast_ids::HasScopedUseId;
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::semantic_index::place::FileScopeId;
|
||||
use crate::semantic_index::semantic_index;
|
||||
use crate::types::ide_support::all_declarations_and_bindings;
|
||||
@@ -175,6 +177,41 @@ pub struct Completion {
|
||||
pub builtin: bool,
|
||||
}
|
||||
|
||||
pub trait HasDefinition {
|
||||
/// Returns the definitions of `self`.
|
||||
///
|
||||
/// ## Panics
|
||||
/// May panic if `self` is from another file than `model`.
|
||||
fn definitions<'db>(&self, model: &SemanticModel<'db>) -> Option<Vec<Definition<'db>>>;
|
||||
}
|
||||
|
||||
impl HasDefinition for ast::ExprRef<'_> {
|
||||
fn definitions<'db>(&self, model: &SemanticModel<'db>) -> Option<Vec<Definition<'db>>> {
|
||||
match self {
|
||||
ExprRef::Name(name) => match name.ctx {
|
||||
ExprContext::Load => {
|
||||
let index = semantic_index(model.db, model.file);
|
||||
let file_scope = index.expression_scope_id(*self);
|
||||
let scope = file_scope.to_scope_id(model.db, model.file);
|
||||
let use_def = index.use_def_map(file_scope);
|
||||
let use_id = self.scoped_use_id(model.db, scope);
|
||||
|
||||
Some(
|
||||
use_def
|
||||
.bindings_at_use(use_id)
|
||||
.filter_map(|binding| binding.binding.definition())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
ExprContext::Store => None,
|
||||
ExprContext::Del => None,
|
||||
ExprContext::Invalid => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasType {
|
||||
/// Returns the inferred type of `self`.
|
||||
///
|
||||
|
||||
@@ -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};
|
||||
@@ -21,7 +19,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
use type_ordering::union_or_intersection_elements_ordering;
|
||||
|
||||
pub(crate) use self::builder::{IntersectionBuilder, UnionBuilder};
|
||||
pub(crate) use self::cyclic::TypeVisitor;
|
||||
pub(crate) use self::cyclic::TypeTransformer;
|
||||
pub use self::diagnostic::TypeCheckDiagnostics;
|
||||
pub(crate) use self::diagnostic::register_lints;
|
||||
pub(crate) use self::infer::{
|
||||
@@ -44,12 +42,15 @@ use crate::types::diagnostic::{INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
||||
use crate::types::function::{
|
||||
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
|
||||
};
|
||||
use crate::types::generics::{GenericContext, PartialSpecialization, Specialization};
|
||||
use crate::types::generics::{
|
||||
GenericContext, PartialSpecialization, Specialization, walk_generic_context,
|
||||
walk_partial_specialization, walk_specialization,
|
||||
};
|
||||
pub use crate::types::ide_support::all_members;
|
||||
use crate::types::infer::infer_unpack_types;
|
||||
use crate::types::mro::{Mro, MroError, MroIterator};
|
||||
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
|
||||
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
|
||||
use crate::{Db, FxOrderSet, Module, Program};
|
||||
@@ -81,6 +82,7 @@ mod subclass_of;
|
||||
mod tuple;
|
||||
mod type_ordering;
|
||||
mod unpacker;
|
||||
mod visitor;
|
||||
|
||||
mod definition;
|
||||
#[cfg(test)]
|
||||
@@ -104,7 +106,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);
|
||||
@@ -370,6 +372,19 @@ pub struct PropertyInstanceType<'db> {
|
||||
setter: Option<Type<'db>>,
|
||||
}
|
||||
|
||||
fn walk_property_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
property: PropertyInstanceType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
if let Some(getter) = property.getter(db) {
|
||||
visitor.visit_type(db, getter);
|
||||
}
|
||||
if let Some(setter) = property.setter(db) {
|
||||
visitor.visit_type(db, setter);
|
||||
}
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for PropertyInstanceType<'_> {}
|
||||
|
||||
@@ -384,7 +399,7 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
Self::new(db, getter, setter)
|
||||
}
|
||||
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.getter(db).map(|ty| ty.normalized_impl(db, visitor)),
|
||||
@@ -412,14 +427,6 @@ impl<'db> PropertyInstanceType<'db> {
|
||||
self.setter(db).map(|ty| ty.materialize(db, variance)),
|
||||
)
|
||||
}
|
||||
|
||||
fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool {
|
||||
self.getter(db)
|
||||
.is_some_and(|ty| ty.any_over_type(db, type_fn))
|
||||
|| self
|
||||
.setter(db)
|
||||
.is_some_and(|ty| ty.any_over_type(db, type_fn))
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
@@ -753,110 +760,6 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `self`, or any of the types contained in `self`, match the closure passed in.
|
||||
pub fn any_over_type(self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool {
|
||||
if type_fn(self) {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::AlwaysFalsy
|
||||
| Self::AlwaysTruthy
|
||||
| Self::Never
|
||||
| Self::BooleanLiteral(_)
|
||||
| Self::BytesLiteral(_)
|
||||
| Self::ModuleLiteral(_)
|
||||
| Self::FunctionLiteral(_)
|
||||
| Self::ClassLiteral(_)
|
||||
| Self::SpecialForm(_)
|
||||
| Self::KnownInstance(_)
|
||||
| Self::StringLiteral(_)
|
||||
| Self::IntLiteral(_)
|
||||
| Self::LiteralString
|
||||
| Self::Dynamic(_)
|
||||
| Self::BoundMethod(_)
|
||||
| Self::WrapperDescriptor(_)
|
||||
| Self::MethodWrapper(_)
|
||||
| Self::DataclassDecorator(_)
|
||||
| Self::DataclassTransformer(_) => false,
|
||||
|
||||
Self::GenericAlias(generic) => generic
|
||||
.specialization(db)
|
||||
.types(db)
|
||||
.iter()
|
||||
.copied()
|
||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||
|
||||
Self::Callable(callable) => {
|
||||
let signatures = callable.signatures(db);
|
||||
signatures.iter().any(|signature| {
|
||||
signature.parameters().iter().any(|param| {
|
||||
param
|
||||
.annotated_type()
|
||||
.is_some_and(|ty| ty.any_over_type(db, type_fn))
|
||||
}) || signature
|
||||
.return_ty
|
||||
.is_some_and(|ty| ty.any_over_type(db, type_fn))
|
||||
})
|
||||
}
|
||||
|
||||
Self::SubclassOf(subclass_of) => {
|
||||
Type::from(subclass_of.subclass_of()).any_over_type(db, type_fn)
|
||||
}
|
||||
|
||||
Self::TypeVar(typevar) => match typevar.bound_or_constraints(db) {
|
||||
None => false,
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||
bound.any_over_type(db, type_fn)
|
||||
}
|
||||
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => constraints
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|constraint| constraint.any_over_type(db, type_fn)),
|
||||
},
|
||||
|
||||
Self::BoundSuper(bound_super) => {
|
||||
Type::from(bound_super.pivot_class(db)).any_over_type(db, type_fn)
|
||||
|| Type::from(bound_super.owner(db)).any_over_type(db, type_fn)
|
||||
}
|
||||
|
||||
Self::Tuple(tuple) => tuple
|
||||
.tuple(db)
|
||||
.all_elements()
|
||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||
|
||||
Self::Union(union) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||
|
||||
Self::Intersection(intersection) => {
|
||||
intersection
|
||||
.positive(db)
|
||||
.iter()
|
||||
.any(|ty| ty.any_over_type(db, type_fn))
|
||||
|| intersection
|
||||
.negative(db)
|
||||
.iter()
|
||||
.any(|ty| ty.any_over_type(db, type_fn))
|
||||
}
|
||||
|
||||
Self::ProtocolInstance(protocol) => protocol.any_over_type(db, type_fn),
|
||||
Self::PropertyInstance(property) => property.any_over_type(db, type_fn),
|
||||
|
||||
Self::NominalInstance(instance) => match instance.class {
|
||||
ClassType::NonGeneric(_) => false,
|
||||
ClassType::Generic(generic) => generic
|
||||
.specialization(db)
|
||||
.types(db)
|
||||
.iter()
|
||||
.any(|ty| ty.any_over_type(db, type_fn)),
|
||||
},
|
||||
|
||||
Self::TypeIs(type_is) => type_is.return_type(db).any_over_type(db, type_fn),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn into_class_literal(self) -> Option<ClassLiteral<'db>> {
|
||||
match self {
|
||||
Type::ClassLiteral(class_type) => Some(class_type),
|
||||
@@ -1070,12 +973,16 @@ impl<'db> Type<'db> {
|
||||
/// - Converts class-based protocols into synthesized protocols
|
||||
#[must_use]
|
||||
pub fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
let mut visitor = TypeVisitor::default();
|
||||
let mut visitor = TypeTransformer::default();
|
||||
self.normalized_impl(db, &mut visitor)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Type::Union(union) => {
|
||||
visitor.visit(self, |v| Type::Union(union.normalized_impl(db, v)))
|
||||
@@ -5735,6 +5642,22 @@ pub enum TypeMapping<'a, 'db> {
|
||||
PromoteLiterals,
|
||||
}
|
||||
|
||||
fn walk_type_mapping<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
mapping: &TypeMapping<'_, 'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match mapping {
|
||||
TypeMapping::Specialization(specialization) => {
|
||||
walk_specialization(db, *specialization, visitor);
|
||||
}
|
||||
TypeMapping::PartialSpecialization(specialization) => {
|
||||
walk_partial_specialization(db, specialization, visitor);
|
||||
}
|
||||
TypeMapping::PromoteLiterals => {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeMapping<'_, 'db> {
|
||||
fn to_owned(&self) -> TypeMapping<'db, 'db> {
|
||||
match self {
|
||||
@@ -5748,7 +5671,7 @@ impl<'db> TypeMapping<'_, 'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
TypeMapping::Specialization(specialization) => {
|
||||
TypeMapping::Specialization(specialization.normalized_impl(db, visitor))
|
||||
@@ -5799,8 +5722,27 @@ pub enum KnownInstanceType<'db> {
|
||||
TypeAliasType(TypeAliasType<'db>),
|
||||
}
|
||||
|
||||
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
known_instance: KnownInstanceType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match known_instance {
|
||||
KnownInstanceType::SubscriptedProtocol(context)
|
||||
| KnownInstanceType::SubscriptedGeneric(context) => {
|
||||
walk_generic_context(db, context, visitor);
|
||||
}
|
||||
KnownInstanceType::TypeVar(typevar) => {
|
||||
visitor.visit_type_var_type(db, typevar);
|
||||
}
|
||||
KnownInstanceType::TypeAliasType(type_alias) => {
|
||||
visitor.visit_type_alias_type(db, type_alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> KnownInstanceType<'db> {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
Self::SubscriptedProtocol(context) => {
|
||||
Self::SubscriptedProtocol(context.normalized_impl(db, visitor))
|
||||
@@ -6171,6 +6113,19 @@ pub struct TypeVarInstance<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for TypeVarInstance<'_> {}
|
||||
|
||||
fn walk_type_var_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
type_var: TypeVarInstance<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
if let Some(bounds) = type_var.bound_or_constraints(db) {
|
||||
walk_type_var_bounds(db, bounds, visitor);
|
||||
}
|
||||
if let Some(default_type) = type_var.default_ty(db) {
|
||||
visitor.visit_type(db, default_type);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeVarInstance<'db> {
|
||||
pub(crate) fn is_legacy(self, db: &'db dyn Db) -> bool {
|
||||
matches!(self.kind(db), TypeVarKind::Legacy)
|
||||
@@ -6192,7 +6147,11 @@ impl<'db> TypeVarInstance<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
@@ -6247,8 +6206,21 @@ pub enum TypeVarBoundOrConstraints<'db> {
|
||||
Constraints(UnionType<'db>),
|
||||
}
|
||||
|
||||
fn walk_type_var_bounds<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
bounds: TypeVarBoundOrConstraints<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match bounds {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => visitor.visit_type(db, bound),
|
||||
TypeVarBoundOrConstraints::Constraints(constraints) => {
|
||||
visitor.visit_union_type(db, constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound) => {
|
||||
TypeVarBoundOrConstraints::UpperBound(bound.normalized_impl(db, visitor))
|
||||
@@ -7151,6 +7123,15 @@ pub struct BoundMethodType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for BoundMethodType<'_> {}
|
||||
|
||||
fn walk_bound_method_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
method: BoundMethodType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_function_type(db, method.function(db));
|
||||
visitor.visit_type(db, method.self_instance(db));
|
||||
}
|
||||
|
||||
impl<'db> BoundMethodType<'db> {
|
||||
pub(crate) fn into_callable_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||
Type::Callable(CallableType::new(
|
||||
@@ -7166,7 +7147,7 @@ impl<'db> BoundMethodType<'db> {
|
||||
))
|
||||
}
|
||||
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.function(db).normalized_impl(db, visitor),
|
||||
@@ -7216,6 +7197,16 @@ pub struct CallableType<'db> {
|
||||
is_function_like: bool,
|
||||
}
|
||||
|
||||
pub(super) fn walk_callable_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
ty: CallableType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for signature in &ty.signatures(db).overloads {
|
||||
walk_signature(db, signature, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for CallableType<'_> {}
|
||||
|
||||
@@ -7273,7 +7264,7 @@ impl<'db> CallableType<'db> {
|
||||
/// Return a "normalized" version of this `Callable` type.
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
CallableType::new(
|
||||
db,
|
||||
self.signatures(db).normalized_impl(db, visitor),
|
||||
@@ -7340,6 +7331,30 @@ pub enum MethodWrapperKind<'db> {
|
||||
StrStartswith(StringLiteralType<'db>),
|
||||
}
|
||||
|
||||
pub(super) fn walk_method_wrapper_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
method_wrapper: MethodWrapperKind<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match method_wrapper {
|
||||
MethodWrapperKind::FunctionTypeDunderGet(function) => {
|
||||
visitor.visit_function_type(db, function);
|
||||
}
|
||||
MethodWrapperKind::FunctionTypeDunderCall(function) => {
|
||||
visitor.visit_function_type(db, function);
|
||||
}
|
||||
MethodWrapperKind::PropertyDunderGet(property) => {
|
||||
visitor.visit_property_instance_type(db, property);
|
||||
}
|
||||
MethodWrapperKind::PropertyDunderSet(property) => {
|
||||
visitor.visit_property_instance_type(db, property);
|
||||
}
|
||||
MethodWrapperKind::StrStartswith(string_literal) => {
|
||||
visitor.visit_type(db, Type::StringLiteral(string_literal));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> MethodWrapperKind<'db> {
|
||||
fn has_relation_to(self, db: &'db dyn Db, other: Self, relation: TypeRelation) -> bool {
|
||||
match (self, other) {
|
||||
@@ -7407,7 +7422,7 @@ impl<'db> MethodWrapperKind<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
MethodWrapperKind::FunctionTypeDunderGet(function) => {
|
||||
MethodWrapperKind::FunctionTypeDunderGet(function.normalized_impl(db, visitor))
|
||||
@@ -7514,6 +7529,14 @@ pub struct PEP695TypeAliasType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for PEP695TypeAliasType<'_> {}
|
||||
|
||||
fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
type_alias: PEP695TypeAliasType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, type_alias.value_type(db));
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> PEP695TypeAliasType<'db> {
|
||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||
@@ -7533,7 +7556,7 @@ impl<'db> PEP695TypeAliasType<'db> {
|
||||
definition_expression_type(db, definition, &type_alias_stmt_node.value)
|
||||
}
|
||||
|
||||
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -7553,8 +7576,16 @@ pub struct BareTypeAliasType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for BareTypeAliasType<'_> {}
|
||||
|
||||
fn walk_bare_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
type_alias: BareTypeAliasType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, type_alias.value(db));
|
||||
}
|
||||
|
||||
impl<'db> BareTypeAliasType<'db> {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.name(db),
|
||||
@@ -7572,8 +7603,27 @@ pub enum TypeAliasType<'db> {
|
||||
Bare(BareTypeAliasType<'db>),
|
||||
}
|
||||
|
||||
fn walk_type_alias_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
type_alias: TypeAliasType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match type_alias {
|
||||
TypeAliasType::PEP695(type_alias) => {
|
||||
walk_pep_695_type_alias(db, type_alias, visitor);
|
||||
}
|
||||
TypeAliasType::Bare(type_alias) => {
|
||||
walk_bare_type_alias(db, type_alias, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> TypeAliasType<'db> {
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
TypeAliasType::PEP695(type_alias) => {
|
||||
TypeAliasType::PEP695(type_alias.normalized_impl(db, visitor))
|
||||
@@ -7620,6 +7670,16 @@ pub struct UnionType<'db> {
|
||||
pub elements: Box<[Type<'db>]>,
|
||||
}
|
||||
|
||||
pub(crate) fn walk_union<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
union: UnionType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for element in union.elements(db) {
|
||||
visitor.visit_type(db, *element);
|
||||
}
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for UnionType<'_> {}
|
||||
|
||||
@@ -7789,10 +7849,14 @@ impl<'db> UnionType<'db> {
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
self.normalized_impl(db, &mut TypeVisitor::default())
|
||||
self.normalized_impl(db, &mut TypeTransformer::default())
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
let mut new_elements: Vec<Type<'db>> = self
|
||||
.elements(db)
|
||||
.iter()
|
||||
@@ -7843,6 +7907,19 @@ pub struct IntersectionType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for IntersectionType<'_> {}
|
||||
|
||||
pub(super) fn walk_intersection_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
intersection: IntersectionType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for element in intersection.positive(db) {
|
||||
visitor.visit_type(db, *element);
|
||||
}
|
||||
for element in intersection.negative(db) {
|
||||
visitor.visit_type(db, *element);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> IntersectionType<'db> {
|
||||
/// Return a new `IntersectionType` instance with the positive and negative types sorted
|
||||
/// according to a canonical ordering, and other normalizations applied to each element as applicable.
|
||||
@@ -7850,15 +7927,19 @@ impl<'db> IntersectionType<'db> {
|
||||
/// See [`Type::normalized`] for more details.
|
||||
#[must_use]
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
let mut visitor = TypeVisitor::default();
|
||||
let mut visitor = TypeTransformer::default();
|
||||
self.normalized_impl(db, &mut visitor)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
fn normalized_set<'db>(
|
||||
db: &'db dyn Db,
|
||||
elements: &FxOrderSet<Type<'db>>,
|
||||
visitor: &mut TypeVisitor<'db>,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> FxOrderSet<Type<'db>> {
|
||||
let mut elements: FxOrderSet<Type<'db>> = elements
|
||||
.iter()
|
||||
@@ -8111,7 +8192,7 @@ pub enum SuperOwnerKind<'db> {
|
||||
}
|
||||
|
||||
impl<'db> SuperOwnerKind<'db> {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
SuperOwnerKind::Dynamic(dynamic) => SuperOwnerKind::Dynamic(dynamic.normalized()),
|
||||
SuperOwnerKind::Class(class) => {
|
||||
@@ -8199,6 +8280,15 @@ pub struct BoundSuperType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for BoundSuperType<'_> {}
|
||||
|
||||
fn walk_bound_super_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
bound_super: BoundSuperType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, bound_super.pivot_class(db).into());
|
||||
visitor.visit_type(db, bound_super.owner(db).into_type());
|
||||
}
|
||||
|
||||
impl<'db> BoundSuperType<'db> {
|
||||
/// Attempts to build a `Type::BoundSuper` based on the given `pivot_class` and `owner`.
|
||||
///
|
||||
@@ -8360,7 +8450,11 @@ impl<'db> BoundSuperType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.pivot_class(db).normalized_impl(db, visitor),
|
||||
@@ -8377,6 +8471,14 @@ pub struct TypeIsType<'db> {
|
||||
place_info: Option<(ScopeId<'db>, ScopedPlaceId)>,
|
||||
}
|
||||
|
||||
fn walk_typeis_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
typeis_type: TypeIsType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, typeis_type.return_type(db));
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for TypeIsType<'_> {}
|
||||
|
||||
|
||||
@@ -15,14 +15,14 @@ use crate::semantic_index::{DeclarationWithConstraint, SemanticIndex};
|
||||
use crate::types::context::InferContext;
|
||||
use crate::types::diagnostic::{INVALID_LEGACY_TYPE_VARIABLE, INVALID_TYPE_ALIAS_TYPE};
|
||||
use crate::types::function::{DataclassTransformerParams, KnownFunction};
|
||||
use crate::types::generics::{GenericContext, Specialization};
|
||||
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
|
||||
use crate::types::infer::nearest_enclosing_class;
|
||||
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{
|
||||
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
|
||||
KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, TypeVarKind, TypeVisitor, infer_definition_types,
|
||||
KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation, TypeTransformer,
|
||||
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, infer_definition_types,
|
||||
};
|
||||
use crate::{
|
||||
Db, FxOrderSet, KnownModule, Program,
|
||||
@@ -177,11 +177,23 @@ pub struct GenericAlias<'db> {
|
||||
pub(crate) specialization: Specialization<'db>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_generic_alias<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
alias: GenericAlias<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
walk_specialization(db, alias.specialization(db), visitor);
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for GenericAlias<'_> {}
|
||||
|
||||
impl<'db> GenericAlias<'db> {
|
||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.origin(db),
|
||||
@@ -252,7 +264,11 @@ pub enum ClassType<'db> {
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> ClassType<'db> {
|
||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Self::NonGeneric(_) => self,
|
||||
Self::Generic(generic) => Self::Generic(generic.normalized_impl(db, visitor)),
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::Db;
|
||||
use crate::types::generics::Specialization;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, KnownInstanceType, MroError, MroIterator, SpecialFormType,
|
||||
Type, TypeMapping, TypeVisitor, todo_type,
|
||||
Type, TypeMapping, TypeTransformer, todo_type,
|
||||
};
|
||||
|
||||
/// Enumeration of the possible kinds of types we allow in class bases.
|
||||
@@ -31,7 +31,11 @@ impl<'db> ClassBase<'db> {
|
||||
Self::Dynamic(DynamicType::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::FxOrderSet;
|
||||
use crate::FxIndexSet;
|
||||
use crate::types::Type;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct TypeVisitor<'db> {
|
||||
seen: FxOrderSet<Type<'db>>,
|
||||
pub(crate) struct TypeTransformer<'db> {
|
||||
seen: FxIndexSet<Type<'db>>,
|
||||
}
|
||||
|
||||
impl<'db> TypeVisitor<'db> {
|
||||
impl<'db> TypeTransformer<'db> {
|
||||
pub(crate) fn visit(
|
||||
&mut self,
|
||||
ty: Type<'db>,
|
||||
|
||||
@@ -70,12 +70,13 @@ use crate::types::diagnostic::{
|
||||
report_bad_argument_to_get_protocol_members,
|
||||
report_runtime_check_against_non_runtime_checkable_protocol,
|
||||
};
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
use crate::types::visitor::any_over_type;
|
||||
use crate::types::{
|
||||
BoundMethodType, CallableType, DynamicType, KnownClass, Type, TypeMapping, TypeRelation,
|
||||
TypeVarInstance, TypeVisitor,
|
||||
TypeTransformer, TypeVarInstance, walk_type_mapping,
|
||||
};
|
||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||
|
||||
@@ -421,6 +422,16 @@ pub struct FunctionLiteral<'db> {
|
||||
inherited_generic_context: Option<GenericContext<'db>>,
|
||||
}
|
||||
|
||||
fn walk_function_literal<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
function: FunctionLiteral<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
if let Some(context) = function.inherited_generic_context(db) {
|
||||
walk_generic_context(db, context, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> FunctionLiteral<'db> {
|
||||
fn with_inherited_generic_context(
|
||||
@@ -545,7 +556,7 @@ impl<'db> FunctionLiteral<'db> {
|
||||
}))
|
||||
}
|
||||
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
let context = self
|
||||
.inherited_generic_context(db)
|
||||
.map(|ctx| ctx.normalized_impl(db, visitor));
|
||||
@@ -570,6 +581,17 @@ pub struct FunctionType<'db> {
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for FunctionType<'_> {}
|
||||
|
||||
pub(super) fn walk_function_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
function: FunctionType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
walk_function_literal(db, function.literal(db), visitor);
|
||||
for mapping in function.type_mappings(db) {
|
||||
walk_type_mapping(db, mapping, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
#[salsa::tracked]
|
||||
impl<'db> FunctionType<'db> {
|
||||
pub(crate) fn with_inherited_generic_context(
|
||||
@@ -819,11 +841,15 @@ impl<'db> FunctionType<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn normalized(self, db: &'db dyn Db) -> Self {
|
||||
let mut visitor = TypeVisitor::default();
|
||||
let mut visitor = TypeTransformer::default();
|
||||
self.normalized_impl(db, &mut visitor)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
let mappings: Box<_> = self
|
||||
.type_mappings(db)
|
||||
.iter()
|
||||
@@ -1148,8 +1174,8 @@ impl KnownFunction {
|
||||
let contains_unknown_or_todo =
|
||||
|ty| matches!(ty, Type::Dynamic(dynamic) if dynamic != DynamicType::Any);
|
||||
if source_type.is_equivalent_to(db, *casted_type)
|
||||
&& !casted_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty))
|
||||
&& !source_type.any_over_type(db, &|ty| contains_unknown_or_todo(ty))
|
||||
&& !any_over_type(db, *source_type, &contains_unknown_or_todo)
|
||||
&& !any_over_type(db, *casted_type, &contains_unknown_or_todo)
|
||||
{
|
||||
let builder = context.report_lint(&REDUNDANT_CAST, call_expression)?;
|
||||
builder.into_diagnostic(format_args!(
|
||||
|
||||
@@ -10,8 +10,8 @@ use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType
|
||||
use crate::types::signatures::{Parameter, Parameters, Signature};
|
||||
use crate::types::tuple::{TupleSpec, TupleType};
|
||||
use crate::types::{
|
||||
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
|
||||
TypeVarVariance, TypeVisitor, UnionType, declaration_type,
|
||||
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints,
|
||||
TypeVarInstance, TypeVarVariance, UnionType, declaration_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
@@ -30,6 +30,16 @@ pub struct GenericContext<'db> {
|
||||
pub(crate) variables: FxOrderSet<TypeVarInstance<'db>>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
context: GenericContext<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for typevar in context.variables(db) {
|
||||
visitor.visit_type_var_type(db, *typevar);
|
||||
}
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for GenericContext<'_> {}
|
||||
|
||||
@@ -233,7 +243,11 @@ impl<'db> GenericContext<'db> {
|
||||
Specialization::new(db, self, expanded.into_boxed_slice(), None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
let variables: FxOrderSet<_> = self
|
||||
.variables(db)
|
||||
.iter()
|
||||
@@ -279,6 +293,20 @@ pub struct Specialization<'db> {
|
||||
tuple_inner: Option<TupleType<'db>>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
specialization: Specialization<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
walk_generic_context(db, specialization.generic_context(db), visitor);
|
||||
for ty in specialization.types(db) {
|
||||
visitor.visit_type(db, *ty);
|
||||
}
|
||||
if let Some(tuple) = specialization.tuple_inner(db) {
|
||||
visitor.visit_tuple_type(db, tuple);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Specialization<'db> {
|
||||
/// Returns the tuple spec for a specialization of the `tuple` class.
|
||||
pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> {
|
||||
@@ -376,7 +404,11 @@ impl<'db> Specialization<'db> {
|
||||
Specialization::new(db, self.generic_context(db), types, None)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
let types: Box<[_]> = self
|
||||
.types(db)
|
||||
.iter()
|
||||
@@ -518,6 +550,17 @@ pub struct PartialSpecialization<'a, 'db> {
|
||||
types: Cow<'a, [Type<'db>]>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_partial_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
specialization: &PartialSpecialization<'_, 'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
walk_generic_context(db, specialization.generic_context, visitor);
|
||||
for ty in &*specialization.types {
|
||||
visitor.visit_type(db, *ty);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> PartialSpecialization<'_, 'db> {
|
||||
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
|
||||
/// mapping.
|
||||
@@ -536,7 +579,7 @@ impl<'db> PartialSpecialization<'_, 'db> {
|
||||
pub(crate) fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeVisitor<'db>,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> PartialSpecialization<'db, 'db> {
|
||||
let generic_context = self.generic_context.normalized_impl(db, visitor);
|
||||
let types: Cow<_> = self
|
||||
|
||||
@@ -5,8 +5,9 @@ use std::marker::PhantomData;
|
||||
use super::protocol_class::ProtocolInterface;
|
||||
use super::{ClassType, KnownClass, SubclassOfType, Type, TypeVarVariance};
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::types::protocol_class::walk_protocol_interface;
|
||||
use crate::types::tuple::TupleType;
|
||||
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeVarInstance, TypeVisitor};
|
||||
use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
pub(super) use synthesized_protocol::SynthesizedProtocolType;
|
||||
@@ -44,7 +45,7 @@ impl<'db> Type<'db> {
|
||||
SynthesizedProtocolType::new(
|
||||
db,
|
||||
ProtocolInterface::with_property_members(db, members),
|
||||
&mut TypeVisitor::default(),
|
||||
&mut TypeTransformer::default(),
|
||||
),
|
||||
))
|
||||
}
|
||||
@@ -74,6 +75,14 @@ pub struct NominalInstanceType<'db> {
|
||||
_phantom: PhantomData<()>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
nominal: NominalInstanceType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, nominal.class.into());
|
||||
}
|
||||
|
||||
impl<'db> NominalInstanceType<'db> {
|
||||
// Keep this method private, so that the only way of constructing `NominalInstanceType`
|
||||
// instances is through the `Type::instance` constructor function.
|
||||
@@ -84,7 +93,11 @@ impl<'db> NominalInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::from_class(self.class.normalized_impl(db, visitor))
|
||||
}
|
||||
|
||||
@@ -160,6 +173,14 @@ pub struct ProtocolInstanceType<'db> {
|
||||
_phantom: PhantomData<()>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_protocol_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
walk_protocol_interface(db, protocol.inner.interface(db), visitor);
|
||||
}
|
||||
|
||||
impl<'db> ProtocolInstanceType<'db> {
|
||||
// Keep this method private, so that the only way of constructing `ProtocolInstanceType`
|
||||
// instances is through the `Type::instance` constructor function.
|
||||
@@ -205,7 +226,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
pub(super) fn normalized(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let mut visitor = TypeVisitor::default();
|
||||
let mut visitor = TypeTransformer::default();
|
||||
self.normalized_impl(db, &mut visitor)
|
||||
}
|
||||
|
||||
@@ -215,7 +236,7 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeVisitor<'db>,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Type<'db> {
|
||||
let object = KnownClass::Object.to_instance(db);
|
||||
if object.satisfies_protocol(db, self, TypeRelation::Subtyping) {
|
||||
@@ -229,15 +250,6 @@ impl<'db> ProtocolInstanceType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the types of any of the members match the closure passed in.
|
||||
pub(super) fn any_over_type(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_fn: &dyn Fn(Type<'db>) -> bool,
|
||||
) -> bool {
|
||||
self.inner.interface(db).any_over_type(db, type_fn)
|
||||
}
|
||||
|
||||
/// Return `true` if this protocol type has the given type relation to the protocol `other`.
|
||||
///
|
||||
/// TODO: consider the types of the members as well as their existence
|
||||
@@ -348,7 +360,7 @@ impl<'db> Protocol<'db> {
|
||||
|
||||
mod synthesized_protocol {
|
||||
use crate::types::protocol_class::ProtocolInterface;
|
||||
use crate::types::{TypeMapping, TypeVarInstance, TypeVarVariance, TypeVisitor};
|
||||
use crate::types::{TypeMapping, TypeTransformer, TypeVarInstance, TypeVarVariance};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
/// A "synthesized" protocol type that is dissociated from a class definition in source code.
|
||||
@@ -369,7 +381,7 @@ mod synthesized_protocol {
|
||||
pub(super) fn new(
|
||||
db: &'db dyn Db,
|
||||
interface: ProtocolInterface<'db>,
|
||||
visitor: &mut TypeVisitor<'db>,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self(interface.normalized_impl(db, visitor))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
semantic_index::{place_table, use_def_map},
|
||||
types::{
|
||||
CallableType, ClassBase, ClassLiteral, KnownFunction, PropertyInstanceType, Signature,
|
||||
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeVarInstance, TypeVisitor,
|
||||
Type, TypeMapping, TypeQualifiers, TypeRelation, TypeTransformer, TypeVarInstance,
|
||||
signatures::{Parameter, Parameters},
|
||||
},
|
||||
};
|
||||
@@ -76,6 +76,16 @@ pub(super) struct ProtocolInterface<'db> {
|
||||
|
||||
impl get_size2::GetSize for ProtocolInterface<'_> {}
|
||||
|
||||
pub(super) fn walk_protocol_interface<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
interface: ProtocolInterface<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for member in interface.members(db) {
|
||||
walk_protocol_member(db, &member, visitor);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> ProtocolInterface<'db> {
|
||||
/// Synthesize a new protocol interface with the given members.
|
||||
///
|
||||
@@ -152,17 +162,11 @@ impl<'db> ProtocolInterface<'db> {
|
||||
.all(|member_name| other.inner(db).contains_key(member_name))
|
||||
}
|
||||
|
||||
/// Return `true` if the types of any of the members match the closure passed in.
|
||||
pub(super) fn any_over_type(
|
||||
pub(super) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
type_fn: &dyn Fn(Type<'db>) -> bool,
|
||||
) -> bool {
|
||||
self.members(db)
|
||||
.any(|member| member.any_over_type(db, type_fn))
|
||||
}
|
||||
|
||||
pub(super) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
db,
|
||||
self.inner(db)
|
||||
@@ -220,10 +224,10 @@ pub(super) struct ProtocolMemberData<'db> {
|
||||
|
||||
impl<'db> ProtocolMemberData<'db> {
|
||||
fn normalized(&self, db: &'db dyn Db) -> Self {
|
||||
self.normalized_impl(db, &mut TypeVisitor::default())
|
||||
self.normalized_impl(db, &mut TypeTransformer::default())
|
||||
}
|
||||
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
Self {
|
||||
kind: self.kind.normalized_impl(db, visitor),
|
||||
qualifiers: self.qualifiers,
|
||||
@@ -261,7 +265,7 @@ enum ProtocolMemberKind<'db> {
|
||||
}
|
||||
|
||||
impl<'db> ProtocolMemberKind<'db> {
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
match self {
|
||||
ProtocolMemberKind::Method(callable) => {
|
||||
ProtocolMemberKind::Method(callable.normalized_impl(db, visitor))
|
||||
@@ -324,6 +328,20 @@ pub(super) struct ProtocolMember<'a, 'db> {
|
||||
qualifiers: TypeQualifiers,
|
||||
}
|
||||
|
||||
fn walk_protocol_member<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
member: &ProtocolMember<'_, 'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match member.kind {
|
||||
ProtocolMemberKind::Method(method) => visitor.visit_type(db, method),
|
||||
ProtocolMemberKind::Property(property) => {
|
||||
visitor.visit_property_instance_type(db, property);
|
||||
}
|
||||
ProtocolMemberKind::Other(ty) => visitor.visit_type(db, ty),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
pub(super) fn name(&self) -> &'a str {
|
||||
self.name
|
||||
@@ -371,14 +389,6 @@ impl<'a, 'db> ProtocolMember<'a, 'db> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn any_over_type(&self, db: &'db dyn Db, type_fn: &dyn Fn(Type<'db>) -> bool) -> bool {
|
||||
match &self.kind {
|
||||
ProtocolMemberKind::Method(callable) => callable.any_over_type(db, type_fn),
|
||||
ProtocolMemberKind::Property(property) => property.any_over_type(db, type_fn),
|
||||
ProtocolMemberKind::Other(ty) => ty.any_over_type(db, type_fn),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if a declaration or binding to a given name in a protocol class body
|
||||
|
||||
@@ -15,9 +15,9 @@ use std::{collections::HashMap, slice::Iter};
|
||||
use itertools::EitherOrBoth;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
|
||||
use super::{DynamicType, Type, TypeVarVariance, TypeVisitor, definition_expression_type};
|
||||
use super::{DynamicType, Type, TypeTransformer, TypeVarVariance, definition_expression_type};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::{TypeMapping, TypeRelation, TypeVarInstance, todo_type};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
@@ -61,7 +61,11 @@ impl<'db> CallableSignature<'db> {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self::from_overloads(
|
||||
self.overloads
|
||||
.iter()
|
||||
@@ -233,6 +237,29 @@ pub struct Signature<'db> {
|
||||
pub(crate) return_ty: Option<Type<'db>>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_signature<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
signature: &Signature<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
if let Some(generic_context) = &signature.generic_context {
|
||||
walk_generic_context(db, *generic_context, visitor);
|
||||
}
|
||||
if let Some(inherited_generic_context) = &signature.inherited_generic_context {
|
||||
walk_generic_context(db, *inherited_generic_context, visitor);
|
||||
}
|
||||
// By default we usually don't visit the type of the default value,
|
||||
// as it isn't relevant to most things
|
||||
for parameter in &signature.parameters {
|
||||
if let Some(ty) = parameter.annotated_type() {
|
||||
visitor.visit_type(db, ty);
|
||||
}
|
||||
}
|
||||
if let Some(return_ty) = &signature.return_ty {
|
||||
visitor.visit_type(db, *return_ty);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> Signature<'db> {
|
||||
pub(crate) fn new(parameters: Parameters<'db>, return_ty: Option<Type<'db>>) -> Self {
|
||||
Self {
|
||||
@@ -334,7 +361,11 @@ impl<'db> Signature<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
generic_context: self
|
||||
.generic_context
|
||||
@@ -1276,7 +1307,11 @@ impl<'db> Parameter<'db> {
|
||||
/// Normalize nested unions and intersections in the annotated type, if any.
|
||||
///
|
||||
/// See [`Type::normalized`] for more details.
|
||||
pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
let Parameter {
|
||||
annotated_type,
|
||||
kind,
|
||||
|
||||
@@ -3,7 +3,7 @@ use ruff_python_ast::name::Name;
|
||||
use crate::place::PlaceAndQualifiers;
|
||||
use crate::types::{
|
||||
ClassType, DynamicType, KnownClass, MemberLookupPolicy, Type, TypeMapping, TypeRelation,
|
||||
TypeVarInstance, TypeVisitor,
|
||||
TypeTransformer, TypeVarInstance,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
|
||||
@@ -16,6 +16,14 @@ pub struct SubclassOfType<'db> {
|
||||
subclass_of: SubclassOfInner<'db>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_subclass_of_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
subclass_of: SubclassOfType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
visitor.visit_type(db, Type::from(subclass_of.subclass_of));
|
||||
}
|
||||
|
||||
impl<'db> SubclassOfType<'db> {
|
||||
/// Construct a new [`Type`] instance representing a given class object (or a given dynamic type)
|
||||
/// and all possible subclasses of that class object/dynamic type.
|
||||
@@ -171,7 +179,11 @@ impl<'db> SubclassOfType<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
Self {
|
||||
subclass_of: self.subclass_of.normalized_impl(db, visitor),
|
||||
}
|
||||
@@ -228,7 +240,11 @@ impl<'db> SubclassOfInner<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Self::Class(class) => Self::Class(class.normalized_impl(db, visitor)),
|
||||
Self::Dynamic(dynamic) => Self::Dynamic(dynamic.normalized()),
|
||||
|
||||
@@ -24,8 +24,8 @@ use itertools::{Either, EitherOrBoth, Itertools};
|
||||
|
||||
use crate::types::class::{ClassType, KnownClass};
|
||||
use crate::types::{
|
||||
Type, TypeMapping, TypeRelation, TypeVarInstance, TypeVarVariance, TypeVisitor, UnionBuilder,
|
||||
UnionType,
|
||||
Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance,
|
||||
UnionBuilder, UnionType,
|
||||
};
|
||||
use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError};
|
||||
use crate::{Db, FxOrderSet};
|
||||
@@ -88,6 +88,16 @@ pub struct TupleType<'db> {
|
||||
pub(crate) tuple: TupleSpec<'db>,
|
||||
}
|
||||
|
||||
pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
tuple: TupleType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
for element in tuple.tuple(db).all_elements() {
|
||||
visitor.visit_type(db, *element);
|
||||
}
|
||||
}
|
||||
|
||||
// The Salsa heap is tracked separately.
|
||||
impl get_size2::GetSize for TupleType<'_> {}
|
||||
|
||||
@@ -178,7 +188,7 @@ impl<'db> TupleType<'db> {
|
||||
pub(crate) fn normalized_impl(
|
||||
self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeVisitor<'db>,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Option<Self> {
|
||||
TupleType::new(db, self.tuple(db).normalized_impl(db, visitor))
|
||||
}
|
||||
@@ -332,7 +342,7 @@ impl<'db> FixedLengthTuple<Type<'db>> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeTransformer<'db>) -> Self {
|
||||
Self::from_elements(self.0.iter().map(|ty| ty.normalized_impl(db, visitor)))
|
||||
}
|
||||
|
||||
@@ -645,7 +655,11 @@ impl<'db> VariableLengthTuple<Type<'db>> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> TupleSpec<'db> {
|
||||
fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> TupleSpec<'db> {
|
||||
let prefix = self
|
||||
.prenormalized_prefix_elements(db, None)
|
||||
.map(|ty| ty.normalized_impl(db, visitor))
|
||||
@@ -985,7 +999,11 @@ impl<'db> Tuple<Type<'db>> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn normalized_impl(&self, db: &'db dyn Db, visitor: &mut TypeVisitor<'db>) -> Self {
|
||||
pub(crate) fn normalized_impl(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
visitor: &mut TypeTransformer<'db>,
|
||||
) -> Self {
|
||||
match self {
|
||||
Tuple::Fixed(tuple) => Tuple::Fixed(tuple.normalized_impl(db, visitor)),
|
||||
Tuple::Variable(tuple) => tuple.normalized_impl(db, visitor),
|
||||
|
||||
275
crates/ty_python_semantic/src/types/visitor.rs
Normal file
275
crates/ty_python_semantic/src/types/visitor.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use crate::{
|
||||
Db, FxIndexSet,
|
||||
types::{
|
||||
BoundMethodType, BoundSuperType, CallableType, GenericAlias, IntersectionType,
|
||||
KnownInstanceType, MethodWrapperKind, NominalInstanceType, PropertyInstanceType,
|
||||
ProtocolInstanceType, SubclassOfType, Type, TypeAliasType, TypeIsType, TypeVarInstance,
|
||||
UnionType,
|
||||
class::walk_generic_alias,
|
||||
function::{FunctionType, walk_function_type},
|
||||
instance::{walk_nominal_instance_type, walk_protocol_instance_type},
|
||||
subclass_of::walk_subclass_of_type,
|
||||
tuple::{TupleType, walk_tuple_type},
|
||||
walk_bound_method_type, walk_bound_super_type, walk_callable_type, walk_intersection_type,
|
||||
walk_known_instance_type, walk_method_wrapper_type, walk_property_instance_type,
|
||||
walk_type_alias_type, walk_type_var_type, walk_typeis_type, walk_union,
|
||||
},
|
||||
};
|
||||
|
||||
/// A visitor trait that recurses into nested types.
|
||||
///
|
||||
/// The trait does not guard against infinite recursion out of the box,
|
||||
/// but it makes it easy for implementors of the trait to do so.
|
||||
/// See [`any_over_type`] for an example of how to do this.
|
||||
pub(crate) trait TypeVisitor<'db> {
|
||||
fn visit_type(&mut self, db: &'db dyn Db, ty: Type<'db>);
|
||||
|
||||
fn visit_union_type(&mut self, db: &'db dyn Db, union: UnionType<'db>) {
|
||||
walk_union(db, union, self);
|
||||
}
|
||||
|
||||
fn visit_intersection_type(&mut self, db: &'db dyn Db, intersection: IntersectionType<'db>) {
|
||||
walk_intersection_type(db, intersection, self);
|
||||
}
|
||||
|
||||
fn visit_tuple_type(&mut self, db: &'db dyn Db, tuple: TupleType<'db>) {
|
||||
walk_tuple_type(db, tuple, self);
|
||||
}
|
||||
|
||||
fn visit_callable_type(&mut self, db: &'db dyn Db, callable: CallableType<'db>) {
|
||||
walk_callable_type(db, callable, self);
|
||||
}
|
||||
|
||||
fn visit_property_instance_type(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
property: PropertyInstanceType<'db>,
|
||||
) {
|
||||
walk_property_instance_type(db, property, self);
|
||||
}
|
||||
|
||||
fn visit_typeis_type(&mut self, db: &'db dyn Db, type_is: TypeIsType<'db>) {
|
||||
walk_typeis_type(db, type_is, self);
|
||||
}
|
||||
|
||||
fn visit_subclass_of_type(&mut self, db: &'db dyn Db, subclass_of: SubclassOfType<'db>) {
|
||||
walk_subclass_of_type(db, subclass_of, self);
|
||||
}
|
||||
|
||||
fn visit_generic_alias_type(&mut self, db: &'db dyn Db, alias: GenericAlias<'db>) {
|
||||
walk_generic_alias(db, alias, self);
|
||||
}
|
||||
|
||||
fn visit_function_type(&mut self, db: &'db dyn Db, function: FunctionType<'db>) {
|
||||
walk_function_type(db, function, self);
|
||||
}
|
||||
|
||||
fn visit_bound_method_type(&mut self, db: &'db dyn Db, method: BoundMethodType<'db>) {
|
||||
walk_bound_method_type(db, method, self);
|
||||
}
|
||||
|
||||
fn visit_bound_super_type(&mut self, db: &'db dyn Db, bound_super: BoundSuperType<'db>) {
|
||||
walk_bound_super_type(db, bound_super, self);
|
||||
}
|
||||
|
||||
fn visit_nominal_instance_type(&mut self, db: &'db dyn Db, nominal: NominalInstanceType<'db>) {
|
||||
walk_nominal_instance_type(db, nominal, self);
|
||||
}
|
||||
|
||||
fn visit_type_var_type(&mut self, db: &'db dyn Db, type_var: TypeVarInstance<'db>) {
|
||||
walk_type_var_type(db, type_var, self);
|
||||
}
|
||||
|
||||
fn visit_protocol_instance_type(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
protocol: ProtocolInstanceType<'db>,
|
||||
) {
|
||||
walk_protocol_instance_type(db, protocol, self);
|
||||
}
|
||||
|
||||
fn visit_method_wrapper_type(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
method_wrapper: MethodWrapperKind<'db>,
|
||||
) {
|
||||
walk_method_wrapper_type(db, method_wrapper, self);
|
||||
}
|
||||
|
||||
fn visit_known_instance_type(
|
||||
&mut self,
|
||||
db: &'db dyn Db,
|
||||
known_instance: KnownInstanceType<'db>,
|
||||
) {
|
||||
walk_known_instance_type(db, known_instance, self);
|
||||
}
|
||||
|
||||
fn visit_type_alias_type(&mut self, db: &'db dyn Db, type_alias: TypeAliasType<'db>) {
|
||||
walk_type_alias_type(db, type_alias, self);
|
||||
}
|
||||
}
|
||||
|
||||
/// Enumeration of types that may contain other types, such as unions, intersections, and generics.
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
enum NonAtomicType<'db> {
|
||||
Union(UnionType<'db>),
|
||||
Intersection(IntersectionType<'db>),
|
||||
Tuple(TupleType<'db>),
|
||||
FunctionLiteral(FunctionType<'db>),
|
||||
BoundMethod(BoundMethodType<'db>),
|
||||
BoundSuper(BoundSuperType<'db>),
|
||||
MethodWrapper(MethodWrapperKind<'db>),
|
||||
Callable(CallableType<'db>),
|
||||
GenericAlias(GenericAlias<'db>),
|
||||
KnownInstance(KnownInstanceType<'db>),
|
||||
SubclassOf(SubclassOfType<'db>),
|
||||
NominalInstance(NominalInstanceType<'db>),
|
||||
PropertyInstance(PropertyInstanceType<'db>),
|
||||
TypeIs(TypeIsType<'db>),
|
||||
TypeVar(TypeVarInstance<'db>),
|
||||
ProtocolInstance(ProtocolInstanceType<'db>),
|
||||
}
|
||||
|
||||
enum TypeKind<'db> {
|
||||
Atomic,
|
||||
NonAtomic(NonAtomicType<'db>),
|
||||
}
|
||||
|
||||
impl<'db> From<Type<'db>> for TypeKind<'db> {
|
||||
fn from(ty: Type<'db>) -> Self {
|
||||
match ty {
|
||||
Type::AlwaysFalsy
|
||||
| Type::AlwaysTruthy
|
||||
| Type::Never
|
||||
| Type::LiteralString
|
||||
| Type::IntLiteral(_)
|
||||
| Type::BooleanLiteral(_)
|
||||
| Type::StringLiteral(_)
|
||||
| Type::BytesLiteral(_)
|
||||
| Type::DataclassDecorator(_)
|
||||
| Type::DataclassTransformer(_)
|
||||
| Type::WrapperDescriptor(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::ClassLiteral(_)
|
||||
| Type::SpecialForm(_)
|
||||
| Type::Dynamic(_) => TypeKind::Atomic,
|
||||
|
||||
// Non-atomic types
|
||||
Type::FunctionLiteral(function) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::FunctionLiteral(function))
|
||||
}
|
||||
Type::Intersection(intersection) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::Intersection(intersection))
|
||||
}
|
||||
Type::Union(union) => TypeKind::NonAtomic(NonAtomicType::Union(union)),
|
||||
Type::Tuple(tuple) => TypeKind::NonAtomic(NonAtomicType::Tuple(tuple)),
|
||||
Type::BoundMethod(method) => TypeKind::NonAtomic(NonAtomicType::BoundMethod(method)),
|
||||
Type::BoundSuper(bound_super) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::BoundSuper(bound_super))
|
||||
}
|
||||
Type::MethodWrapper(method_wrapper) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::MethodWrapper(method_wrapper))
|
||||
}
|
||||
Type::Callable(callable) => TypeKind::NonAtomic(NonAtomicType::Callable(callable)),
|
||||
Type::GenericAlias(alias) => TypeKind::NonAtomic(NonAtomicType::GenericAlias(alias)),
|
||||
Type::KnownInstance(known_instance) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::KnownInstance(known_instance))
|
||||
}
|
||||
Type::SubclassOf(subclass_of) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::SubclassOf(subclass_of))
|
||||
}
|
||||
Type::NominalInstance(nominal) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::NominalInstance(nominal))
|
||||
}
|
||||
Type::ProtocolInstance(protocol) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::ProtocolInstance(protocol))
|
||||
}
|
||||
Type::PropertyInstance(property) => {
|
||||
TypeKind::NonAtomic(NonAtomicType::PropertyInstance(property))
|
||||
}
|
||||
Type::TypeVar(type_var) => TypeKind::NonAtomic(NonAtomicType::TypeVar(type_var)),
|
||||
Type::TypeIs(type_is) => TypeKind::NonAtomic(NonAtomicType::TypeIs(type_is)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>(
|
||||
db: &'db dyn Db,
|
||||
non_atomic_type: NonAtomicType<'db>,
|
||||
visitor: &mut V,
|
||||
) {
|
||||
match non_atomic_type {
|
||||
NonAtomicType::FunctionLiteral(function) => visitor.visit_function_type(db, function),
|
||||
NonAtomicType::Intersection(intersection) => {
|
||||
visitor.visit_intersection_type(db, intersection);
|
||||
}
|
||||
NonAtomicType::Union(union) => visitor.visit_union_type(db, union),
|
||||
NonAtomicType::Tuple(tuple) => visitor.visit_tuple_type(db, tuple),
|
||||
NonAtomicType::BoundMethod(method) => visitor.visit_bound_method_type(db, method),
|
||||
NonAtomicType::BoundSuper(bound_super) => visitor.visit_bound_super_type(db, bound_super),
|
||||
NonAtomicType::MethodWrapper(method_wrapper) => {
|
||||
visitor.visit_method_wrapper_type(db, method_wrapper);
|
||||
}
|
||||
NonAtomicType::Callable(callable) => visitor.visit_callable_type(db, callable),
|
||||
NonAtomicType::GenericAlias(alias) => visitor.visit_generic_alias_type(db, alias),
|
||||
NonAtomicType::KnownInstance(known_instance) => {
|
||||
visitor.visit_known_instance_type(db, known_instance);
|
||||
}
|
||||
NonAtomicType::SubclassOf(subclass_of) => visitor.visit_subclass_of_type(db, subclass_of),
|
||||
NonAtomicType::NominalInstance(nominal) => visitor.visit_nominal_instance_type(db, nominal),
|
||||
NonAtomicType::PropertyInstance(property) => {
|
||||
visitor.visit_property_instance_type(db, property);
|
||||
}
|
||||
NonAtomicType::TypeIs(type_is) => visitor.visit_typeis_type(db, type_is),
|
||||
NonAtomicType::TypeVar(type_var) => visitor.visit_type_var_type(db, type_var),
|
||||
NonAtomicType::ProtocolInstance(protocol) => {
|
||||
visitor.visit_protocol_instance_type(db, protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if `ty`, or any of the types contained in `ty`, match the closure passed in.
|
||||
///
|
||||
/// The function guards against infinite recursion
|
||||
/// by keeping track of the non-atomic types it has already seen.
|
||||
pub(super) fn any_over_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
ty: Type<'db>,
|
||||
query: &dyn Fn(Type<'db>) -> bool,
|
||||
) -> bool {
|
||||
struct AnyOverTypeVisitor<'db, 'a> {
|
||||
query: &'a dyn Fn(Type<'db>) -> bool,
|
||||
seen_types: FxIndexSet<NonAtomicType<'db>>,
|
||||
found_matching_type: bool,
|
||||
}
|
||||
|
||||
impl<'db> TypeVisitor<'db> for AnyOverTypeVisitor<'db, '_> {
|
||||
fn visit_type(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
if self.found_matching_type {
|
||||
return;
|
||||
}
|
||||
self.found_matching_type |= (self.query)(ty);
|
||||
if self.found_matching_type {
|
||||
return;
|
||||
}
|
||||
match TypeKind::from(ty) {
|
||||
TypeKind::Atomic => {}
|
||||
TypeKind::NonAtomic(non_atomic_type) => {
|
||||
if !self.seen_types.insert(non_atomic_type) {
|
||||
// If we have already seen this type, we can skip it.
|
||||
return;
|
||||
}
|
||||
walk_non_atomic_type(db, non_atomic_type, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut visitor = AnyOverTypeVisitor {
|
||||
query,
|
||||
seen_types: FxIndexSet::default(),
|
||||
found_matching_type: false,
|
||||
};
|
||||
visitor.visit_type(db, ty);
|
||||
visitor.found_matching_type
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use crate::session::{AllOptions, ClientOptions, Session};
|
||||
use lsp_server::Connection;
|
||||
use lsp_types::{
|
||||
ClientCapabilities, DiagnosticOptions, DiagnosticServerCapabilities, HoverProviderCapability,
|
||||
InlayHintOptions, InlayHintServerCapabilities, MessageType, ServerCapabilities,
|
||||
InlayHintOptions, InlayHintServerCapabilities, MessageType, OneOf, ServerCapabilities,
|
||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
||||
TypeDefinitionProviderCapability, Url,
|
||||
};
|
||||
@@ -183,6 +183,7 @@ impl Server {
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
definition_provider: Some(OneOf::Left(true)),
|
||||
type_definition_provider: Some(TypeDefinitionProviderCapability::Simple(true)),
|
||||
hover_provider: Some(HoverProviderCapability::Simple(true)),
|
||||
inlay_hint_provider: Some(lsp_types::OneOf::Right(
|
||||
|
||||
@@ -43,6 +43,9 @@ pub(super) fn request(req: server::Request) -> Task {
|
||||
>(
|
||||
req, BackgroundSchedule::Worker
|
||||
),
|
||||
requests::GotoDefinitionRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::GotoDefinitionRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
requests::HoverRequestHandler::METHOD => background_document_request_task::<
|
||||
requests::HoverRequestHandler,
|
||||
>(req, BackgroundSchedule::Worker),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
mod completion;
|
||||
mod diagnostic;
|
||||
mod goto_definition;
|
||||
mod goto_type_definition;
|
||||
mod hover;
|
||||
mod inlay_hints;
|
||||
@@ -8,6 +9,7 @@ mod workspace_diagnostic;
|
||||
|
||||
pub(super) use completion::CompletionRequestHandler;
|
||||
pub(super) use diagnostic::DocumentDiagnosticRequestHandler;
|
||||
pub(super) use goto_definition::GotoDefinitionRequestHandler;
|
||||
pub(super) use goto_type_definition::GotoTypeDefinitionRequestHandler;
|
||||
pub(super) use hover::HoverRequestHandler;
|
||||
pub(super) use inlay_hints::InlayHintRequestHandler;
|
||||
|
||||
77
crates/ty_server/src/server/api/requests/goto_definition.rs
Normal file
77
crates/ty_server/src/server/api/requests/goto_definition.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lsp_types::GotoDefinitionParams;
|
||||
use lsp_types::request::GotoDefinition;
|
||||
use lsp_types::{GotoDefinitionResponse, Url};
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
use ty_ide::goto_definition;
|
||||
use ty_project::ProjectDatabase;
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::document::{PositionExt, ToLink};
|
||||
use crate::server::api::traits::{
|
||||
BackgroundDocumentRequestHandler, RequestHandler, RetriableRequestHandler,
|
||||
};
|
||||
use crate::session::client::Client;
|
||||
|
||||
pub(crate) struct GotoDefinitionRequestHandler;
|
||||
|
||||
impl RequestHandler for GotoDefinitionRequestHandler {
|
||||
type RequestType = GotoDefinition;
|
||||
}
|
||||
|
||||
impl BackgroundDocumentRequestHandler for GotoDefinitionRequestHandler {
|
||||
fn document_url(params: &GotoDefinitionParams) -> Cow<Url> {
|
||||
Cow::Borrowed(¶ms.text_document_position_params.text_document.uri)
|
||||
}
|
||||
|
||||
fn run_with_snapshot(
|
||||
db: &ProjectDatabase,
|
||||
snapshot: DocumentSnapshot,
|
||||
_client: &Client,
|
||||
params: GotoDefinitionParams,
|
||||
) -> crate::server::Result<Option<GotoDefinitionResponse>> {
|
||||
if snapshot.client_settings().is_language_services_disabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some(file) = snapshot.file(db) else {
|
||||
tracing::debug!("Failed to resolve file for {:?}", params);
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let source = source_text(db, file);
|
||||
let line_index = line_index(db, file);
|
||||
let offset = params.text_document_position_params.position.to_text_size(
|
||||
&source,
|
||||
&line_index,
|
||||
snapshot.encoding(),
|
||||
);
|
||||
|
||||
let Some(ranged) = goto_definition(db, file, offset) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if snapshot
|
||||
.resolved_client_capabilities()
|
||||
.type_definition_link_support
|
||||
{
|
||||
let src = Some(ranged.range);
|
||||
let links: Vec<_> = ranged
|
||||
.into_iter()
|
||||
.filter_map(|target| target.to_link(db, src, snapshot.encoding()))
|
||||
.collect();
|
||||
|
||||
Ok(Some(GotoDefinitionResponse::Link(links)))
|
||||
} else {
|
||||
let locations: Vec<_> = ranged
|
||||
.into_iter()
|
||||
.filter_map(|target| target.to_location(db, snapshot.encoding()))
|
||||
.collect();
|
||||
|
||||
Ok(Some(GotoDefinitionResponse::Array(locations)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RetriableRequestHandler for GotoDefinitionRequestHandler {}
|
||||
@@ -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<Diagnostic> = 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);
|
||||
|
||||
Reference in New Issue
Block a user