mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00
Create Unknown rule diagnostics with a source range (#15648)
This commit is contained in:
parent
1e790d3885
commit
05ea77b1d4
10 changed files with 354 additions and 128 deletions
|
@ -7,7 +7,7 @@ use colored::Colorize;
|
||||||
use crossbeam::channel as crossbeam_channel;
|
use crossbeam::channel as crossbeam_channel;
|
||||||
use python_version::PythonVersion;
|
use python_version::PythonVersion;
|
||||||
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
||||||
use red_knot_project::metadata::value::RelativePathBuf;
|
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||||
use red_knot_project::watch;
|
use red_knot_project::watch;
|
||||||
use red_knot_project::watch::ProjectWatcher;
|
use red_knot_project::watch::ProjectWatcher;
|
||||||
use red_knot_project::{ProjectDatabase, ProjectMetadata};
|
use red_knot_project::{ProjectDatabase, ProjectMetadata};
|
||||||
|
@ -73,7 +73,9 @@ impl Args {
|
||||||
fn to_options(&self) -> Options {
|
fn to_options(&self) -> Options {
|
||||||
Options {
|
Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: self.python_version.map(Into::into),
|
python_version: self
|
||||||
|
.python_version
|
||||||
|
.map(|version| RangedValue::cli(version.into())),
|
||||||
venv_path: self.venv_path.as_ref().map(RelativePathBuf::cli),
|
venv_path: self.venv_path.as_ref().map(RelativePathBuf::cli),
|
||||||
typeshed: self.typeshed.as_ref().map(RelativePathBuf::cli),
|
typeshed: self.typeshed.as_ref().map(RelativePathBuf::cli),
|
||||||
extra_paths: self.extra_search_path.as_ref().map(|extra_search_paths| {
|
extra_paths: self.extra_search_path.as_ref().map(|extra_search_paths| {
|
||||||
|
|
|
@ -287,7 +287,7 @@ division-by-zer = "warn" # incorrect rule name
|
||||||
success: false
|
success: false
|
||||||
exit_code: 1
|
exit_code: 1
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
warning[unknown-rule] Unknown lint rule `division-by-zer`
|
warning[unknown-rule] <temp_dir>/pyproject.toml:3:1 Unknown lint rule `division-by-zer`
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
");
|
");
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::time::{Duration, Instant};
|
||||||
use anyhow::{anyhow, Context};
|
use anyhow::{anyhow, Context};
|
||||||
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
||||||
use red_knot_project::metadata::pyproject::{PyProject, Tool};
|
use red_knot_project::metadata::pyproject::{PyProject, Tool};
|
||||||
use red_knot_project::metadata::value::RelativePathBuf;
|
use red_knot_project::metadata::value::{RangedValue, RelativePathBuf};
|
||||||
use red_knot_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher};
|
use red_knot_project::watch::{directory_watcher, ChangeEvent, ProjectWatcher};
|
||||||
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
|
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||||
use red_knot_python_semantic::{resolve_module, ModuleName, PythonPlatform, PythonVersion};
|
use red_knot_python_semantic::{resolve_module, ModuleName, PythonPlatform, PythonVersion};
|
||||||
|
@ -897,8 +897,10 @@ print(sys.last_exc, os.getegid())
|
||||||
|_root_path, _project_path| {
|
|_root_path, _project_path| {
|
||||||
Some(Options {
|
Some(Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: Some(PythonVersion::PY311),
|
python_version: Some(RangedValue::cli(PythonVersion::PY311)),
|
||||||
python_platform: Some(PythonPlatform::Identifier("win32".to_string())),
|
python_platform: Some(RangedValue::cli(PythonPlatform::Identifier(
|
||||||
|
"win32".to_string(),
|
||||||
|
))),
|
||||||
..EnvironmentOptions::default()
|
..EnvironmentOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
|
@ -921,8 +923,10 @@ print(sys.last_exc, os.getegid())
|
||||||
// Change the python version
|
// Change the python version
|
||||||
case.update_options(Options {
|
case.update_options(Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: Some(PythonVersion::PY312),
|
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
|
||||||
python_platform: Some(PythonPlatform::Identifier("linux".to_string())),
|
python_platform: Some(RangedValue::cli(PythonPlatform::Identifier(
|
||||||
|
"linux".to_string(),
|
||||||
|
))),
|
||||||
..EnvironmentOptions::default()
|
..EnvironmentOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
|
@ -1382,7 +1386,7 @@ mod unix {
|
||||||
extra_paths: Some(vec![RelativePathBuf::cli(
|
extra_paths: Some(vec![RelativePathBuf::cli(
|
||||||
".venv/lib/python3.12/site-packages",
|
".venv/lib/python3.12/site-packages",
|
||||||
)]),
|
)]),
|
||||||
python_version: Some(PythonVersion::PY312),
|
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
|
||||||
..EnvironmentOptions::default()
|
..EnvironmentOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
|
|
|
@ -55,7 +55,7 @@ impl ProjectMetadata {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let name = project
|
let name = project
|
||||||
.and_then(|project| project.name.as_ref())
|
.and_then(|project| project.name.as_ref())
|
||||||
.map(|name| Name::new(&**name))
|
.map(|name| Name::new(&***name))
|
||||||
.unwrap_or_else(|| Name::new(root.file_name().unwrap_or("root")));
|
.unwrap_or_else(|| Name::new(root.file_name().unwrap_or("root")));
|
||||||
|
|
||||||
// TODO(https://github.com/astral-sh/ruff/issues/15491): Respect requires-python
|
// TODO(https://github.com/astral-sh/ruff/issues/15491): Respect requires-python
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::metadata::value::{RelativePathBuf, ValueSource, ValueSourceGuard};
|
use crate::metadata::value::{RangedValue, RelativePathBuf, ValueSource, ValueSourceGuard};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use red_knot_python_semantic::lint::{GetLintError, Level, RuleSelection};
|
use red_knot_python_semantic::lint::{GetLintError, Level, LintSource, RuleSelection};
|
||||||
use red_knot_python_semantic::{
|
use red_knot_python_semantic::{
|
||||||
ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings, SitePackages,
|
ProgramSettings, PythonPlatform, PythonVersion, SearchPathSettings, SitePackages,
|
||||||
};
|
};
|
||||||
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
use ruff_db::diagnostic::{Diagnostic, DiagnosticId, Severity};
|
||||||
use ruff_db::files::File;
|
use ruff_db::files::{system_path_to_file, File};
|
||||||
use ruff_db::system::{System, SystemPath};
|
use ruff_db::system::{System, SystemPath};
|
||||||
use ruff_macros::Combine;
|
use ruff_macros::Combine;
|
||||||
use ruff_text_size::TextRange;
|
use ruff_text_size::TextRange;
|
||||||
|
@ -44,7 +44,12 @@ impl Options {
|
||||||
let (python_version, python_platform) = self
|
let (python_version, python_platform) = self
|
||||||
.environment
|
.environment
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|env| (env.python_version, env.python_platform.as_ref()))
|
.map(|env| {
|
||||||
|
(
|
||||||
|
env.python_version.as_deref().copied(),
|
||||||
|
env.python_platform.as_deref(),
|
||||||
|
)
|
||||||
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
ProgramSettings {
|
ProgramSettings {
|
||||||
|
@ -116,27 +121,42 @@ impl Options {
|
||||||
.flat_map(|rules| rules.inner.iter());
|
.flat_map(|rules| rules.inner.iter());
|
||||||
|
|
||||||
for (rule_name, level) in rules {
|
for (rule_name, level) in rules {
|
||||||
|
let source = rule_name.source();
|
||||||
match registry.get(rule_name) {
|
match registry.get(rule_name) {
|
||||||
Ok(lint) => {
|
Ok(lint) => {
|
||||||
if let Ok(severity) = Severity::try_from(*level) {
|
let lint_source = match source {
|
||||||
selection.enable(lint, severity);
|
ValueSource::File(_) => LintSource::File,
|
||||||
|
ValueSource::Cli => LintSource::Cli,
|
||||||
|
};
|
||||||
|
if let Ok(severity) = Severity::try_from(**level) {
|
||||||
|
selection.enable(lint, severity, lint_source);
|
||||||
} else {
|
} else {
|
||||||
selection.disable(lint);
|
selection.disable(lint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(GetLintError::Unknown(_)) => {
|
Err(error) => {
|
||||||
diagnostics.push(OptionDiagnostic::new(
|
// `system_path_to_file` can return `Err` if the file was deleted since the configuration
|
||||||
DiagnosticId::UnknownRule,
|
// was read. This should be rare and it should be okay to default to not showing a configuration
|
||||||
format!("Unknown lint rule `{rule_name}`"),
|
// file in that case.
|
||||||
Severity::Warning,
|
let file = source
|
||||||
));
|
.file()
|
||||||
}
|
.and_then(|path| system_path_to_file(db.upcast(), path).ok());
|
||||||
Err(GetLintError::Removed(_)) => {
|
|
||||||
diagnostics.push(OptionDiagnostic::new(
|
// TODO: Add a note if the value was configured on the CLI
|
||||||
DiagnosticId::UnknownRule,
|
let diagnostic = match error {
|
||||||
format!("The lint rule `{rule_name}` has been removed and is no longer supported"),
|
GetLintError::Unknown(_) => OptionDiagnostic::new(
|
||||||
Severity::Warning,
|
DiagnosticId::UnknownRule,
|
||||||
));
|
format!("Unknown lint rule `{rule_name}`"),
|
||||||
|
Severity::Warning,
|
||||||
|
),
|
||||||
|
GetLintError::Removed(_) => OptionDiagnostic::new(
|
||||||
|
DiagnosticId::UnknownRule,
|
||||||
|
format!("Unknown lint rule `{rule_name}`"),
|
||||||
|
Severity::Warning,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
diagnostics.push(diagnostic.with_file(file).with_range(rule_name.range()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,10 +169,10 @@ impl Options {
|
||||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||||
pub struct EnvironmentOptions {
|
pub struct EnvironmentOptions {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub python_version: Option<PythonVersion>,
|
pub python_version: Option<RangedValue<PythonVersion>>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub python_platform: Option<PythonPlatform>,
|
pub python_platform: Option<RangedValue<PythonPlatform>>,
|
||||||
|
|
||||||
/// List of user-provided paths that should take first priority in the module resolution.
|
/// List of user-provided paths that should take first priority in the module resolution.
|
||||||
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
|
/// Examples in other type checkers are mypy's MYPYPATH environment variable,
|
||||||
|
@ -183,7 +203,7 @@ pub struct SrcOptions {
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Combine, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", transparent)]
|
#[serde(rename_all = "kebab-case", transparent)]
|
||||||
pub struct Rules {
|
pub struct Rules {
|
||||||
inner: FxHashMap<String, Level>,
|
inner: FxHashMap<RangedValue<String>, RangedValue<Level>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -197,6 +217,8 @@ pub struct OptionDiagnostic {
|
||||||
id: DiagnosticId,
|
id: DiagnosticId,
|
||||||
message: String,
|
message: String,
|
||||||
severity: Severity,
|
severity: Severity,
|
||||||
|
file: Option<File>,
|
||||||
|
range: Option<TextRange>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OptionDiagnostic {
|
impl OptionDiagnostic {
|
||||||
|
@ -205,8 +227,22 @@ impl OptionDiagnostic {
|
||||||
id,
|
id,
|
||||||
message,
|
message,
|
||||||
severity,
|
severity,
|
||||||
|
file: None,
|
||||||
|
range: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn with_file(mut self, file: Option<File>) -> Self {
|
||||||
|
self.file = file;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn with_range(mut self, range: Option<TextRange>) -> Self {
|
||||||
|
self.range = range;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for OptionDiagnostic {
|
impl Diagnostic for OptionDiagnostic {
|
||||||
|
@ -219,11 +255,11 @@ impl Diagnostic for OptionDiagnostic {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self) -> Option<File> {
|
fn file(&self) -> Option<File> {
|
||||||
None
|
self.file
|
||||||
}
|
}
|
||||||
|
|
||||||
fn range(&self) -> Option<TextRange> {
|
fn range(&self) -> Option<TextRange> {
|
||||||
None
|
self.range
|
||||||
}
|
}
|
||||||
|
|
||||||
fn severity(&self) -> Severity {
|
fn severity(&self) -> Severity {
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::ops::Deref;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::metadata::options::Options;
|
use crate::metadata::options::Options;
|
||||||
use crate::metadata::value::{ValueSource, ValueSourceGuard};
|
use crate::metadata::value::{RangedValue, ValueSource, ValueSourceGuard};
|
||||||
|
|
||||||
/// A `pyproject.toml` as specified in PEP 517.
|
/// A `pyproject.toml` as specified in PEP 517.
|
||||||
#[derive(Deserialize, Serialize, Debug, Default, Clone)]
|
#[derive(Deserialize, Serialize, Debug, Default, Clone)]
|
||||||
|
@ -48,11 +48,11 @@ pub struct Project {
|
||||||
///
|
///
|
||||||
/// Note: Intentionally option to be more permissive during deserialization.
|
/// Note: Intentionally option to be more permissive during deserialization.
|
||||||
/// `PackageMetadata::from_pyproject` reports missing names.
|
/// `PackageMetadata::from_pyproject` reports missing names.
|
||||||
pub name: Option<PackageName>,
|
pub name: Option<RangedValue<PackageName>>,
|
||||||
/// The version of the project
|
/// The version of the project
|
||||||
pub version: Option<Version>,
|
pub version: Option<RangedValue<Version>>,
|
||||||
/// The Python versions this project is compatible with.
|
/// The Python versions this project is compatible with.
|
||||||
pub requires_python: Option<VersionSpecifiers>,
|
pub requires_python: Option<RangedValue<VersionSpecifiers>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use crate::combine::Combine;
|
use crate::combine::Combine;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||||
|
use ruff_text_size::{TextRange, TextSize};
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::fmt;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use toml::Spanned;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ValueSource {
|
pub enum ValueSource {
|
||||||
|
@ -19,6 +23,15 @@ pub enum ValueSource {
|
||||||
Cli,
|
Cli,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ValueSource {
|
||||||
|
pub fn file(&self) -> Option<&SystemPath> {
|
||||||
|
match self {
|
||||||
|
ValueSource::File(path) => Some(&**path),
|
||||||
|
ValueSource::Cli => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
|
/// Serde doesn't provide any easy means to pass a value to a [`Deserialize`] implementation,
|
||||||
/// but we want to associate each deserialized [`RelativePath`] with the source from
|
/// but we want to associate each deserialized [`RelativePath`] with the source from
|
||||||
|
@ -49,6 +62,222 @@ impl Drop for ValueSourceGuard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A value that "remembers" where it comes from (source) and its range in source.
|
||||||
|
///
|
||||||
|
/// ## Equality, Hash, and Ordering
|
||||||
|
/// The equality, hash, and ordering are solely based on the value. They disregard the value's range
|
||||||
|
/// or source.
|
||||||
|
///
|
||||||
|
/// This ensures that two resolved configurations are identical even if the position of a value has changed
|
||||||
|
/// or if the values were loaded from different sources.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RangedValue<T> {
|
||||||
|
value: T,
|
||||||
|
source: ValueSource,
|
||||||
|
|
||||||
|
/// The byte range of `value` in `source`.
|
||||||
|
///
|
||||||
|
/// Can be `None` because not all sources support a range.
|
||||||
|
/// For example, arguments provided on the CLI won't have a range attached.
|
||||||
|
range: Option<TextRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RangedValue<T> {
|
||||||
|
pub fn new(value: T, source: ValueSource) -> Self {
|
||||||
|
Self::with_range(value, source, TextRange::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cli(value: T) -> Self {
|
||||||
|
Self::with_range(value, ValueSource::Cli, TextRange::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_range(value: T, source: ValueSource, range: TextRange) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
range: Some(range),
|
||||||
|
source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range(&self) -> Option<TextRange> {
|
||||||
|
self.range
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source(&self) -> &ValueSource {
|
||||||
|
&self.source
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_source(mut self, source: ValueSource) -> Self {
|
||||||
|
self.source = source;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Combine for RangedValue<T> {
|
||||||
|
fn combine(self, _other: Self) -> Self
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn combine_with(&mut self, _other: Self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IntoIterator for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: IntoIterator,
|
||||||
|
{
|
||||||
|
type Item = T::Item;
|
||||||
|
type IntoIter = T::IntoIter;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.value.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type already has an `iter` method thanks to `Deref`.
|
||||||
|
#[allow(clippy::into_iter_without_iter)]
|
||||||
|
impl<'a, T> IntoIterator for &'a RangedValue<T>
|
||||||
|
where
|
||||||
|
&'a T: IntoIterator,
|
||||||
|
{
|
||||||
|
type Item = <&'a T as IntoIterator>::Item;
|
||||||
|
type IntoIter = <&'a T as IntoIterator>::IntoIter;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.value.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The type already has a `into_iter_mut` method thanks to `DerefMut`.
|
||||||
|
#[allow(clippy::into_iter_without_iter)]
|
||||||
|
impl<'a, T> IntoIterator for &'a mut RangedValue<T>
|
||||||
|
where
|
||||||
|
&'a mut T: IntoIterator,
|
||||||
|
{
|
||||||
|
type Item = <&'a mut T as IntoIterator>::Item;
|
||||||
|
type IntoIter = <&'a mut T as IntoIterator>::IntoIter;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.value.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Debug for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.value.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fmt::Display for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: fmt::Display,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.value.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Deref for RangedValue<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DerefMut for RangedValue<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U: ?Sized> AsRef<U> for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: AsRef<U>,
|
||||||
|
{
|
||||||
|
fn as_ref(&self) -> &U {
|
||||||
|
self.value.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq> PartialEq for RangedValue<T> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.value.eq(&other.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq<T>> PartialEq<T> for RangedValue<T> {
|
||||||
|
fn eq(&self, other: &T) -> bool {
|
||||||
|
self.value.eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq> Eq for RangedValue<T> {}
|
||||||
|
|
||||||
|
impl<T: Hash> Hash for RangedValue<T> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.value.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialOrd> PartialOrd for RangedValue<T> {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.value.partial_cmp(&other.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialOrd<T>> PartialOrd<T> for RangedValue<T> {
|
||||||
|
fn partial_cmp(&self, other: &T) -> Option<Ordering> {
|
||||||
|
self.value.partial_cmp(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Ord> Ord for RangedValue<T> {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.value.cmp(&other.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, T> Deserialize<'de> for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let spanned: Spanned<T> = Spanned::deserialize(deserializer)?;
|
||||||
|
let span = spanned.span();
|
||||||
|
let range = TextRange::new(
|
||||||
|
TextSize::try_from(span.start).expect("Configuration file to be smaller than 4GB"),
|
||||||
|
TextSize::try_from(span.end).expect("Configuration file to be smaller than 4GB"),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(VALUE_SOURCE.with_borrow(|source| {
|
||||||
|
let source = source.clone().unwrap();
|
||||||
|
|
||||||
|
Self::with_range(spanned.into_inner(), source, range)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Serialize for RangedValue<T>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
self.value.serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A possibly relative path in a configuration file.
|
/// A possibly relative path in a configuration file.
|
||||||
///
|
///
|
||||||
/// Relative paths in configuration files or from CLI options
|
/// Relative paths in configuration files or from CLI options
|
||||||
|
@ -56,18 +285,15 @@ impl Drop for ValueSourceGuard {
|
||||||
///
|
///
|
||||||
/// * CLI: The path is relative to the current working directory
|
/// * CLI: The path is relative to the current working directory
|
||||||
/// * Configuration file: The path is relative to the project's root.
|
/// * Configuration file: The path is relative to the project's root.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(
|
||||||
pub struct RelativePathBuf {
|
Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
|
||||||
path: SystemPathBuf,
|
)]
|
||||||
source: ValueSource,
|
#[serde(transparent)]
|
||||||
}
|
pub struct RelativePathBuf(RangedValue<SystemPathBuf>);
|
||||||
|
|
||||||
impl RelativePathBuf {
|
impl RelativePathBuf {
|
||||||
pub fn new(path: impl AsRef<SystemPath>, source: ValueSource) -> Self {
|
pub fn new(path: impl AsRef<SystemPath>, source: ValueSource) -> Self {
|
||||||
Self {
|
Self(RangedValue::new(path.as_ref().to_path_buf(), source))
|
||||||
path: path.as_ref().to_path_buf(),
|
|
||||||
source,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cli(path: impl AsRef<SystemPath>) -> Self {
|
pub fn cli(path: impl AsRef<SystemPath>) -> Self {
|
||||||
|
@ -76,12 +302,12 @@ impl RelativePathBuf {
|
||||||
|
|
||||||
/// Returns the relative path as specified by the user.
|
/// Returns the relative path as specified by the user.
|
||||||
pub fn path(&self) -> &SystemPath {
|
pub fn path(&self) -> &SystemPath {
|
||||||
&self.path
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the owned relative path.
|
/// Returns the owned relative path.
|
||||||
pub fn into_path_buf(self) -> SystemPathBuf {
|
pub fn into_path_buf(self) -> SystemPathBuf {
|
||||||
self.path
|
self.0.into_inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolves the absolute path for `self` based on its origin.
|
/// Resolves the absolute path for `self` based on its origin.
|
||||||
|
@ -91,73 +317,21 @@ impl RelativePathBuf {
|
||||||
|
|
||||||
/// Resolves the absolute path for `self` based on its origin.
|
/// Resolves the absolute path for `self` based on its origin.
|
||||||
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
|
pub fn absolute(&self, project_root: &SystemPath, system: &dyn System) -> SystemPathBuf {
|
||||||
let relative_to = match &self.source {
|
let relative_to = match &self.0.source {
|
||||||
ValueSource::File(_) => project_root,
|
ValueSource::File(_) => project_root,
|
||||||
ValueSource::Cli => system.current_directory(),
|
ValueSource::Cli => system.current_directory(),
|
||||||
};
|
};
|
||||||
|
|
||||||
SystemPath::absolute(&self.path, relative_to)
|
SystemPath::absolute(&self.0, relative_to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(micha): Derive most of those implementations once `RelativePath` uses `Value`.
|
|
||||||
// and use `serde(transparent, deny_unknown_fields)`
|
|
||||||
impl Combine for RelativePathBuf {
|
impl Combine for RelativePathBuf {
|
||||||
fn combine(self, _other: Self) -> Self {
|
fn combine(self, other: Self) -> Self {
|
||||||
self
|
Self(self.0.combine(other.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
fn combine_with(&mut self, other: Self) {
|
||||||
fn combine_with(&mut self, _other: Self) {}
|
self.0.combine_with(other.0);
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for RelativePathBuf {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.path.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for RelativePathBuf {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.path.eq(&other.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for RelativePathBuf {}
|
|
||||||
|
|
||||||
impl PartialOrd for RelativePathBuf {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for RelativePathBuf {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.path.cmp(&other.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for RelativePathBuf {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
self.path.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for RelativePathBuf {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let path = SystemPathBuf::deserialize(deserializer)?;
|
|
||||||
Ok(VALUE_SOURCE.with_borrow(|source| {
|
|
||||||
let source = source
|
|
||||||
.clone()
|
|
||||||
.expect("Thread local `VALUE_SOURCE` to be set. Use `ValueSourceGuard` to set the value source before calling serde/toml `from_str`.");
|
|
||||||
|
|
||||||
Self { path, source }
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -414,7 +414,7 @@ pub struct RuleSelection {
|
||||||
/// Map with the severity for each enabled lint rule.
|
/// Map with the severity for each enabled lint rule.
|
||||||
///
|
///
|
||||||
/// If a rule isn't present in this map, then it should be considered disabled.
|
/// If a rule isn't present in this map, then it should be considered disabled.
|
||||||
lints: FxHashMap<LintId, Severity>,
|
lints: FxHashMap<LintId, (Severity, LintSource)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuleSelection {
|
impl RuleSelection {
|
||||||
|
@ -427,7 +427,7 @@ impl RuleSelection {
|
||||||
.filter_map(|lint| {
|
.filter_map(|lint| {
|
||||||
Severity::try_from(lint.default_level())
|
Severity::try_from(lint.default_level())
|
||||||
.ok()
|
.ok()
|
||||||
.map(|severity| (*lint, severity))
|
.map(|severity| (*lint, (severity, LintSource::Default)))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -441,12 +441,14 @@ impl RuleSelection {
|
||||||
|
|
||||||
/// Returns an iterator over all enabled lints and their severity.
|
/// Returns an iterator over all enabled lints and their severity.
|
||||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LintId, Severity)> + '_ {
|
pub fn iter(&self) -> impl ExactSizeIterator<Item = (LintId, Severity)> + '_ {
|
||||||
self.lints.iter().map(|(&lint, &severity)| (lint, severity))
|
self.lints
|
||||||
|
.iter()
|
||||||
|
.map(|(&lint, &(severity, _))| (lint, severity))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the configured severity for the lint with the given id or `None` if the lint is disabled.
|
/// Returns the configured severity for the lint with the given id or `None` if the lint is disabled.
|
||||||
pub fn severity(&self, lint: LintId) -> Option<Severity> {
|
pub fn severity(&self, lint: LintId) -> Option<Severity> {
|
||||||
self.lints.get(&lint).copied()
|
self.lints.get(&lint).map(|(severity, _)| *severity)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the `lint` is enabled.
|
/// Returns `true` if the `lint` is enabled.
|
||||||
|
@ -457,19 +459,25 @@ impl RuleSelection {
|
||||||
/// Enables `lint` and configures with the given `severity`.
|
/// Enables `lint` and configures with the given `severity`.
|
||||||
///
|
///
|
||||||
/// Overrides any previous configuration for the lint.
|
/// Overrides any previous configuration for the lint.
|
||||||
pub fn enable(&mut self, lint: LintId, severity: Severity) {
|
pub fn enable(&mut self, lint: LintId, severity: Severity, source: LintSource) {
|
||||||
self.lints.insert(lint, severity);
|
self.lints.insert(lint, (severity, source));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Disables `lint` if it was previously enabled.
|
/// Disables `lint` if it was previously enabled.
|
||||||
pub fn disable(&mut self, lint: LintId) {
|
pub fn disable(&mut self, lint: LintId) {
|
||||||
self.lints.remove(&lint);
|
self.lints.remove(&lint);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// Merges the enabled lints from `other` into this selection.
|
|
||||||
///
|
#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
/// Lints from `other` will override any existing configuration.
|
pub enum LintSource {
|
||||||
pub fn merge(&mut self, other: &RuleSelection) {
|
/// The user didn't enable the rule explicitly, instead it's enabled by default.
|
||||||
self.lints.extend(other.iter());
|
#[default]
|
||||||
}
|
Default,
|
||||||
|
|
||||||
|
/// The rule was enabled by using a CLI argument
|
||||||
|
Cli,
|
||||||
|
|
||||||
|
/// The rule was enabled in a configuration file.
|
||||||
|
File,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ use js_sys::Error;
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
||||||
|
use red_knot_project::metadata::value::RangedValue;
|
||||||
use red_knot_project::ProjectMetadata;
|
use red_knot_project::ProjectMetadata;
|
||||||
use red_knot_project::{Db, ProjectDatabase};
|
use red_knot_project::{Db, ProjectDatabase};
|
||||||
use ruff_db::diagnostic::Diagnostic;
|
use ruff_db::diagnostic::Diagnostic;
|
||||||
|
@ -48,7 +49,7 @@ impl Workspace {
|
||||||
|
|
||||||
workspace.apply_cli_options(Options {
|
workspace.apply_cli_options(Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: Some(settings.python_version.into()),
|
python_version: Some(RangedValue::cli(settings.python_version.into())),
|
||||||
..EnvironmentOptions::default()
|
..EnvironmentOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use rayon::ThreadPoolBuilder;
|
use rayon::ThreadPoolBuilder;
|
||||||
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
use red_knot_project::metadata::options::{EnvironmentOptions, Options};
|
||||||
|
use red_knot_project::metadata::value::RangedValue;
|
||||||
use red_knot_project::watch::{ChangeEvent, ChangedKind};
|
use red_knot_project::watch::{ChangeEvent, ChangedKind};
|
||||||
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
|
use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
|
||||||
use red_knot_python_semantic::PythonVersion;
|
use red_knot_python_semantic::PythonVersion;
|
||||||
|
@ -76,7 +77,7 @@ fn setup_case() -> Case {
|
||||||
let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap();
|
let mut metadata = ProjectMetadata::discover(src_root, &system).unwrap();
|
||||||
metadata.apply_cli_options(Options {
|
metadata.apply_cli_options(Options {
|
||||||
environment: Some(EnvironmentOptions {
|
environment: Some(EnvironmentOptions {
|
||||||
python_version: Some(PythonVersion::PY312),
|
python_version: Some(RangedValue::cli(PythonVersion::PY312)),
|
||||||
..EnvironmentOptions::default()
|
..EnvironmentOptions::default()
|
||||||
}),
|
}),
|
||||||
..Options::default()
|
..Options::default()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue