Compare commits
2 Commits
v0.4.3
...
red-knot-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93d3e6f21 | ||
|
|
523235d6ea |
1
.github/workflows/ci.yaml
vendored
1
.github/workflows/ci.yaml
vendored
@@ -59,6 +59,7 @@ jobs:
|
||||
- "!crates/ruff_python_formatter/**"
|
||||
- "!crates/ruff_formatter/**"
|
||||
- "!crates/ruff_dev/**"
|
||||
- "!crates/ruff_shrinking/**"
|
||||
- scripts/*
|
||||
- python/**
|
||||
- .github/workflows/ci.yaml
|
||||
|
||||
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,53 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.4.3
|
||||
|
||||
### Enhancements
|
||||
|
||||
- Add support for PEP 696 syntax ([#11120](https://github.com/astral-sh/ruff/pull/11120))
|
||||
|
||||
### Preview features
|
||||
|
||||
- \[`refurb`\] Use function range for `reimplemented-operator` diagnostics ([#11271](https://github.com/astral-sh/ruff/pull/11271))
|
||||
- \[`refurb`\] Ignore methods in `reimplemented-operator` (`FURB118`) ([#11270](https://github.com/astral-sh/ruff/pull/11270))
|
||||
- \[`refurb`\] Implement `fstring-number-format` (`FURB116`) ([#10921](https://github.com/astral-sh/ruff/pull/10921))
|
||||
- \[`ruff`\] Implement `redirected-noqa` (`RUF101`) ([#11052](https://github.com/astral-sh/ruff/pull/11052))
|
||||
- \[`pyflakes`\] Distinguish between first-party and third-party imports for fix suggestions ([#11168](https://github.com/astral-sh/ruff/pull/11168))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- \[`flake8-bugbear`\] Ignore non-abstract class attributes when enforcing `B024` ([#11210](https://github.com/astral-sh/ruff/pull/11210))
|
||||
- \[`flake8-logging`\] Include inline instantiations when detecting loggers ([#11154](https://github.com/astral-sh/ruff/pull/11154))
|
||||
- \[`pylint`\] Also emit `PLR0206` for properties with variadic parameters ([#11200](https://github.com/astral-sh/ruff/pull/11200))
|
||||
- \[`ruff`\] Detect duplicate codes as part of `unused-noqa` (`RUF100`) ([#10850](https://github.com/astral-sh/ruff/pull/10850))
|
||||
|
||||
### Formatter
|
||||
|
||||
- Avoid multiline expression if format specifier is present ([#11123](https://github.com/astral-sh/ruff/pull/11123))
|
||||
|
||||
### LSP
|
||||
|
||||
- Write `ruff server` setup guide for Helix ([#11183](https://github.com/astral-sh/ruff/pull/11183))
|
||||
- `ruff server` no longer hangs after shutdown ([#11222](https://github.com/astral-sh/ruff/pull/11222))
|
||||
- `ruff server` reads from a configuration TOML file in the user configuration directory if no local configuration exists ([#11225](https://github.com/astral-sh/ruff/pull/11225))
|
||||
- `ruff server` respects `per-file-ignores` configuration ([#11224](https://github.com/astral-sh/ruff/pull/11224))
|
||||
- `ruff server`: Support a custom TOML configuration file ([#11140](https://github.com/astral-sh/ruff/pull/11140))
|
||||
- `ruff server`: Support setting to prioritize project configuration over editor configuration ([#11086](https://github.com/astral-sh/ruff/pull/11086))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid debug assertion around NFKC renames ([#11249](https://github.com/astral-sh/ruff/pull/11249))
|
||||
- \[`pyflakes`\] Prioritize `redefined-while-unused` over `unused-import` ([#11173](https://github.com/astral-sh/ruff/pull/11173))
|
||||
- \[`ruff`\] Respect `async` expressions in comprehension bodies ([#11219](https://github.com/astral-sh/ruff/pull/11219))
|
||||
- \[`pygrep_hooks`\] Fix `blanket-noqa` panic when last line has noqa with no newline (`PGH004`) ([#11108](https://github.com/astral-sh/ruff/pull/11108))
|
||||
- \[`perflint`\] Ignore list-copy recommendations for async `for` loops ([#11250](https://github.com/astral-sh/ruff/pull/11250))
|
||||
- \[`pyflakes`\] Improve `invalid-print-syntax` documentation ([#11171](https://github.com/astral-sh/ruff/pull/11171))
|
||||
|
||||
### Performance
|
||||
|
||||
- Avoid allocations for isort module names ([#11251](https://github.com/astral-sh/ruff/pull/11251))
|
||||
- Build a separate ARM wheel for macOS ([#11149](https://github.com/astral-sh/ruff/pull/11149))
|
||||
|
||||
## 0.4.2
|
||||
|
||||
### Rule changes
|
||||
|
||||
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -1457,6 +1457,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
@@ -1825,9 +1835,11 @@ dependencies = [
|
||||
"notify",
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"ruff_formatter",
|
||||
"ruff_index",
|
||||
"ruff_notebook",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_python_parser",
|
||||
"ruff_python_trivia",
|
||||
"ruff_text_size",
|
||||
@@ -1943,7 +1955,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -1964,6 +1976,7 @@ dependencies = [
|
||||
"log",
|
||||
"mimalloc",
|
||||
"notify",
|
||||
"num_cpus",
|
||||
"path-absolutize",
|
||||
"rayon",
|
||||
"regex",
|
||||
@@ -2104,7 +2117,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2377,6 +2390,22 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"fs-err",
|
||||
"regex",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_parser",
|
||||
"ruff_text_size",
|
||||
"shlex",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff_source_file"
|
||||
version = "0.0.0"
|
||||
@@ -2702,6 +2731,12 @@ dependencies = [
|
||||
"dirs 5.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.5.0"
|
||||
|
||||
@@ -66,6 +66,7 @@ memchr = { version = "2.7.1" }
|
||||
mimalloc = { version = "0.1.39" }
|
||||
natord = { version = "1.0.9" }
|
||||
notify = { version = "6.1.1" }
|
||||
num_cpus = { version = "1.16.0" }
|
||||
once_cell = { version = "1.19.0" }
|
||||
path-absolutize = { version = "3.1.1" }
|
||||
path-slash = { version = "0.2.1" }
|
||||
@@ -90,6 +91,7 @@ serde_json = { version = "1.0.113" }
|
||||
serde_test = { version = "1.0.152" }
|
||||
serde_with = { version = "3.6.0", default-features = false, features = ["macros"] }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
shlex = { version = "1.3.0" }
|
||||
similar = { version = "2.4.0", features = ["inline"] }
|
||||
smallvec = { version = "1.13.2" }
|
||||
static_assertions = "1.1.0"
|
||||
|
||||
@@ -152,7 +152,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.4.3
|
||||
rev: v0.4.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -3,10 +3,5 @@ doc-valid-idents = [
|
||||
"CodeQL",
|
||||
"IPython",
|
||||
"NumPy",
|
||||
"LibCST",
|
||||
"SCREAMING_SNAKE_CASE",
|
||||
"SQLAlchemy",
|
||||
"McCabe",
|
||||
"FastAPI",
|
||||
"..",
|
||||
]
|
||||
|
||||
@@ -12,12 +12,14 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
ruff_formatter = { path = "../ruff_formatter" }
|
||||
ruff_index = { path = "../ruff_index" }
|
||||
ruff_notebook = { path = "../ruff_notebook" }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_python_parser = { path = "../ruff_python_parser" }
|
||||
ruff_python_trivia = { path = "../ruff_python_trivia" }
|
||||
ruff_text_size = { path = "../ruff_text_size" }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
|
||||
@@ -57,7 +57,6 @@ pub trait ParallelDatabase: Database + Send {
|
||||
/// We should avoid creating snapshots while running a query because we might want to adopt Salsa in the future (if we can figure out persistent caching).
|
||||
/// Unfortunately, the infrastructure doesn't provide an automated way of knowing when a query is run, that's
|
||||
/// why we have to "enforce" this constraint manually.
|
||||
#[must_use]
|
||||
fn snapshot(&self) -> Snapshot<Self>;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ type Map<K, V> = hashbrown::HashMap<K, V, ()>;
|
||||
pub struct FileId;
|
||||
|
||||
// TODO we'll need a higher level virtual file system abstraction that allows testing if a file exists
|
||||
// or retrieving its content (ideally lazily and in a way that the memory can be retained later)
|
||||
// I suspect that we'll end up with a FileSystem trait and our own Path abstraction.
|
||||
#[derive(Default)]
|
||||
// or retrieving its content (ideally lazily and in a way that the memory can be retained later)
|
||||
// I suspect that we'll end up with a FileSystem trait and our own Path abstraction.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Files {
|
||||
inner: Arc<RwLock<FilesInner>>,
|
||||
}
|
||||
@@ -36,16 +36,6 @@ impl Files {
|
||||
pub fn path(&self, id: FileId) -> Arc<Path> {
|
||||
self.inner.read().path(id)
|
||||
}
|
||||
|
||||
/// Snapshots files for a new database snapshot.
|
||||
///
|
||||
/// This method should not be used outside a database snapshot.
|
||||
#[must_use]
|
||||
pub fn snapshot(&self) -> Files {
|
||||
Files {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Files {
|
||||
@@ -73,7 +63,7 @@ struct FilesInner {
|
||||
by_path: Map<FileId, ()>,
|
||||
// TODO should we use a map here to reclaim the space for removed files?
|
||||
// TODO I think we should use our own path abstraction here to avoid having to normalize paths
|
||||
// and dealing with non-utf paths everywhere.
|
||||
// and dealing with non-utf paths everywhere.
|
||||
by_id: IndexVec<FileId, Arc<Path>>,
|
||||
}
|
||||
|
||||
|
||||
135
crates/red_knot/src/format.rs
Normal file
135
crates/red_knot/src/format.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ruff_formatter::PrintedRange;
|
||||
use ruff_python_formatter::{FormatModuleError, PyFormatOptions};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::cache::KeyValueCache;
|
||||
use crate::db::{HasJar, QueryError, SourceDb};
|
||||
use crate::files::FileId;
|
||||
use crate::lint::Diagnostics;
|
||||
use crate::FxDashSet;
|
||||
|
||||
pub(crate) trait FormatDb: SourceDb {
|
||||
/// Formats a file and returns its formatted content or an indicator that it is unchanged.
|
||||
fn format_file(&self, file_id: FileId) -> Result<FormattedFile, FormatError>;
|
||||
|
||||
/// Formats a range in a file.
|
||||
fn format_file_range(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
range: TextRange,
|
||||
) -> Result<PrintedRange, FormatError>;
|
||||
|
||||
fn check_file_formatted(&self, file_id: FileId) -> Result<Diagnostics, FormatError>;
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(db))]
|
||||
pub(crate) fn format_file<Db>(db: &Db, file_id: FileId) -> Result<FormattedFile, FormatError>
|
||||
where
|
||||
Db: FormatDb + HasJar<FormatJar>,
|
||||
{
|
||||
let formatted = &db.jar()?.formatted;
|
||||
|
||||
if formatted.contains(&file_id) {
|
||||
return Ok(FormattedFile::Unchanged);
|
||||
}
|
||||
|
||||
let source = db.source(file_id)?;
|
||||
|
||||
// TODO use the `format_module` method here to re-use the AST.
|
||||
let printed =
|
||||
ruff_python_formatter::format_module_source(source.text(), PyFormatOptions::default())?;
|
||||
|
||||
Ok(if printed.as_code() == source.text() {
|
||||
formatted.insert(file_id);
|
||||
FormattedFile::Unchanged
|
||||
} else {
|
||||
FormattedFile::Formatted(printed.into_code())
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(db))]
|
||||
pub(crate) fn format_file_range<Db: FormatDb + HasJar<FormatJar>>(
|
||||
db: &Db,
|
||||
file_id: FileId,
|
||||
range: TextRange,
|
||||
) -> Result<PrintedRange, FormatError> {
|
||||
let formatted = &db.jar()?.formatted;
|
||||
let source = db.source(file_id)?;
|
||||
|
||||
if formatted.contains(&file_id) {
|
||||
return Ok(PrintedRange::new(source.text()[range].into(), range));
|
||||
}
|
||||
|
||||
// TODO use the `format_module` method here to re-use the AST.
|
||||
|
||||
let result =
|
||||
ruff_python_formatter::format_range(source.text(), range, PyFormatOptions::default())?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Checks if the file is correctly formatted. It creates a diagnostic for formatting issues.
|
||||
#[tracing::instrument(level = "trace", skip(db))]
|
||||
pub(crate) fn check_formatted<Db>(db: &Db, file_id: FileId) -> Result<Diagnostics, FormatError>
|
||||
where
|
||||
Db: FormatDb + HasJar<FormatJar>,
|
||||
{
|
||||
Ok(if db.format_file(file_id)?.is_unchanged() {
|
||||
Diagnostics::Empty
|
||||
} else {
|
||||
Diagnostics::from(vec!["File is not formatted".to_string()])
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum FormatError {
|
||||
Format(FormatModuleError),
|
||||
Query(QueryError),
|
||||
}
|
||||
|
||||
impl From<FormatModuleError> for FormatError {
|
||||
fn from(value: FormatModuleError) -> Self {
|
||||
Self::Format(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryError> for FormatError {
|
||||
fn from(value: QueryError) -> Self {
|
||||
Self::Query(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Debug)]
|
||||
pub(crate) enum FormattedFile {
|
||||
Formatted(String),
|
||||
Unchanged,
|
||||
}
|
||||
|
||||
impl FormattedFile {
|
||||
pub(crate) const fn is_unchanged(&self) -> bool {
|
||||
matches!(self, FormattedFile::Unchanged)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FormatJar {
|
||||
pub formatted: FxDashSet<FileId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct FormattedStorage(KeyValueCache<FileId, ()>);
|
||||
|
||||
impl Deref for FormattedStorage {
|
||||
type Target = KeyValueCache<FileId, ()>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for FormattedStorage {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ pub mod cache;
|
||||
pub mod cancellation;
|
||||
pub mod db;
|
||||
pub mod files;
|
||||
mod format;
|
||||
pub mod hir;
|
||||
pub mod lint;
|
||||
pub mod module;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#![allow(clippy::dbg_macro)]
|
||||
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crossbeam::channel as crossbeam_channel;
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::subscriber::Interest;
|
||||
use tracing::{Level, Metadata};
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
@@ -11,10 +13,13 @@ use tracing_subscriber::layer::{Context, Filter, SubscriberExt};
|
||||
use tracing_subscriber::{Layer, Registry};
|
||||
use tracing_tree::time::Uptime;
|
||||
|
||||
use red_knot::db::{HasJar, ParallelDatabase, QueryError, SemanticDb, SourceDb, SourceJar};
|
||||
use red_knot::db::{
|
||||
Database, HasJar, ParallelDatabase, QueryError, SemanticDb, SourceDb, SourceJar,
|
||||
};
|
||||
use red_knot::files::FileId;
|
||||
use red_knot::module::{ModuleSearchPath, ModuleSearchPathKind};
|
||||
use red_knot::program::check::ExecutionMode;
|
||||
use red_knot::program::{FileWatcherChange, Program};
|
||||
use red_knot::program::{FileChange, FileChangeKind, Program};
|
||||
use red_knot::watch::FileWatcher;
|
||||
use red_knot::Workspace;
|
||||
|
||||
@@ -69,9 +74,12 @@ fn main() -> anyhow::Result<()> {
|
||||
let file_changes_notifier = main_loop.file_changes_notifier();
|
||||
|
||||
// Watch for file changes and re-trigger the analysis.
|
||||
let mut file_watcher = FileWatcher::new(move |changes| {
|
||||
file_changes_notifier.notify(changes);
|
||||
})?;
|
||||
let mut file_watcher = FileWatcher::new(
|
||||
move |changes| {
|
||||
file_changes_notifier.notify(changes);
|
||||
},
|
||||
program.files().clone(),
|
||||
)?;
|
||||
|
||||
file_watcher.watch_folder(workspace_folder)?;
|
||||
|
||||
@@ -132,26 +140,32 @@ impl MainLoop {
|
||||
|
||||
match message {
|
||||
MainLoopMessage::CheckProgram { revision } => {
|
||||
let program = program.snapshot();
|
||||
let sender = self.orchestrator_sender.clone();
|
||||
{
|
||||
let program = program.snapshot();
|
||||
let sender = self.orchestrator_sender.clone();
|
||||
|
||||
// Spawn a new task that checks the program. This needs to be done in a separate thread
|
||||
// to prevent blocking the main loop here.
|
||||
rayon::spawn(move || match program.check(ExecutionMode::ThreadPool) {
|
||||
Ok(result) => {
|
||||
sender
|
||||
.send(OrchestratorMessage::CheckProgramCompleted {
|
||||
diagnostics: result,
|
||||
revision,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Err(QueryError::Cancelled) => {}
|
||||
});
|
||||
// Spawn a new task that checks the program. This needs to be done in a separate thread
|
||||
// to prevent blocking the main loop here.
|
||||
rayon::spawn(move || match program.check(ExecutionMode::ThreadPool) {
|
||||
Ok(result) => {
|
||||
sender
|
||||
.send(OrchestratorMessage::CheckProgramCompleted {
|
||||
diagnostics: result,
|
||||
revision,
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Err(QueryError::Cancelled) => {}
|
||||
});
|
||||
}
|
||||
|
||||
if !program.is_cancelled() {
|
||||
let _ = program.format();
|
||||
}
|
||||
}
|
||||
MainLoopMessage::ApplyChanges(changes) => {
|
||||
// Automatically cancels any pending queries and waits for them to complete.
|
||||
program.apply_changes(changes);
|
||||
program.apply_changes(changes.iter());
|
||||
}
|
||||
MainLoopMessage::CheckCompleted(diagnostics) => {
|
||||
dbg!(diagnostics);
|
||||
@@ -178,7 +192,7 @@ struct FileChangesNotifier {
|
||||
}
|
||||
|
||||
impl FileChangesNotifier {
|
||||
fn notify(&self, changes: Vec<FileWatcherChange>) {
|
||||
fn notify(&self, changes: Vec<FileChange>) {
|
||||
self.sender
|
||||
.send(OrchestratorMessage::FileChanges(changes))
|
||||
.unwrap();
|
||||
@@ -244,7 +258,10 @@ impl Orchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
fn debounce_changes(&self, mut changes: Vec<FileWatcherChange>) {
|
||||
fn debounce_changes(&self, changes: Vec<FileChange>) {
|
||||
let mut aggregated_changes = AggregatedChanges::default();
|
||||
aggregated_changes.extend(changes);
|
||||
|
||||
loop {
|
||||
// Consume possibly incoming file change messages before running a new analysis, but don't wait for more than 100ms.
|
||||
crossbeam_channel::select! {
|
||||
@@ -254,7 +271,7 @@ impl Orchestrator {
|
||||
return self.shutdown();
|
||||
}
|
||||
Ok(OrchestratorMessage::FileChanges(file_changes)) => {
|
||||
changes.extend(file_changes);
|
||||
aggregated_changes.extend(file_changes);
|
||||
}
|
||||
|
||||
Ok(OrchestratorMessage::CheckProgramCompleted { .. })=> {
|
||||
@@ -270,7 +287,7 @@ impl Orchestrator {
|
||||
},
|
||||
default(std::time::Duration::from_millis(10)) => {
|
||||
// No more file changes after 10 ms, send the changes and schedule a new analysis
|
||||
self.sender.send(MainLoopMessage::ApplyChanges(changes)).unwrap();
|
||||
self.sender.send(MainLoopMessage::ApplyChanges(aggregated_changes)).unwrap();
|
||||
self.sender.send(MainLoopMessage::CheckProgram { revision: self.revision}).unwrap();
|
||||
return;
|
||||
}
|
||||
@@ -289,7 +306,7 @@ impl Orchestrator {
|
||||
enum MainLoopMessage {
|
||||
CheckProgram { revision: usize },
|
||||
CheckCompleted(Vec<String>),
|
||||
ApplyChanges(Vec<FileWatcherChange>),
|
||||
ApplyChanges(AggregatedChanges),
|
||||
Exit,
|
||||
}
|
||||
|
||||
@@ -303,7 +320,77 @@ enum OrchestratorMessage {
|
||||
revision: usize,
|
||||
},
|
||||
|
||||
FileChanges(Vec<FileWatcherChange>),
|
||||
FileChanges(Vec<FileChange>),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct AggregatedChanges {
|
||||
changes: FxHashMap<FileId, FileChangeKind>,
|
||||
}
|
||||
|
||||
impl AggregatedChanges {
|
||||
fn add(&mut self, change: FileChange) {
|
||||
match self.changes.entry(change.file_id()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let merged = entry.get_mut();
|
||||
|
||||
match (merged, change.kind()) {
|
||||
(FileChangeKind::Created, FileChangeKind::Deleted) => {
|
||||
// Deletion after creations means that ruff never saw the file.
|
||||
entry.remove();
|
||||
}
|
||||
(FileChangeKind::Created, FileChangeKind::Modified) => {
|
||||
// No-op, for ruff, modifying a file that it doesn't yet know that it exists is still considered a creation.
|
||||
}
|
||||
|
||||
(FileChangeKind::Modified, FileChangeKind::Created) => {
|
||||
// Uhh, that should probably not happen. Continue considering it a modification.
|
||||
}
|
||||
|
||||
(FileChangeKind::Modified, FileChangeKind::Deleted) => {
|
||||
*entry.get_mut() = FileChangeKind::Deleted;
|
||||
}
|
||||
|
||||
(FileChangeKind::Deleted, FileChangeKind::Created) => {
|
||||
*entry.get_mut() = FileChangeKind::Modified;
|
||||
}
|
||||
|
||||
(FileChangeKind::Deleted, FileChangeKind::Modified) => {
|
||||
// That's weird, but let's consider it a modification.
|
||||
*entry.get_mut() = FileChangeKind::Modified;
|
||||
}
|
||||
|
||||
(FileChangeKind::Created, FileChangeKind::Created)
|
||||
| (FileChangeKind::Modified, FileChangeKind::Modified)
|
||||
| (FileChangeKind::Deleted, FileChangeKind::Deleted) => {
|
||||
// No-op transitions. Some of them should be impossible but we handle them anyway.
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(change.kind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend<I>(&mut self, changes: I)
|
||||
where
|
||||
I: IntoIterator<Item = FileChange>,
|
||||
I::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
let iter = changes.into_iter();
|
||||
self.changes.reserve(iter.len());
|
||||
|
||||
for change in iter {
|
||||
self.add(change);
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> impl Iterator<Item = FileChange> + '_ {
|
||||
self.changes
|
||||
.iter()
|
||||
.map(|(id, kind)| FileChange::new(*id, *kind))
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_tracing() {
|
||||
|
||||
@@ -3,6 +3,7 @@ use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::db::{Database, LintDb, QueryError, QueryResult, SemanticDb};
|
||||
use crate::files::FileId;
|
||||
use crate::format::{FormatDb, FormatError};
|
||||
use crate::lint::Diagnostics;
|
||||
use crate::program::Program;
|
||||
use crate::symbols::Dependency;
|
||||
@@ -64,6 +65,18 @@ impl Program {
|
||||
if self.workspace().is_file_open(file) {
|
||||
diagnostics.extend_from_slice(&self.lint_syntax(file)?);
|
||||
diagnostics.extend_from_slice(&self.lint_semantic(file)?);
|
||||
|
||||
match self.check_file_formatted(file) {
|
||||
Ok(format_diagnostics) => {
|
||||
diagnostics.extend_from_slice(&format_diagnostics);
|
||||
}
|
||||
Err(FormatError::Query(err)) => {
|
||||
return Err(err);
|
||||
}
|
||||
Err(FormatError::Format(error)) => {
|
||||
diagnostics.push(format!("Error formatting file: {error}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Diagnostics::from(diagnostics))
|
||||
|
||||
44
crates/red_knot/src/program/format.rs
Normal file
44
crates/red_knot/src/program/format.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use crate::db::{QueryResult, SourceDb};
|
||||
use crate::format::{FormatDb, FormatError, FormattedFile};
|
||||
use crate::program::Program;
|
||||
|
||||
impl Program {
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
pub fn format(&mut self) -> QueryResult<()> {
|
||||
// Formats all open files
|
||||
|
||||
// TODO make `Executor` from `check` reusable.
|
||||
for file in self.workspace.open_files() {
|
||||
match self.format_file(file) {
|
||||
Ok(FormattedFile::Formatted(content)) => {
|
||||
let path = self.file_path(file);
|
||||
|
||||
// TODO: This is problematic because it immediately re-triggers the file watcher.
|
||||
// A possible solution is to track the self "inflicted" changes inside of programs
|
||||
// by tracking the file revision right after the write. It could then use the revision
|
||||
// to determine which changes are safe to ignore (and in which context).
|
||||
// An other alternative is to not write as part of the `format` command and instead
|
||||
// return a Vec with the format results and leave the writing to the caller.
|
||||
// I think that's undesired because a) we still need a way to tell the formatter
|
||||
// that it won't be necessary to format the content again and
|
||||
// b) it would reduce concurrency because the writing would need to wait for the file
|
||||
// formatting to be complete, unless we use some form of communication channel.
|
||||
std::fs::write(path, content).expect("Unable to write file");
|
||||
}
|
||||
Ok(FormattedFile::Unchanged) => {
|
||||
// No op
|
||||
}
|
||||
Err(FormatError::Query(error)) => {
|
||||
return Err(error);
|
||||
}
|
||||
Err(FormatError::Format(error)) => {
|
||||
// TODO proper error handling. We should either propagate this error or
|
||||
// emit a diagnostic (probably this).
|
||||
tracing::warn!("Failed to format file: {}", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::path::{Path, PathBuf};
|
||||
use ruff_formatter::PrintedRange;
|
||||
use ruff_text_size::TextRange;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::db::{
|
||||
Database, Db, DbRuntime, HasJar, HasJars, JarsStorage, LintDb, LintJar, ParallelDatabase,
|
||||
QueryResult, SemanticDb, SemanticJar, Snapshot, SourceDb, SourceJar,
|
||||
};
|
||||
use crate::files::{FileId, Files};
|
||||
use crate::format::{
|
||||
check_formatted, format_file, format_file_range, FormatDb, FormatError, FormatJar,
|
||||
FormattedFile,
|
||||
};
|
||||
use crate::lint::{lint_semantic, lint_syntax, Diagnostics};
|
||||
use crate::module::{
|
||||
add_module, file_to_module, path_to_module, resolve_module, set_module_search_paths, Module,
|
||||
@@ -21,6 +24,7 @@ use crate::types::{infer_symbol_type, Type};
|
||||
use crate::Workspace;
|
||||
|
||||
pub mod check;
|
||||
mod format;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
@@ -40,17 +44,10 @@ impl Program {
|
||||
|
||||
pub fn apply_changes<I>(&mut self, changes: I)
|
||||
where
|
||||
I: IntoIterator<Item = FileWatcherChange>,
|
||||
I: IntoIterator<Item = FileChange>,
|
||||
{
|
||||
let mut aggregated_changes = AggregatedChanges::default();
|
||||
|
||||
aggregated_changes.extend(changes.into_iter().map(|change| FileChange {
|
||||
id: self.files.intern(&change.path),
|
||||
kind: change.kind,
|
||||
}));
|
||||
|
||||
let (source, semantic, lint) = self.jars_mut();
|
||||
for change in aggregated_changes.iter() {
|
||||
let (source, semantic, lint, format) = self.jars_mut();
|
||||
for change in changes {
|
||||
semantic.module_resolver.remove_module(change.id);
|
||||
semantic.symbol_tables.remove(&change.id);
|
||||
source.sources.remove(&change.id);
|
||||
@@ -59,6 +56,7 @@ impl Program {
|
||||
semantic.type_store.remove_module(change.id);
|
||||
lint.lint_syntax.remove(&change.id);
|
||||
lint.lint_semantic.remove(&change.id);
|
||||
format.formatted.remove(&change.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +132,24 @@ impl LintDb for Program {
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatDb for Program {
|
||||
fn format_file(&self, file_id: FileId) -> Result<FormattedFile, FormatError> {
|
||||
format_file(self, file_id)
|
||||
}
|
||||
|
||||
fn format_file_range(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
range: TextRange,
|
||||
) -> Result<PrintedRange, FormatError> {
|
||||
format_file_range(self, file_id, range)
|
||||
}
|
||||
|
||||
fn check_file_formatted(&self, file_id: FileId) -> Result<Diagnostics, FormatError> {
|
||||
check_formatted(self, file_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Db for Program {}
|
||||
|
||||
impl Database for Program {
|
||||
@@ -150,14 +166,14 @@ impl ParallelDatabase for Program {
|
||||
fn snapshot(&self) -> Snapshot<Self> {
|
||||
Snapshot::new(Self {
|
||||
jars: self.jars.snapshot(),
|
||||
files: self.files.snapshot(),
|
||||
files: self.files.clone(),
|
||||
workspace: self.workspace.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HasJars for Program {
|
||||
type Jars = (SourceJar, SemanticJar, LintJar);
|
||||
type Jars = (SourceJar, SemanticJar, LintJar, FormatJar);
|
||||
|
||||
fn jars(&self) -> QueryResult<&Self::Jars> {
|
||||
self.jars.jars()
|
||||
@@ -198,30 +214,32 @@ impl HasJar<LintJar> for Program {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FileWatcherChange {
|
||||
path: PathBuf,
|
||||
kind: FileChangeKind,
|
||||
}
|
||||
impl HasJar<FormatJar> for Program {
|
||||
fn jar(&self) -> QueryResult<&FormatJar> {
|
||||
Ok(&self.jars()?.3)
|
||||
}
|
||||
|
||||
impl FileWatcherChange {
|
||||
pub fn new(path: PathBuf, kind: FileChangeKind) -> Self {
|
||||
Self { path, kind }
|
||||
fn jar_mut(&mut self) -> &mut FormatJar {
|
||||
&mut self.jars_mut().3
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
struct FileChange {
|
||||
pub struct FileChange {
|
||||
id: FileId,
|
||||
kind: FileChangeKind,
|
||||
}
|
||||
|
||||
impl FileChange {
|
||||
fn file_id(self) -> FileId {
|
||||
pub fn new(file_id: FileId, kind: FileChangeKind) -> Self {
|
||||
Self { id: file_id, kind }
|
||||
}
|
||||
|
||||
pub fn file_id(&self) -> FileId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn kind(self) -> FileChangeKind {
|
||||
pub fn kind(&self) -> FileChangeKind {
|
||||
self.kind
|
||||
}
|
||||
}
|
||||
@@ -232,74 +250,3 @@ pub enum FileChangeKind {
|
||||
Modified,
|
||||
Deleted,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct AggregatedChanges {
|
||||
changes: FxHashMap<FileId, FileChangeKind>,
|
||||
}
|
||||
|
||||
impl AggregatedChanges {
|
||||
fn add(&mut self, change: FileChange) {
|
||||
match self.changes.entry(change.file_id()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
let merged = entry.get_mut();
|
||||
|
||||
match (merged, change.kind()) {
|
||||
(FileChangeKind::Created, FileChangeKind::Deleted) => {
|
||||
// Deletion after creations means that ruff never saw the file.
|
||||
entry.remove();
|
||||
}
|
||||
(FileChangeKind::Created, FileChangeKind::Modified) => {
|
||||
// No-op, for ruff, modifying a file that it doesn't yet know that it exists is still considered a creation.
|
||||
}
|
||||
|
||||
(FileChangeKind::Modified, FileChangeKind::Created) => {
|
||||
// Uhh, that should probably not happen. Continue considering it a modification.
|
||||
}
|
||||
|
||||
(FileChangeKind::Modified, FileChangeKind::Deleted) => {
|
||||
*entry.get_mut() = FileChangeKind::Deleted;
|
||||
}
|
||||
|
||||
(FileChangeKind::Deleted, FileChangeKind::Created) => {
|
||||
*entry.get_mut() = FileChangeKind::Modified;
|
||||
}
|
||||
|
||||
(FileChangeKind::Deleted, FileChangeKind::Modified) => {
|
||||
// That's weird, but let's consider it a modification.
|
||||
*entry.get_mut() = FileChangeKind::Modified;
|
||||
}
|
||||
|
||||
(FileChangeKind::Created, FileChangeKind::Created)
|
||||
| (FileChangeKind::Modified, FileChangeKind::Modified)
|
||||
| (FileChangeKind::Deleted, FileChangeKind::Deleted) => {
|
||||
// No-op transitions. Some of them should be impossible but we handle them anyway.
|
||||
}
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(change.kind());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extend<I>(&mut self, changes: I)
|
||||
where
|
||||
I: IntoIterator<Item = FileChange>,
|
||||
{
|
||||
let iter = changes.into_iter();
|
||||
let (lower, _) = iter.size_hint();
|
||||
self.changes.reserve(lower);
|
||||
|
||||
for change in iter {
|
||||
self.add(change);
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> impl Iterator<Item = FileChange> + '_ {
|
||||
self.changes.iter().map(|(id, kind)| FileChange {
|
||||
id: *id,
|
||||
kind: *kind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ pub(crate) struct Scope {
|
||||
name: Name,
|
||||
kind: ScopeKind,
|
||||
child_scopes: Vec<ScopeId>,
|
||||
/// symbol IDs, hashed by symbol name
|
||||
// symbol IDs, hashed by symbol name
|
||||
symbols_by_name: Map<SymbolId, ()>,
|
||||
}
|
||||
|
||||
@@ -107,7 +107,6 @@ bitflags! {
|
||||
pub(crate) struct Symbol {
|
||||
name: Name,
|
||||
flags: SymbolFlags,
|
||||
scope_id: ScopeId,
|
||||
// kind: Kind,
|
||||
}
|
||||
|
||||
@@ -142,7 +141,7 @@ pub(crate) enum Definition {
|
||||
// the small amount of information we need from the AST.
|
||||
Import(ImportDefinition),
|
||||
ImportFrom(ImportFromDefinition),
|
||||
ClassDef(ClassDefinition),
|
||||
ClassDef(TypedNodeKey<ast::StmtClassDef>),
|
||||
FunctionDef(TypedNodeKey<ast::StmtFunctionDef>),
|
||||
Assignment(TypedNodeKey<ast::StmtAssign>),
|
||||
AnnotatedAssignment(TypedNodeKey<ast::StmtAnnAssign>),
|
||||
@@ -175,12 +174,6 @@ impl ImportFromDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ClassDefinition {
|
||||
pub(crate) node_key: TypedNodeKey<ast::StmtClassDef>,
|
||||
pub(crate) scope_id: ScopeId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Dependency {
|
||||
Module(ModuleName),
|
||||
@@ -339,11 +332,7 @@ impl SymbolTable {
|
||||
*entry.key()
|
||||
}
|
||||
RawEntryMut::Vacant(entry) => {
|
||||
let id = self.symbols_by_id.push(Symbol {
|
||||
name,
|
||||
flags,
|
||||
scope_id,
|
||||
});
|
||||
let id = self.symbols_by_id.push(Symbol { name, flags });
|
||||
entry.insert_with_hasher(hash, id, (), |_| hash);
|
||||
id
|
||||
}
|
||||
@@ -470,8 +459,8 @@ impl SymbolTableBuilder {
|
||||
symbol_id
|
||||
}
|
||||
|
||||
fn push_scope(&mut self, name: &str, kind: ScopeKind) -> ScopeId {
|
||||
let scope_id = self.table.add_child_scope(self.cur_scope(), name, kind);
|
||||
fn push_scope(&mut self, child_of: ScopeId, name: &str, kind: ScopeKind) -> ScopeId {
|
||||
let scope_id = self.table.add_child_scope(child_of, name, kind);
|
||||
self.scopes.push(scope_id);
|
||||
scope_id
|
||||
}
|
||||
@@ -493,10 +482,10 @@ impl SymbolTableBuilder {
|
||||
&mut self,
|
||||
name: &str,
|
||||
params: &Option<Box<ast::TypeParams>>,
|
||||
nested: impl FnOnce(&mut Self) -> ScopeId,
|
||||
) -> ScopeId {
|
||||
nested: impl FnOnce(&mut Self),
|
||||
) {
|
||||
if let Some(type_params) = params {
|
||||
self.push_scope(name, ScopeKind::Annotation);
|
||||
self.push_scope(self.cur_scope(), name, ScopeKind::Annotation);
|
||||
for type_param in &type_params.type_params {
|
||||
let name = match type_param {
|
||||
ast::TypeParam::TypeVar(ast::TypeParamTypeVar { name, .. }) => name,
|
||||
@@ -506,11 +495,10 @@ impl SymbolTableBuilder {
|
||||
self.add_or_update_symbol(name, SymbolFlags::IS_DEFINED);
|
||||
}
|
||||
}
|
||||
let scope_id = nested(self);
|
||||
nested(self);
|
||||
if params.is_some() {
|
||||
self.pop_scope();
|
||||
}
|
||||
scope_id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,26 +525,21 @@ impl PreorderVisitor<'_> for SymbolTableBuilder {
|
||||
// TODO need to capture more definition statements here
|
||||
match stmt {
|
||||
ast::Stmt::ClassDef(node) => {
|
||||
let scope_id = self.with_type_params(&node.name, &node.type_params, |builder| {
|
||||
let scope_id = builder.push_scope(&node.name, ScopeKind::Class);
|
||||
let def = Definition::ClassDef(TypedNodeKey::from_node(node));
|
||||
self.add_or_update_symbol_with_def(&node.name, def);
|
||||
self.with_type_params(&node.name, &node.type_params, |builder| {
|
||||
builder.push_scope(builder.cur_scope(), &node.name, ScopeKind::Class);
|
||||
ast::visitor::preorder::walk_stmt(builder, stmt);
|
||||
builder.pop_scope();
|
||||
scope_id
|
||||
});
|
||||
let def = Definition::ClassDef(ClassDefinition {
|
||||
node_key: TypedNodeKey::from_node(node),
|
||||
scope_id,
|
||||
});
|
||||
self.add_or_update_symbol_with_def(&node.name, def);
|
||||
}
|
||||
ast::Stmt::FunctionDef(node) => {
|
||||
let def = Definition::FunctionDef(TypedNodeKey::from_node(node));
|
||||
self.add_or_update_symbol_with_def(&node.name, def);
|
||||
self.with_type_params(&node.name, &node.type_params, |builder| {
|
||||
let scope_id = builder.push_scope(&node.name, ScopeKind::Function);
|
||||
builder.push_scope(builder.cur_scope(), &node.name, ScopeKind::Function);
|
||||
ast::visitor::preorder::walk_stmt(builder, stmt);
|
||||
builder.pop_scope();
|
||||
scope_id
|
||||
});
|
||||
}
|
||||
ast::Stmt::Import(ast::StmtImport { names, .. }) => {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#![allow(dead_code)]
|
||||
use crate::ast_ids::NodeKey;
|
||||
use crate::db::{HasJar, QueryResult, SemanticDb, SemanticJar};
|
||||
use crate::files::FileId;
|
||||
use crate::symbols::{ScopeId, SymbolId};
|
||||
use crate::symbols::SymbolId;
|
||||
use crate::{FxDashMap, FxIndexSet, Name};
|
||||
use ruff_index::{newtype_index, IndexVec};
|
||||
use rustc_hash::FxHashMap;
|
||||
@@ -120,20 +119,12 @@ impl TypeStore {
|
||||
self.modules.get(&file_id)
|
||||
}
|
||||
|
||||
fn add_function(&self, file_id: FileId, name: &str, decorators: Vec<Type>) -> FunctionTypeId {
|
||||
self.add_or_get_module(file_id)
|
||||
.add_function(name, decorators)
|
||||
fn add_function(&self, file_id: FileId, name: &str) -> FunctionTypeId {
|
||||
self.add_or_get_module(file_id).add_function(name)
|
||||
}
|
||||
|
||||
fn add_class(
|
||||
&self,
|
||||
file_id: FileId,
|
||||
name: &str,
|
||||
scope_id: ScopeId,
|
||||
bases: Vec<Type>,
|
||||
) -> ClassTypeId {
|
||||
self.add_or_get_module(file_id)
|
||||
.add_class(name, scope_id, bases)
|
||||
fn add_class(&self, file_id: FileId, name: &str, bases: Vec<Type>) -> ClassTypeId {
|
||||
self.add_or_get_module(file_id).add_class(name, bases)
|
||||
}
|
||||
|
||||
fn add_union(&mut self, file_id: FileId, elems: &[Type]) -> UnionTypeId {
|
||||
@@ -261,24 +252,6 @@ pub struct ClassTypeId {
|
||||
class_id: ModuleClassTypeId,
|
||||
}
|
||||
|
||||
impl ClassTypeId {
|
||||
fn get_own_class_member<Db>(self, db: &Db, name: &Name) -> QueryResult<Option<Type>>
|
||||
where
|
||||
Db: SemanticDb + HasJar<SemanticJar>,
|
||||
{
|
||||
// TODO: this should distinguish instance-only members (e.g. `x: int`) and not return them
|
||||
let ClassType { scope_id, .. } = *db.jar()?.type_store.get_class(self);
|
||||
let table = db.symbol_table(self.file_id)?;
|
||||
if let Some(symbol_id) = table.symbol_id_by_name(scope_id, name) {
|
||||
Ok(Some(db.infer_symbol_type(self.file_id, symbol_id)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: get_own_instance_member, get_class_member, get_instance_member
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct UnionTypeId {
|
||||
file_id: FileId,
|
||||
@@ -333,10 +306,9 @@ impl ModuleTypeStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_function(&mut self, name: &str, decorators: Vec<Type>) -> FunctionTypeId {
|
||||
fn add_function(&mut self, name: &str) -> FunctionTypeId {
|
||||
let func_id = self.functions.push(FunctionType {
|
||||
name: Name::new(name),
|
||||
decorators,
|
||||
});
|
||||
FunctionTypeId {
|
||||
file_id: self.file_id,
|
||||
@@ -344,10 +316,9 @@ impl ModuleTypeStore {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_class(&mut self, name: &str, scope_id: ScopeId, bases: Vec<Type>) -> ClassTypeId {
|
||||
fn add_class(&mut self, name: &str, bases: Vec<Type>) -> ClassTypeId {
|
||||
let class_id = self.classes.push(ClassType {
|
||||
name: Name::new(name),
|
||||
scope_id,
|
||||
// TODO: if no bases are given, that should imply [object]
|
||||
bases,
|
||||
});
|
||||
@@ -432,11 +403,7 @@ impl std::fmt::Display for DisplayType<'_> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ClassType {
|
||||
/// Name of the class at definition
|
||||
name: Name,
|
||||
/// `ScopeId` of the class body
|
||||
pub(crate) scope_id: ScopeId,
|
||||
/// Types of all class bases
|
||||
bases: Vec<Type>,
|
||||
}
|
||||
|
||||
@@ -453,17 +420,12 @@ impl ClassType {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FunctionType {
|
||||
name: Name,
|
||||
decorators: Vec<Type>,
|
||||
}
|
||||
|
||||
impl FunctionType {
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn decorators(&self) -> &[Type] {
|
||||
self.decorators.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -527,7 +489,6 @@ impl IntersectionType {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::files::Files;
|
||||
use crate::symbols::SymbolTable;
|
||||
use crate::types::{Type, TypeStore};
|
||||
use crate::FxIndexSet;
|
||||
use std::path::Path;
|
||||
@@ -537,7 +498,7 @@ mod tests {
|
||||
let store = TypeStore::default();
|
||||
let files = Files::default();
|
||||
let file_id = files.intern(Path::new("/foo"));
|
||||
let id = store.add_class(file_id, "C", SymbolTable::root_scope_id(), Vec::new());
|
||||
let id = store.add_class(file_id, "C", Vec::new());
|
||||
assert_eq!(store.get_class(id).name(), "C");
|
||||
let inst = Type::Instance(id);
|
||||
assert_eq!(format!("{}", inst.display(&store)), "C");
|
||||
@@ -548,9 +509,8 @@ mod tests {
|
||||
let store = TypeStore::default();
|
||||
let files = Files::default();
|
||||
let file_id = files.intern(Path::new("/foo"));
|
||||
let id = store.add_function(file_id, "func", vec![Type::Unknown]);
|
||||
let id = store.add_function(file_id, "func");
|
||||
assert_eq!(store.get_function(id).name(), "func");
|
||||
assert_eq!(store.get_function(id).decorators(), vec![Type::Unknown]);
|
||||
let func = Type::Function(id);
|
||||
assert_eq!(format!("{}", func.display(&store)), "func");
|
||||
}
|
||||
@@ -560,8 +520,8 @@ mod tests {
|
||||
let mut store = TypeStore::default();
|
||||
let files = Files::default();
|
||||
let file_id = files.intern(Path::new("/foo"));
|
||||
let c1 = store.add_class(file_id, "C1", SymbolTable::root_scope_id(), Vec::new());
|
||||
let c2 = store.add_class(file_id, "C2", SymbolTable::root_scope_id(), Vec::new());
|
||||
let c1 = store.add_class(file_id, "C1", Vec::new());
|
||||
let c2 = store.add_class(file_id, "C2", Vec::new());
|
||||
let elems = vec![Type::Instance(c1), Type::Instance(c2)];
|
||||
let id = store.add_union(file_id, &elems);
|
||||
assert_eq!(
|
||||
@@ -577,9 +537,9 @@ mod tests {
|
||||
let mut store = TypeStore::default();
|
||||
let files = Files::default();
|
||||
let file_id = files.intern(Path::new("/foo"));
|
||||
let c1 = store.add_class(file_id, "C1", SymbolTable::root_scope_id(), Vec::new());
|
||||
let c2 = store.add_class(file_id, "C2", SymbolTable::root_scope_id(), Vec::new());
|
||||
let c3 = store.add_class(file_id, "C3", SymbolTable::root_scope_id(), Vec::new());
|
||||
let c1 = store.add_class(file_id, "C1", Vec::new());
|
||||
let c2 = store.add_class(file_id, "C2", Vec::new());
|
||||
let c3 = store.add_class(file_id, "C3", Vec::new());
|
||||
let pos = vec![Type::Instance(c1), Type::Instance(c2)];
|
||||
let neg = vec![Type::Instance(c3)];
|
||||
let id = store.add_intersection(file_id, &pos, &neg);
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_python_ast::AstNode;
|
||||
|
||||
use crate::db::{HasJar, QueryResult, SemanticDb, SemanticJar};
|
||||
use crate::module::ModuleName;
|
||||
use crate::symbols::{ClassDefinition, Definition, ImportFromDefinition, SymbolId};
|
||||
use crate::symbols::{Definition, ImportFromDefinition, SymbolId};
|
||||
use crate::types::Type;
|
||||
use crate::FileId;
|
||||
use ruff_python_ast as ast;
|
||||
@@ -51,7 +51,7 @@ where
|
||||
Type::Unknown
|
||||
}
|
||||
}
|
||||
Definition::ClassDef(ClassDefinition { node_key, scope_id }) => {
|
||||
Definition::ClassDef(node_key) => {
|
||||
if let Some(ty) = type_store.get_cached_node_type(file_id, node_key.erased()) {
|
||||
ty
|
||||
} else {
|
||||
@@ -65,8 +65,7 @@ where
|
||||
bases.push(infer_expr_type(db, file_id, base)?);
|
||||
}
|
||||
|
||||
let ty =
|
||||
Type::Class(type_store.add_class(file_id, &node.name.id, *scope_id, bases));
|
||||
let ty = Type::Class(type_store.add_class(file_id, &node.name.id, bases));
|
||||
type_store.cache_node_type(file_id, *node_key.erased(), ty);
|
||||
ty
|
||||
}
|
||||
@@ -81,15 +80,7 @@ where
|
||||
.resolve(ast.as_any_node_ref())
|
||||
.expect("node key should resolve");
|
||||
|
||||
let decorator_tys = node
|
||||
.decorator_list
|
||||
.iter()
|
||||
.map(|decorator| infer_expr_type(db, file_id, &decorator.expression))
|
||||
.collect::<QueryResult<_>>()?;
|
||||
|
||||
let ty = type_store
|
||||
.add_function(file_id, &node.name.id, decorator_tys)
|
||||
.into();
|
||||
let ty = type_store.add_function(file_id, &node.name.id).into();
|
||||
type_store.cache_node_type(file_id, *node_key.erased(), ty);
|
||||
ty
|
||||
}
|
||||
@@ -134,7 +125,6 @@ mod tests {
|
||||
use crate::db::{HasJar, SemanticDb, SemanticJar};
|
||||
use crate::module::{ModuleName, ModuleSearchPath, ModuleSearchPathKind};
|
||||
use crate::types::Type;
|
||||
use crate::Name;
|
||||
|
||||
// TODO with virtual filesystem we shouldn't have to write files to disk for these
|
||||
// tests
|
||||
@@ -224,42 +214,4 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_method() -> anyhow::Result<()> {
|
||||
let case = create_test()?;
|
||||
let db = &case.db;
|
||||
|
||||
let path = case.src.path().join("mod.py");
|
||||
std::fs::write(path, "class C:\n def f(self): pass")?;
|
||||
let file = db
|
||||
.resolve_module(ModuleName::new("mod"))?
|
||||
.expect("module should be found")
|
||||
.path(db)?
|
||||
.file();
|
||||
let syms = db.symbol_table(file)?;
|
||||
let sym = syms
|
||||
.root_symbol_id_by_name("C")
|
||||
.expect("C symbol should be found");
|
||||
|
||||
let ty = db.infer_symbol_type(file, sym)?;
|
||||
|
||||
let Type::Class(class_id) = ty else {
|
||||
panic!("C is not a Class");
|
||||
};
|
||||
|
||||
let member_ty = class_id
|
||||
.get_own_class_member(db, &Name::new("f"))
|
||||
.expect("C.f to resolve");
|
||||
|
||||
let Some(Type::Function(func_id)) = member_ty else {
|
||||
panic!("C.f is not a Function");
|
||||
};
|
||||
|
||||
let jar = HasJar::<SemanticJar>::jar(db)?;
|
||||
let function = jar.type_store.get_function(func_id);
|
||||
assert_eq!(function.name(), "f");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
use anyhow::Context;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Context;
|
||||
use crate::files::Files;
|
||||
use crate::program::{FileChange, FileChangeKind};
|
||||
use notify::event::{CreateKind, RemoveKind};
|
||||
use notify::{recommended_watcher, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use crate::program::{FileChangeKind, FileWatcherChange};
|
||||
|
||||
pub struct FileWatcher {
|
||||
watcher: RecommendedWatcher,
|
||||
}
|
||||
|
||||
pub trait EventHandler: Send + 'static {
|
||||
fn handle(&self, changes: Vec<FileWatcherChange>);
|
||||
fn handle(&self, changes: Vec<FileChange>);
|
||||
}
|
||||
|
||||
impl<F> EventHandler for F
|
||||
where
|
||||
F: Fn(Vec<FileWatcherChange>) + Send + 'static,
|
||||
F: Fn(Vec<FileChange>) + Send + 'static,
|
||||
{
|
||||
fn handle(&self, changes: Vec<FileWatcherChange>) {
|
||||
fn handle(&self, changes: Vec<FileChange>) {
|
||||
let f = self;
|
||||
f(changes);
|
||||
}
|
||||
}
|
||||
|
||||
impl FileWatcher {
|
||||
pub fn new<E>(handler: E) -> anyhow::Result<Self>
|
||||
pub fn new<E>(handler: E, files: Files) -> anyhow::Result<Self>
|
||||
where
|
||||
E: EventHandler,
|
||||
{
|
||||
Self::from_handler(Box::new(handler))
|
||||
Self::from_handler(Box::new(handler), files)
|
||||
}
|
||||
|
||||
fn from_handler(handler: Box<dyn EventHandler>) -> anyhow::Result<Self> {
|
||||
fn from_handler(handler: Box<dyn EventHandler>, files: Files) -> anyhow::Result<Self> {
|
||||
let watcher = recommended_watcher(move |changes: notify::Result<Event>| {
|
||||
match changes {
|
||||
Ok(event) => {
|
||||
@@ -50,7 +50,8 @@ impl FileWatcher {
|
||||
|
||||
for path in event.paths {
|
||||
if path.is_file() {
|
||||
changes.push(FileWatcherChange::new(path, change_kind));
|
||||
let id = files.intern(&path);
|
||||
changes.push(FileChange::new(id, change_kind));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -41,6 +41,7 @@ is-macro = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
log = { workspace = true }
|
||||
notify = { workspace = true }
|
||||
num_cpus = { workspace = true }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
|
||||
@@ -338,7 +338,7 @@ pub struct CheckCommand {
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// List of mappings from file extension to language (one of `python`, `ipynb`, `pyi`). For
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For
|
||||
/// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
@@ -466,7 +466,7 @@ pub struct FormatCommand {
|
||||
/// The name of the file when passing it through stdin.
|
||||
#[arg(long, help_heading = "Miscellaneous")]
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
/// List of mappings from file extension to language (one of `python`, `ipynb`, `pyi`). For
|
||||
/// List of mappings from file extension to language (one of ["python", "ipynb", "pyi"]). For
|
||||
/// example, to treat `.ipy` files as IPython notebooks, use `--extension ipy:ipynb`.
|
||||
#[arg(long, value_delimiter = ',')]
|
||||
pub extension: Option<Vec<ExtensionPair>>,
|
||||
|
||||
@@ -23,6 +23,7 @@ use ruff_linter::message::Message;
|
||||
use ruff_linter::{warn_user, VERSION};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_notebook::NotebookIndex;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::Resolver;
|
||||
@@ -347,7 +348,7 @@ impl FileCache {
|
||||
} else {
|
||||
FxHashMap::default()
|
||||
};
|
||||
Diagnostics::new(messages, notebook_indexes)
|
||||
Diagnostics::new(messages, lint.imports.clone(), notebook_indexes)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -393,7 +394,7 @@ pub(crate) fn init(path: &Path) -> Result<()> {
|
||||
#[derive(Deserialize, Debug, Serialize, PartialEq)]
|
||||
pub(crate) struct LintCacheData {
|
||||
/// Imports made.
|
||||
// pub(super) imports: ImportMap,
|
||||
pub(super) imports: ImportMap,
|
||||
/// Diagnostic messages.
|
||||
pub(super) messages: Vec<CacheMessage>,
|
||||
/// Source code of the file.
|
||||
@@ -409,6 +410,7 @@ pub(crate) struct LintCacheData {
|
||||
impl LintCacheData {
|
||||
pub(crate) fn from_messages(
|
||||
messages: &[Message],
|
||||
imports: ImportMap,
|
||||
notebook_index: Option<NotebookIndex>,
|
||||
) -> Self {
|
||||
let source = if let Some(msg) = messages.first() {
|
||||
@@ -436,6 +438,7 @@ impl LintCacheData {
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
imports,
|
||||
messages,
|
||||
source,
|
||||
notebook_index,
|
||||
|
||||
@@ -17,6 +17,7 @@ use ruff_linter::registry::Rule;
|
||||
use ruff_linter::settings::types::UnsafeFixes;
|
||||
use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::{fs, warn_user_once, IOError};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{
|
||||
@@ -133,6 +134,7 @@ pub(crate) fn check(
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -23,6 +23,7 @@ use ruff_linter::settings::{flags, LinterSettings};
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::{fs, IOError, SyntaxError};
|
||||
use ruff_notebook::{Notebook, NotebookError, NotebookIndex};
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
|
||||
use ruff_source_file::SourceFileBuilder;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
@@ -34,17 +35,20 @@ use crate::cache::{Cache, FileCacheKey, LintCacheData};
|
||||
pub(crate) struct Diagnostics {
|
||||
pub(crate) messages: Vec<Message>,
|
||||
pub(crate) fixed: FixMap,
|
||||
pub(crate) imports: ImportMap,
|
||||
pub(crate) notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
}
|
||||
|
||||
impl Diagnostics {
|
||||
pub(crate) fn new(
|
||||
messages: Vec<Message>,
|
||||
imports: ImportMap,
|
||||
notebook_indexes: FxHashMap<String, NotebookIndex>,
|
||||
) -> Self {
|
||||
Self {
|
||||
messages,
|
||||
fixed: FixMap::default(),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
}
|
||||
}
|
||||
@@ -88,6 +92,7 @@ impl Diagnostics {
|
||||
dummy,
|
||||
TextSize::default(),
|
||||
)],
|
||||
ImportMap::default(),
|
||||
FxHashMap::default(),
|
||||
)
|
||||
} else {
|
||||
@@ -122,6 +127,7 @@ impl Add for Diagnostics {
|
||||
impl AddAssign for Diagnostics {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.messages.extend(other.messages);
|
||||
self.imports.extend(other.imports);
|
||||
self.fixed += other.fixed;
|
||||
self.notebook_indexes.extend(other.notebook_indexes);
|
||||
}
|
||||
@@ -261,7 +267,7 @@ pub(crate) fn lint_path(
|
||||
// Lint the file.
|
||||
let (
|
||||
LinterResult {
|
||||
data: messages,
|
||||
data: (messages, imports),
|
||||
error: parse_error,
|
||||
},
|
||||
transformed,
|
||||
@@ -329,6 +335,8 @@ pub(crate) fn lint_path(
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
let imports = imports.unwrap_or_default();
|
||||
|
||||
if let Some((cache, relative_path, key)) = caching {
|
||||
// We don't cache parsing errors.
|
||||
if parse_error.is_none() {
|
||||
@@ -346,6 +354,7 @@ pub(crate) fn lint_path(
|
||||
&key,
|
||||
LintCacheData::from_messages(
|
||||
&messages,
|
||||
imports.clone(),
|
||||
transformed.as_ipy_notebook().map(Notebook::index).cloned(),
|
||||
),
|
||||
);
|
||||
@@ -369,6 +378,7 @@ pub(crate) fn lint_path(
|
||||
Ok(Diagnostics {
|
||||
messages,
|
||||
fixed: FixMap::from_iter([(fs::relativize_path(path), fixed)]),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
})
|
||||
}
|
||||
@@ -406,7 +416,7 @@ pub(crate) fn lint_stdin(
|
||||
// Lint the inputs.
|
||||
let (
|
||||
LinterResult {
|
||||
data: messages,
|
||||
data: (messages, imports),
|
||||
error: parse_error,
|
||||
},
|
||||
transformed,
|
||||
@@ -484,6 +494,8 @@ pub(crate) fn lint_stdin(
|
||||
(result, transformed, fixed)
|
||||
};
|
||||
|
||||
let imports = imports.unwrap_or_default();
|
||||
|
||||
if let Some(error) = parse_error {
|
||||
error!(
|
||||
"{}",
|
||||
@@ -506,6 +518,7 @@ pub(crate) fn lint_stdin(
|
||||
fs::relativize_path(path.unwrap_or_else(|| Path::new("-"))),
|
||||
fixed,
|
||||
)]),
|
||||
imports,
|
||||
notebook_indexes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -214,14 +214,13 @@ fn format(args: FormatCommand, global_options: GlobalConfigArgs) -> Result<ExitS
|
||||
|
||||
fn server(args: ServerCommand, log_level: LogLevel) -> Result<ExitStatus> {
|
||||
let ServerCommand { preview } = args;
|
||||
|
||||
let four = NonZeroUsize::new(4).unwrap();
|
||||
|
||||
// by default, we set the number of worker threads to `num_cpus`, with a maximum of 4.
|
||||
let worker_threads = std::thread::available_parallelism()
|
||||
.unwrap_or(four)
|
||||
.max(four);
|
||||
commands::server::run_server(preview, worker_threads, log_level)
|
||||
let worker_threads = num_cpus::get().max(4);
|
||||
commands::server::run_server(
|
||||
preview,
|
||||
NonZeroUsize::try_from(worker_threads).expect("a non-zero worker thread count"),
|
||||
log_level,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn check(args: CheckCommand, global_options: GlobalConfigArgs) -> Result<ExitStatus> {
|
||||
|
||||
@@ -344,7 +344,7 @@ pub struct RemoveSoftLinesBuffer<'a, Context> {
|
||||
|
||||
/// Caches the interned elements after the soft line breaks have been removed.
|
||||
///
|
||||
/// The `key` is the [Interned] element as it has been passed to [`Self::write_element`] or the child of another
|
||||
/// The `key` is the [Interned] element as it has been passed to [Self::write_element] or the child of another
|
||||
/// [Interned] element. The `value` is the matching document of the key where all soft line breaks have been removed.
|
||||
///
|
||||
/// It's fine to not snapshot the cache. The worst that can happen is that it holds on interned elements
|
||||
|
||||
@@ -18,10 +18,10 @@ pub enum FormatError {
|
||||
InvalidDocument(InvalidDocumentError),
|
||||
|
||||
/// Formatting failed because some content encountered a situation where a layout
|
||||
/// choice by an enclosing [`crate::Format`] resulted in a poor layout for a child [`crate::Format`].
|
||||
/// choice by an enclosing [crate::Format] resulted in a poor layout for a child [crate::Format].
|
||||
///
|
||||
/// It's up to an enclosing [`crate::Format`] to handle the error and pick another layout.
|
||||
/// This error should not be raised if there's no outer [`crate::Format`] handling the poor layout error,
|
||||
/// It's up to an enclosing [crate::Format] to handle the error and pick another layout.
|
||||
/// This error should not be raised if there's no outer [crate::Format] handling the poor layout error,
|
||||
/// avoiding that formatting of the whole document fails.
|
||||
PoorLayout,
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@ use ruff_text_size::TextSize;
|
||||
/// Use the helper functions like [`crate::builders::space`], [`crate::builders::soft_line_break`] etc. defined in this file to create elements.
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
pub enum FormatElement {
|
||||
/// A space token, see [`crate::builders::space`] for documentation.
|
||||
/// A space token, see [crate::builders::space] for documentation.
|
||||
Space,
|
||||
|
||||
/// A new line, see [`crate::builders::soft_line_break`], [`crate::builders::hard_line_break`], and [`crate::builders::soft_line_break_or_space`] for documentation.
|
||||
/// A new line, see [crate::builders::soft_line_break], [crate::builders::hard_line_break], and [crate::builders::soft_line_break_or_space] for documentation.
|
||||
Line(LineMode),
|
||||
|
||||
/// Forces the parent group to print in expanded mode.
|
||||
@@ -108,13 +108,13 @@ impl std::fmt::Debug for FormatElement {
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum LineMode {
|
||||
/// See [`crate::builders::soft_line_break_or_space`] for documentation.
|
||||
/// See [crate::builders::soft_line_break_or_space] for documentation.
|
||||
SoftOrSpace,
|
||||
/// See [`crate::builders::soft_line_break`] for documentation.
|
||||
/// See [crate::builders::soft_line_break] for documentation.
|
||||
Soft,
|
||||
/// See [`crate::builders::hard_line_break`] for documentation.
|
||||
/// See [crate::builders::hard_line_break] for documentation.
|
||||
Hard,
|
||||
/// See [`crate::builders::empty_line`] for documentation.
|
||||
/// See [crate::builders::empty_line] for documentation.
|
||||
Empty,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ use std::num::NonZeroU8;
|
||||
/// will be applied to all elements in between the start/end tags.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Tag {
|
||||
/// Indents the content one level deeper, see [`crate::builders::indent`] for documentation and examples.
|
||||
/// Indents the content one level deeper, see [crate::builders::indent] for documentation and examples.
|
||||
StartIndent,
|
||||
EndIndent,
|
||||
|
||||
/// Variant of [`TagKind::Indent`] that indents content by a number of spaces. For example, `Align(2)`
|
||||
/// Variant of [TagKind::Indent] that indents content by a number of spaces. For example, `Align(2)`
|
||||
/// indents any content following a line break by an additional two spaces.
|
||||
///
|
||||
/// Nesting (Aligns)[`TagKind::Align`] has the effect that all except the most inner align are handled as (Indent)[`TagKind::Indent`].
|
||||
/// Nesting (Aligns)[TagKind::Align] has the effect that all except the most inner align are handled as (Indent)[TagKind::Indent].
|
||||
StartAlign(Align),
|
||||
EndAlign,
|
||||
|
||||
@@ -29,7 +29,7 @@ pub enum Tag {
|
||||
/// - on a single line: Omitting `LineMode::Soft` line breaks and printing spaces for `LineMode::SoftOrSpace`
|
||||
/// - on multiple lines: Printing all line breaks
|
||||
///
|
||||
/// See [`crate::builders::group`] for documentation and examples.
|
||||
/// See [crate::builders::group] for documentation and examples.
|
||||
StartGroup(Group),
|
||||
EndGroup,
|
||||
|
||||
@@ -44,22 +44,22 @@ pub enum Tag {
|
||||
EndConditionalGroup,
|
||||
|
||||
/// Allows to specify content that gets printed depending on whatever the enclosing group
|
||||
/// is printed on a single line or multiple lines. See [`crate::builders::if_group_breaks`] for examples.
|
||||
/// is printed on a single line or multiple lines. See [crate::builders::if_group_breaks] for examples.
|
||||
StartConditionalContent(Condition),
|
||||
EndConditionalContent,
|
||||
|
||||
/// Optimized version of [`Tag::StartConditionalContent`] for the case where some content
|
||||
/// Optimized version of [Tag::StartConditionalContent] for the case where some content
|
||||
/// should be indented if the specified group breaks.
|
||||
StartIndentIfGroupBreaks(GroupId),
|
||||
EndIndentIfGroupBreaks,
|
||||
|
||||
/// Concatenates multiple elements together with a given separator printed in either
|
||||
/// flat or expanded mode to fill the print width. Expect that the content is a list of alternating
|
||||
/// [element, separator] See [`crate::Formatter::fill`].
|
||||
/// [element, separator] See [crate::Formatter::fill].
|
||||
StartFill,
|
||||
EndFill,
|
||||
|
||||
/// Entry inside of a [`Tag::StartFill`]
|
||||
/// Entry inside of a [Tag::StartFill]
|
||||
StartEntry,
|
||||
EndEntry,
|
||||
|
||||
@@ -77,7 +77,7 @@ pub enum Tag {
|
||||
/// Special semantic element marking the content with a label.
|
||||
/// This does not directly influence how the content will be printed.
|
||||
///
|
||||
/// See [`crate::builders::labelled`] for documentation.
|
||||
/// See [crate::builders::labelled] for documentation.
|
||||
StartLabelled(LabelId),
|
||||
EndLabelled,
|
||||
|
||||
|
||||
@@ -189,6 +189,27 @@ impl<'a, 'print> Queue<'a> for FitsQueue<'a, 'print> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator that calls [`Queue::pop`] until it reaches the end of the document.
|
||||
///
|
||||
/// The iterator traverses into the content of any [`FormatElement::Interned`].
|
||||
pub(super) struct QueueIterator<'a, 'q, Q: Queue<'a>> {
|
||||
queue: &'q mut Q,
|
||||
lifetime: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, Q> Iterator for QueueIterator<'a, '_, Q>
|
||||
where
|
||||
Q: Queue<'a>,
|
||||
{
|
||||
type Item = &'a FormatElement;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.queue.pop()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Q> FusedIterator for QueueIterator<'a, '_, Q> where Q: Queue<'a> {}
|
||||
|
||||
pub(super) struct QueueContentIterator<'a, 'q, Q: Queue<'a>> {
|
||||
queue: &'q mut Q,
|
||||
kind: TagKind,
|
||||
|
||||
@@ -8,6 +8,9 @@ pub(super) trait Stack<T> {
|
||||
|
||||
/// Returns the last element if any
|
||||
fn top(&self) -> Option<&T>;
|
||||
|
||||
/// Returns `true` if the stack is empty
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<T> Stack<T> for Vec<T> {
|
||||
@@ -22,6 +25,10 @@ impl<T> Stack<T> for Vec<T> {
|
||||
fn top(&self) -> Option<&T> {
|
||||
self.last()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Stack that is stacked on top of another stack. Guarantees that the underlying stack remains unchanged.
|
||||
@@ -73,6 +80,10 @@ where
|
||||
.last()
|
||||
.or_else(|| self.original.as_slice().last())
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.stack.is_empty() && self.original.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.4.3"
|
||||
version = "0.4.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -8,6 +8,7 @@ class Class:
|
||||
pass
|
||||
|
||||
if False:
|
||||
|
||||
def extra_bad_method(this):
|
||||
pass
|
||||
|
||||
@@ -93,7 +94,6 @@ class ModelClass:
|
||||
def badstatic(foo):
|
||||
pass
|
||||
|
||||
|
||||
class SelfInArgsClass:
|
||||
def self_as_argument(this, self):
|
||||
pass
|
||||
@@ -110,7 +110,6 @@ class SelfInArgsClass:
|
||||
def self_as_kwargs(this, **self):
|
||||
pass
|
||||
|
||||
|
||||
class RenamingInMethodBodyClass:
|
||||
def bad_method(this):
|
||||
this = this
|
||||
@@ -118,8 +117,3 @@ class RenamingInMethodBodyClass:
|
||||
|
||||
def bad_method(this):
|
||||
self = this
|
||||
|
||||
|
||||
class RenamingWithNFKC:
|
||||
def formula(household):
|
||||
hºusehold(1)
|
||||
|
||||
@@ -72,18 +72,3 @@ def f():
|
||||
result = Foo()
|
||||
for i in items:
|
||||
result.append(i) # Ok
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
async for i in items:
|
||||
if i % 2:
|
||||
result.append(i) # PERF401
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
async for i in items:
|
||||
result.append(i) # PERF401
|
||||
|
||||
@@ -43,10 +43,3 @@ def f():
|
||||
|
||||
for path in ("foo", "bar"):
|
||||
sys.path.append(path) # OK
|
||||
|
||||
|
||||
def f():
|
||||
items = [1, 2, 3, 4]
|
||||
result = []
|
||||
async for i in items:
|
||||
result.append(i) # PERF402
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
"""__init__.py without __all__
|
||||
|
||||
Unused stdlib and third party imports are unsafe removals
|
||||
|
||||
Unused first party imports get changed to redundant aliases
|
||||
"""
|
||||
|
||||
|
||||
# stdlib
|
||||
|
||||
import os # Ok: is used
|
||||
|
||||
_ = os
|
||||
|
||||
|
||||
import argparse as argparse # Ok: is redundant alias
|
||||
|
||||
|
||||
import sys # F401: remove unused
|
||||
|
||||
|
||||
# first-party
|
||||
|
||||
|
||||
from . import used # Ok: is used
|
||||
|
||||
_ = used
|
||||
|
||||
|
||||
from . import aliased as aliased # Ok: is redundant alias
|
||||
|
||||
|
||||
from . import unused # F401: change to redundant alias
|
||||
|
||||
|
||||
from . import renamed as bees # F401: no fix
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1,42 +0,0 @@
|
||||
"""__init__.py with __all__
|
||||
|
||||
Unused stdlib and third party imports are unsafe removals
|
||||
|
||||
Unused first party imports get added to __all__
|
||||
"""
|
||||
|
||||
|
||||
# stdlib
|
||||
|
||||
import os # Ok: is used
|
||||
|
||||
_ = os
|
||||
|
||||
|
||||
import argparse # Ok: is exported in __all__
|
||||
|
||||
|
||||
import sys # F401: remove unused
|
||||
|
||||
|
||||
# first-party
|
||||
|
||||
|
||||
from . import used # Ok: is used
|
||||
|
||||
_ = used
|
||||
|
||||
|
||||
from . import aliased as aliased # Ok: is redundant alias
|
||||
|
||||
|
||||
from . import exported # Ok: is exported in __all__
|
||||
|
||||
|
||||
# from . import unused # F401: add to __all__
|
||||
|
||||
|
||||
# from . import renamed as bees # F401: add to __all__
|
||||
|
||||
|
||||
__all__ = ["argparse", "exported"]
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -1 +0,0 @@
|
||||
# empty module imported by __init__.py for test fixture
|
||||
@@ -73,10 +73,3 @@ def op_add4(x, y=1):
|
||||
def op_add5(x, y):
|
||||
print("op_add5")
|
||||
return x + y
|
||||
|
||||
|
||||
# OK
|
||||
class Class:
|
||||
@staticmethod
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
@@ -109,7 +109,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
};
|
||||
|
||||
let definitions = std::mem::take(&mut checker.semantic.definitions);
|
||||
let mut overloaded_name: Option<&str> = None;
|
||||
let mut overloaded_name: Option<String> = None;
|
||||
for ContextualizedDefinition {
|
||||
definition,
|
||||
visibility,
|
||||
@@ -127,7 +127,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
if !overloaded_name.is_some_and(|overloaded_name| {
|
||||
flake8_annotations::helpers::is_overload_impl(
|
||||
definition,
|
||||
overloaded_name,
|
||||
&overloaded_name,
|
||||
&checker.semantic,
|
||||
)
|
||||
}) {
|
||||
|
||||
@@ -78,6 +78,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
Rule::DuplicateUnionMember,
|
||||
Rule::RedundantLiteralUnion,
|
||||
Rule::UnnecessaryTypeUnion,
|
||||
Rule::NeverUnion,
|
||||
]) {
|
||||
// Avoid duplicate checks if the parent is a union, since these rules already
|
||||
// traverse nested unions.
|
||||
|
||||
@@ -877,7 +877,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if !matches!(checker.semantic.current_scope().kind, ScopeKind::Module) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithNestedImportStarUsage {
|
||||
name: helpers::format_import_from(level, module).to_string(),
|
||||
name: helpers::format_import_from(level, module),
|
||||
},
|
||||
stmt.range(),
|
||||
));
|
||||
@@ -886,7 +886,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::UndefinedLocalWithImportStar) {
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
pyflakes::rules::UndefinedLocalWithImportStar {
|
||||
name: helpers::format_import_from(level, module).to_string(),
|
||||
name: helpers::format_import_from(level, module),
|
||||
},
|
||||
stmt.range(),
|
||||
));
|
||||
@@ -1323,10 +1323,10 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
pylint::rules::dict_iter_missing_items(checker, target, iter);
|
||||
}
|
||||
if checker.enabled(Rule::ManualListComprehension) {
|
||||
perflint::rules::manual_list_comprehension(checker, for_stmt);
|
||||
perflint::rules::manual_list_comprehension(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ManualListCopy) {
|
||||
perflint::rules::manual_list_copy(checker, for_stmt);
|
||||
perflint::rules::manual_list_copy(checker, target, body);
|
||||
}
|
||||
if checker.enabled(Rule::ManualDictComprehension) {
|
||||
perflint::rules::manual_dict_comprehension(checker, target, body);
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
//! Lint rules based on import analysis.
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_notebook::CellOffsets;
|
||||
use ruff_python_ast::helpers::to_module_path;
|
||||
use ruff_python_ast::imports::{ImportMap, ModuleImport};
|
||||
use ruff_python_ast::statement_visitor::StatementVisitor;
|
||||
use ruff_python_ast::{PySourceType, Suite};
|
||||
use ruff_python_ast::{self as ast, PySourceType, Stmt, Suite};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::directives::IsortDirectives;
|
||||
use crate::registry::Rule;
|
||||
@@ -15,6 +19,57 @@ use crate::rules::isort;
|
||||
use crate::rules::isort::block::{Block, BlockBuilder};
|
||||
use crate::settings::LinterSettings;
|
||||
|
||||
fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) -> Option<ImportMap> {
|
||||
let module_path = to_module_path(package?, path)?;
|
||||
|
||||
let num_imports = blocks.iter().map(|block| block.imports.len()).sum();
|
||||
let mut module_imports = Vec::with_capacity(num_imports);
|
||||
for stmt in blocks.iter().flat_map(|block| &block.imports) {
|
||||
match stmt {
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
module_imports.extend(
|
||||
names
|
||||
.iter()
|
||||
.map(|name| ModuleImport::new(name.name.to_string(), stmt.range())),
|
||||
);
|
||||
}
|
||||
Stmt::ImportFrom(ast::StmtImportFrom {
|
||||
module,
|
||||
names,
|
||||
level,
|
||||
range: _,
|
||||
}) => {
|
||||
let level = *level as usize;
|
||||
let module = if let Some(module) = module {
|
||||
let module: &String = module.as_ref();
|
||||
if level == 0 {
|
||||
Cow::Borrowed(module)
|
||||
} else {
|
||||
if module_path.len() <= level {
|
||||
continue;
|
||||
}
|
||||
let prefix = module_path[..module_path.len() - level].join(".");
|
||||
Cow::Owned(format!("{prefix}.{module}"))
|
||||
}
|
||||
} else {
|
||||
if module_path.len() <= level {
|
||||
continue;
|
||||
}
|
||||
Cow::Owned(module_path[..module_path.len() - level].join("."))
|
||||
};
|
||||
module_imports.extend(names.iter().map(|name| {
|
||||
ModuleImport::new(format!("{}.{}", module, name.name), name.range())
|
||||
}));
|
||||
}
|
||||
_ => panic!("Expected Stmt::Import | Stmt::ImportFrom"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut import_map = ImportMap::default();
|
||||
import_map.insert(module_path.join("."), module_imports);
|
||||
Some(import_map)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn check_imports(
|
||||
python_ast: &Suite,
|
||||
@@ -23,10 +78,11 @@ pub(crate) fn check_imports(
|
||||
directives: &IsortDirectives,
|
||||
settings: &LinterSettings,
|
||||
stylist: &Stylist,
|
||||
path: &Path,
|
||||
package: Option<&Path>,
|
||||
source_type: PySourceType,
|
||||
cell_offsets: Option<&CellOffsets>,
|
||||
) -> Vec<Diagnostic> {
|
||||
) -> (Vec<Diagnostic>, Option<ImportMap>) {
|
||||
// Extract all import blocks from the AST.
|
||||
let tracker = {
|
||||
let mut tracker =
|
||||
@@ -66,5 +122,8 @@ pub(crate) fn check_imports(
|
||||
));
|
||||
}
|
||||
|
||||
diagnostics
|
||||
// Extract import map.
|
||||
let imports = extract_import_map(path, package, &blocks);
|
||||
|
||||
(diagnostics, imports)
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ pub(crate) struct TodoComment<'a> {
|
||||
pub(crate) directive: TodoDirective<'a>,
|
||||
/// The comment's actual [`TextRange`].
|
||||
pub(crate) range: TextRange,
|
||||
/// The comment range's position in [`Indexer::comment_ranges`]
|
||||
/// The comment range's position in [`Indexer`].comment_ranges()
|
||||
pub(crate) range_index: usize,
|
||||
}
|
||||
|
||||
|
||||
@@ -122,28 +122,6 @@ pub(crate) fn remove_unused_imports<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Edits to make the specified imports explicit, e.g. change `import x` to `import x as x`.
|
||||
pub(crate) fn make_redundant_alias<'a>(
|
||||
member_names: impl Iterator<Item = &'a str>,
|
||||
stmt: &Stmt,
|
||||
) -> Vec<Edit> {
|
||||
let aliases = match stmt {
|
||||
Stmt::Import(ast::StmtImport { names, .. }) => names,
|
||||
Stmt::ImportFrom(ast::StmtImportFrom { names, .. }) => names,
|
||||
_ => {
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
member_names
|
||||
.filter_map(|name| {
|
||||
aliases
|
||||
.iter()
|
||||
.find(|alias| alias.asname.is_none() && name == alias.name.id)
|
||||
.map(|alias| Edit::range_replacement(format!("{name} as {name}"), alias.range))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Parentheses {
|
||||
/// Remove parentheses, if the removed argument is the only argument left.
|
||||
@@ -479,12 +457,11 @@ fn all_lines_fit(
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_parser::parse_suite;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
use crate::fix::edits::{make_redundant_alias, next_stmt_break, trailing_semicolon};
|
||||
use crate::fix::edits::{next_stmt_break, trailing_semicolon};
|
||||
|
||||
#[test]
|
||||
fn find_semicolon() -> Result<()> {
|
||||
@@ -555,35 +532,4 @@ x = 1 \
|
||||
TextSize::from(12)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redundant_alias() {
|
||||
let contents = "import x, y as y, z as bees";
|
||||
let program = parse_suite(contents).unwrap();
|
||||
let stmt = program.first().unwrap();
|
||||
assert_eq!(
|
||||
make_redundant_alias(["x"].into_iter(), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
)],
|
||||
"make just one item redundant"
|
||||
);
|
||||
assert_eq!(
|
||||
make_redundant_alias(vec!["x", "y"].into_iter(), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
)],
|
||||
"the second item is already a redundant alias"
|
||||
);
|
||||
assert_eq!(
|
||||
make_redundant_alias(vec!["x", "z"].into_iter(), stmt),
|
||||
vec![Edit::range_replacement(
|
||||
String::from("x as x"),
|
||||
TextRange::new(TextSize::new(7), TextSize::new(8)),
|
||||
)],
|
||||
"the third item is already aliased to something else"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ impl CacheKey for LineLength {
|
||||
pub enum ParseLineWidthError {
|
||||
/// The string could not be parsed as a valid [u16]
|
||||
ParseError(ParseIntError),
|
||||
/// The [u16] value of the string is not a valid [`LineLength`]
|
||||
/// The [u16] value of the string is not a valid [LineLength]
|
||||
TryFromIntError(LineLengthFromIntError),
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_notebook::Notebook;
|
||||
use ruff_python_ast::imports::ImportMap;
|
||||
use ruff_python_ast::{PySourceType, Suite};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
@@ -61,7 +62,7 @@ pub type FixTable = FxHashMap<Rule, usize>;
|
||||
|
||||
pub struct FixerResult<'a> {
|
||||
/// The result returned by the linter, after applying any fixes.
|
||||
pub result: LinterResult<Vec<Message>>,
|
||||
pub result: LinterResult<(Vec<Message>, Option<ImportMap>)>,
|
||||
/// The resulting source code, after applying any fixes.
|
||||
pub transformed: Cow<'a, SourceKind>,
|
||||
/// The number of fixes applied for each [`Rule`].
|
||||
@@ -83,9 +84,10 @@ pub fn check_path(
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
tokens: TokenSource,
|
||||
) -> LinterResult<Vec<Diagnostic>> {
|
||||
) -> LinterResult<(Vec<Diagnostic>, Option<ImportMap>)> {
|
||||
// Aggregate all diagnostics.
|
||||
let mut diagnostics = vec![];
|
||||
let mut imports = None;
|
||||
let mut error = None;
|
||||
|
||||
// Collect doc lines. This requires a rare mix of tokens (for comments) and AST
|
||||
@@ -167,18 +169,19 @@ pub fn check_path(
|
||||
));
|
||||
}
|
||||
if use_imports {
|
||||
let import_diagnostics = check_imports(
|
||||
let (import_diagnostics, module_imports) = check_imports(
|
||||
&python_ast,
|
||||
locator,
|
||||
indexer,
|
||||
&directives.isort,
|
||||
settings,
|
||||
stylist,
|
||||
path,
|
||||
package,
|
||||
source_type,
|
||||
cell_offsets,
|
||||
);
|
||||
|
||||
imports = module_imports;
|
||||
diagnostics.extend(import_diagnostics);
|
||||
}
|
||||
if use_doc_lines {
|
||||
@@ -337,7 +340,7 @@ pub fn check_path(
|
||||
}
|
||||
}
|
||||
|
||||
LinterResult::new(diagnostics, error)
|
||||
LinterResult::new((diagnostics, imports), error)
|
||||
}
|
||||
|
||||
const MAX_ITERATIONS: usize = 100;
|
||||
@@ -407,7 +410,7 @@ pub fn add_noqa_to_path(
|
||||
// TODO(dhruvmanila): Add support for Jupyter Notebooks
|
||||
add_noqa(
|
||||
path,
|
||||
&diagnostics,
|
||||
&diagnostics.0,
|
||||
&locator,
|
||||
indexer.comment_ranges(),
|
||||
&settings.external,
|
||||
@@ -426,7 +429,7 @@ pub fn lint_only(
|
||||
source_kind: &SourceKind,
|
||||
source_type: PySourceType,
|
||||
data: ParseSource,
|
||||
) -> LinterResult<Vec<Message>> {
|
||||
) -> LinterResult<(Vec<Message>, Option<ImportMap>)> {
|
||||
// Tokenize once.
|
||||
let tokens = data.into_token_source(source_kind, source_type);
|
||||
|
||||
@@ -462,7 +465,12 @@ pub fn lint_only(
|
||||
tokens,
|
||||
);
|
||||
|
||||
result.map(|diagnostics| diagnostics_to_messages(diagnostics, path, &locator, &directives))
|
||||
result.map(|(diagnostics, imports)| {
|
||||
(
|
||||
diagnostics_to_messages(diagnostics, path, &locator, &directives),
|
||||
imports,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert from diagnostics to messages.
|
||||
@@ -575,7 +583,7 @@ pub fn lint_fix<'a>(
|
||||
code: fixed_contents,
|
||||
fixes: applied,
|
||||
source_map,
|
||||
}) = fix_file(&result.data, &locator, unsafe_fixes)
|
||||
}) = fix_file(&result.data.0, &locator, unsafe_fixes)
|
||||
{
|
||||
if iterations < MAX_ITERATIONS {
|
||||
// Count the number of fixed errors.
|
||||
@@ -592,12 +600,15 @@ pub fn lint_fix<'a>(
|
||||
continue;
|
||||
}
|
||||
|
||||
report_failed_to_converge_error(path, transformed.source_code(), &result.data);
|
||||
report_failed_to_converge_error(path, transformed.source_code(), &result.data.0);
|
||||
}
|
||||
|
||||
return Ok(FixerResult {
|
||||
result: result.map(|diagnostics| {
|
||||
diagnostics_to_messages(diagnostics, path, &locator, &directives)
|
||||
result: result.map(|(diagnostics, imports)| {
|
||||
(
|
||||
diagnostics_to_messages(diagnostics, path, &locator, &directives),
|
||||
imports,
|
||||
)
|
||||
}),
|
||||
transformed,
|
||||
fixed,
|
||||
|
||||
@@ -207,15 +207,6 @@ impl RuleSet {
|
||||
*self = set.union(&RuleSet::from_rule(rule));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set(&mut self, rule: Rule, enabled: bool) {
|
||||
if enabled {
|
||||
self.insert(rule);
|
||||
} else {
|
||||
self.remove(rule);
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes `rule` from the set.
|
||||
///
|
||||
/// ## Examples
|
||||
|
||||
@@ -6,7 +6,7 @@ use itertools::Itertools;
|
||||
use ruff_diagnostics::Edit;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_semantic::{Binding, BindingKind, Scope, ScopeId, SemanticModel};
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
|
||||
pub(crate) struct Renamer;
|
||||
|
||||
@@ -215,6 +215,7 @@ impl Renamer {
|
||||
let quote = stylist.quote();
|
||||
format!("{quote}{target}{quote}")
|
||||
} else {
|
||||
debug_assert_eq!(TextSize::of(name), reference.range().len());
|
||||
target.to_string()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,13 +17,10 @@ use crate::importer::{ImportRequest, Importer};
|
||||
use crate::settings::types::PythonVersion;
|
||||
|
||||
/// Return the name of the function, if it's overloaded.
|
||||
pub(crate) fn overloaded_name<'a>(
|
||||
definition: &'a Definition,
|
||||
semantic: &SemanticModel,
|
||||
) -> Option<&'a str> {
|
||||
pub(crate) fn overloaded_name(definition: &Definition, semantic: &SemanticModel) -> Option<String> {
|
||||
let function = definition.as_function_def()?;
|
||||
if visibility::is_overload(&function.decorator_list, semantic) {
|
||||
Some(function.name.as_str())
|
||||
Some(function.name.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ pub(crate) fn fix_unnecessary_double_cast_or_process(
|
||||
.find(|argument| argument.keyword.is_none())
|
||||
{
|
||||
let mut arg = arg.clone();
|
||||
arg.comma.clone_from(&first.comma);
|
||||
arg.comma = first.comma.clone();
|
||||
arg.whitespace_after_arg = first.whitespace_after_arg.clone();
|
||||
iter::once(arg)
|
||||
.chain(rest.iter().cloned())
|
||||
|
||||
@@ -8,8 +8,8 @@ use ruff_text_size::Ranged;
|
||||
/// Checks for incorrect import of pytest.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// For consistency, `pytest` should be imported as `import pytest` and its members should be
|
||||
/// accessed in the form of `pytest.xxx.yyy` for consistency
|
||||
/// `pytest` should be imported as `import pytest` and its members should be accessed in the form of
|
||||
/// `pytest.xxx.yyy` for consistency and to make it easier for linting tools to analyze the code.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -27,7 +27,7 @@ pub struct PytestIncorrectPytestImport;
|
||||
impl Violation for PytestIncorrectPytestImport {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Incorrect import of `pytest`; use `import pytest` instead")
|
||||
format!("Found incorrect import of pytest, use simple `import pytest` instead")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pytest_style/mod.rs
|
||||
---
|
||||
PT013.py:11:1: PT013 Incorrect import of `pytest`; use `import pytest` instead
|
||||
PT013.py:11:1: PT013 Found incorrect import of pytest, use simple `import pytest` instead
|
||||
|
|
||||
9 | # Error
|
||||
10 |
|
||||
@@ -11,7 +11,7 @@ PT013.py:11:1: PT013 Incorrect import of `pytest`; use `import pytest` instead
|
||||
13 | from pytest import fixture as other_name
|
||||
|
|
||||
|
||||
PT013.py:12:1: PT013 Incorrect import of `pytest`; use `import pytest` instead
|
||||
PT013.py:12:1: PT013 Found incorrect import of pytest, use simple `import pytest` instead
|
||||
|
|
||||
11 | import pytest as other_name
|
||||
12 | from pytest import fixture
|
||||
@@ -19,10 +19,12 @@ PT013.py:12:1: PT013 Incorrect import of `pytest`; use `import pytest` instead
|
||||
13 | from pytest import fixture as other_name
|
||||
|
|
||||
|
||||
PT013.py:13:1: PT013 Incorrect import of `pytest`; use `import pytest` instead
|
||||
PT013.py:13:1: PT013 Found incorrect import of pytest, use simple `import pytest` instead
|
||||
|
|
||||
11 | import pytest as other_name
|
||||
12 | from pytest import fixture
|
||||
13 | from pytest import fixture as other_name
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PT013
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -67,8 +67,8 @@ pub(crate) fn fix_multiple_with_statements(
|
||||
|
||||
outer_with.items.append(&mut inner_with.items);
|
||||
if outer_with.lpar.is_none() {
|
||||
outer_with.lpar.clone_from(&inner_with.lpar);
|
||||
outer_with.rpar.clone_from(&inner_with.rpar);
|
||||
outer_with.lpar = inner_with.lpar.clone();
|
||||
outer_with.rpar = inner_with.rpar.clone();
|
||||
}
|
||||
outer_with.body = inner_with.body.clone();
|
||||
|
||||
|
||||
@@ -322,7 +322,7 @@ pub(crate) fn unused_arguments(
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -29,38 +29,29 @@ pub(crate) struct CommentSet<'a> {
|
||||
pub(crate) inline: Vec<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
pub(crate) trait Importable<'a> {
|
||||
fn module_name(&self) -> Cow<'a, str>;
|
||||
pub(crate) trait Importable {
|
||||
fn module_name(&self) -> String;
|
||||
fn module_base(&self) -> String;
|
||||
}
|
||||
|
||||
fn module_base(&self) -> Cow<'a, str> {
|
||||
match self.module_name() {
|
||||
Cow::Borrowed(module_name) => Cow::Borrowed(
|
||||
module_name
|
||||
.split('.')
|
||||
.next()
|
||||
.expect("module to include at least one segment"),
|
||||
),
|
||||
Cow::Owned(module_name) => Cow::Owned(
|
||||
module_name
|
||||
.split('.')
|
||||
.next()
|
||||
.expect("module to include at least one segment")
|
||||
.to_owned(),
|
||||
),
|
||||
}
|
||||
impl Importable for AliasData<'_> {
|
||||
fn module_name(&self) -> String {
|
||||
self.name.to_string()
|
||||
}
|
||||
|
||||
fn module_base(&self) -> String {
|
||||
self.module_name().split('.').next().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Importable<'a> for AliasData<'a> {
|
||||
fn module_name(&self) -> Cow<'a, str> {
|
||||
Cow::Borrowed(self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Importable<'a> for ImportFromData<'a> {
|
||||
fn module_name(&self) -> Cow<'a, str> {
|
||||
impl Importable for ImportFromData<'_> {
|
||||
fn module_name(&self) -> String {
|
||||
format_import_from(self.level, self.module)
|
||||
}
|
||||
|
||||
fn module_base(&self) -> String {
|
||||
self.module_name().split('.').next().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn invalid_first_argument_name(
|
||||
panic!("Expected ScopeKind::Function")
|
||||
};
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -20,159 +20,160 @@ N805.py:7:20: N805 [*] First argument of a method should be named `self`
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
|
||||
N805.py:11:30: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:12:30: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
10 | if False:
|
||||
11 | def extra_bad_method(this):
|
||||
11 |
|
||||
12 | def extra_bad_method(this):
|
||||
| ^^^^ N805
|
||||
12 | pass
|
||||
13 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
11 |- def extra_bad_method(this):
|
||||
11 |+ def extra_bad_method(self):
|
||||
12 12 | pass
|
||||
13 13 |
|
||||
14 14 | def good_method(self):
|
||||
11 11 |
|
||||
12 |- def extra_bad_method(this):
|
||||
12 |+ def extra_bad_method(self):
|
||||
13 13 | pass
|
||||
14 14 |
|
||||
15 15 | def good_method(self):
|
||||
|
||||
N805.py:30:15: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:31:15: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
29 | @pydantic.validator
|
||||
30 | def lower(cls, my_field: str) -> str:
|
||||
30 | @pydantic.validator
|
||||
31 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
31 | pass
|
||||
32 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
27 27 | return x
|
||||
28 28 |
|
||||
29 29 | @pydantic.validator
|
||||
30 |- def lower(cls, my_field: str) -> str:
|
||||
30 |+ def lower(self, my_field: str) -> str:
|
||||
31 31 | pass
|
||||
32 32 |
|
||||
33 33 | @pydantic.validator("my_field")
|
||||
28 28 | return x
|
||||
29 29 |
|
||||
30 30 | @pydantic.validator
|
||||
31 |- def lower(cls, my_field: str) -> str:
|
||||
31 |+ def lower(self, my_field: str) -> str:
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 34 | @pydantic.validator("my_field")
|
||||
|
||||
N805.py:34:15: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:35:15: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
33 | @pydantic.validator("my_field")
|
||||
34 | def lower(cls, my_field: str) -> str:
|
||||
34 | @pydantic.validator("my_field")
|
||||
35 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
35 | pass
|
||||
36 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
31 31 | pass
|
||||
32 32 |
|
||||
33 33 | @pydantic.validator("my_field")
|
||||
34 |- def lower(cls, my_field: str) -> str:
|
||||
34 |+ def lower(self, my_field: str) -> str:
|
||||
35 35 | pass
|
||||
36 36 |
|
||||
37 37 | def __init__(self):
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 34 | @pydantic.validator("my_field")
|
||||
35 |- def lower(cls, my_field: str) -> str:
|
||||
35 |+ def lower(self, my_field: str) -> str:
|
||||
36 36 | pass
|
||||
37 37 |
|
||||
38 38 | def __init__(self):
|
||||
|
||||
N805.py:63:29: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:64:29: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
61 | pass
|
||||
62 |
|
||||
63 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
62 | pass
|
||||
63 |
|
||||
64 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
| ^^^^ N805
|
||||
64 | pass
|
||||
65 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 | def good_method_pos_only(self, blah, /, something: str):
|
||||
61 61 | pass
|
||||
62 62 |
|
||||
63 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
63 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
64 64 | pass
|
||||
65 65 |
|
||||
61 61 | def good_method_pos_only(self, blah, /, something: str):
|
||||
62 62 | pass
|
||||
63 63 |
|
||||
64 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
64 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
65 65 | pass
|
||||
66 66 |
|
||||
67 67 |
|
||||
|
||||
N805.py:69:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:70:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
67 | class ModelClass:
|
||||
68 | @hybrid_property
|
||||
69 | def bad(cls):
|
||||
68 | class ModelClass:
|
||||
69 | @hybrid_property
|
||||
70 | def bad(cls):
|
||||
| ^^^ N805
|
||||
70 | pass
|
||||
71 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
66 66 |
|
||||
67 67 | class ModelClass:
|
||||
68 68 | @hybrid_property
|
||||
69 |- def bad(cls):
|
||||
69 |+ def bad(self):
|
||||
70 70 | pass
|
||||
71 71 |
|
||||
72 72 | @bad.expression
|
||||
67 67 |
|
||||
68 68 | class ModelClass:
|
||||
69 69 | @hybrid_property
|
||||
70 |- def bad(cls):
|
||||
70 |+ def bad(self):
|
||||
71 71 | pass
|
||||
72 72 |
|
||||
73 73 | @bad.expression
|
||||
|
||||
N805.py:77:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:78:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
76 | @bad.wtf
|
||||
77 | def bad(cls):
|
||||
77 | @bad.wtf
|
||||
78 | def bad(cls):
|
||||
| ^^^ N805
|
||||
78 | pass
|
||||
79 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
74 74 | pass
|
||||
75 75 |
|
||||
76 76 | @bad.wtf
|
||||
77 |- def bad(cls):
|
||||
77 |+ def bad(self):
|
||||
78 78 | pass
|
||||
79 79 |
|
||||
80 80 | @hybrid_property
|
||||
75 75 | pass
|
||||
76 76 |
|
||||
77 77 | @bad.wtf
|
||||
78 |- def bad(cls):
|
||||
78 |+ def bad(self):
|
||||
79 79 | pass
|
||||
80 80 |
|
||||
81 81 | @hybrid_property
|
||||
|
||||
N805.py:85:14: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:86:14: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
84 | @good.expression
|
||||
85 | def good(cls):
|
||||
85 | @good.expression
|
||||
86 | def good(cls):
|
||||
| ^^^ N805
|
||||
86 | pass
|
||||
87 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
82 82 | pass
|
||||
83 83 |
|
||||
84 84 | @good.expression
|
||||
85 |- def good(cls):
|
||||
85 |+ def good(self):
|
||||
86 86 | pass
|
||||
87 87 |
|
||||
88 88 | @good.wtf
|
||||
83 83 | pass
|
||||
84 84 |
|
||||
85 85 | @good.expression
|
||||
86 |- def good(cls):
|
||||
86 |+ def good(self):
|
||||
87 87 | pass
|
||||
88 88 |
|
||||
89 89 | @good.wtf
|
||||
|
||||
N805.py:93:19: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:94:19: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
92 | @foobar.thisisstatic
|
||||
93 | def badstatic(foo):
|
||||
93 | @foobar.thisisstatic
|
||||
94 | def badstatic(foo):
|
||||
| ^^^ N805
|
||||
94 | pass
|
||||
95 | pass
|
||||
|
|
||||
= help: Rename `foo` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
90 90 | pass
|
||||
91 91 |
|
||||
92 92 | @foobar.thisisstatic
|
||||
93 |- def badstatic(foo):
|
||||
93 |+ def badstatic(self):
|
||||
94 94 | pass
|
||||
95 95 |
|
||||
91 91 | pass
|
||||
92 92 |
|
||||
93 93 | @foobar.thisisstatic
|
||||
94 |- def badstatic(foo):
|
||||
94 |+ def badstatic(self):
|
||||
95 95 | pass
|
||||
96 96 |
|
||||
97 97 | class SelfInArgsClass:
|
||||
|
||||
N805.py:98:26: N805 First argument of a method should be named `self`
|
||||
|
|
||||
@@ -223,66 +224,45 @@ N805.py:110:24: N805 First argument of a method should be named `self`
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
N805.py:115:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:114:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
114 | class RenamingInMethodBodyClass:
|
||||
115 | def bad_method(this):
|
||||
113 | class RenamingInMethodBodyClass:
|
||||
114 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
116 | this = this
|
||||
117 | this
|
||||
115 | this = this
|
||||
116 | this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
111 111 | pass
|
||||
112 112 |
|
||||
113 113 |
|
||||
114 114 | class RenamingInMethodBodyClass:
|
||||
115 |- def bad_method(this):
|
||||
116 |- this = this
|
||||
117 |- this
|
||||
115 |+ def bad_method(self):
|
||||
116 |+ self = self
|
||||
117 |+ self
|
||||
118 118 |
|
||||
119 119 | def bad_method(this):
|
||||
120 120 | self = this
|
||||
113 113 | class RenamingInMethodBodyClass:
|
||||
114 |- def bad_method(this):
|
||||
115 |- this = this
|
||||
116 |- this
|
||||
114 |+ def bad_method(self):
|
||||
115 |+ self = self
|
||||
116 |+ self
|
||||
117 117 |
|
||||
118 118 | def bad_method(this):
|
||||
119 119 | self = this
|
||||
|
||||
N805.py:119:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:118:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
117 | this
|
||||
118 |
|
||||
119 | def bad_method(this):
|
||||
116 | this
|
||||
117 |
|
||||
118 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
120 | self = this
|
||||
119 | self = this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | this = this
|
||||
117 117 | this
|
||||
118 118 |
|
||||
119 |- def bad_method(this):
|
||||
120 |- self = this
|
||||
119 |+ def bad_method(self):
|
||||
120 |+ self = self
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
|
||||
N805.py:124:17: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
123 | class RenamingWithNFKC:
|
||||
124 | def formula(household):
|
||||
| ^^^^^^^^^ N805
|
||||
125 | hºusehold(1)
|
||||
|
|
||||
= help: Rename `household` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
124 |- def formula(household):
|
||||
125 |- hºusehold(1)
|
||||
124 |+ def formula(self):
|
||||
125 |+ self(1)
|
||||
115 115 | this = this
|
||||
116 116 | this
|
||||
117 117 |
|
||||
118 |- def bad_method(this):
|
||||
119 |- self = this
|
||||
118 |+ def bad_method(self):
|
||||
119 |+ self = self
|
||||
|
||||
@@ -20,102 +20,103 @@ N805.py:7:20: N805 [*] First argument of a method should be named `self`
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
|
||||
N805.py:11:30: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:12:30: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
10 | if False:
|
||||
11 | def extra_bad_method(this):
|
||||
11 |
|
||||
12 | def extra_bad_method(this):
|
||||
| ^^^^ N805
|
||||
12 | pass
|
||||
13 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
11 |- def extra_bad_method(this):
|
||||
11 |+ def extra_bad_method(self):
|
||||
12 12 | pass
|
||||
13 13 |
|
||||
14 14 | def good_method(self):
|
||||
11 11 |
|
||||
12 |- def extra_bad_method(this):
|
||||
12 |+ def extra_bad_method(self):
|
||||
13 13 | pass
|
||||
14 14 |
|
||||
15 15 | def good_method(self):
|
||||
|
||||
N805.py:63:29: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:64:29: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
61 | pass
|
||||
62 |
|
||||
63 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
62 | pass
|
||||
63 |
|
||||
64 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
| ^^^^ N805
|
||||
64 | pass
|
||||
65 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 | def good_method_pos_only(self, blah, /, something: str):
|
||||
61 61 | pass
|
||||
62 62 |
|
||||
63 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
63 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
64 64 | pass
|
||||
65 65 |
|
||||
61 61 | def good_method_pos_only(self, blah, /, something: str):
|
||||
62 62 | pass
|
||||
63 63 |
|
||||
64 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
64 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
65 65 | pass
|
||||
66 66 |
|
||||
67 67 |
|
||||
|
||||
N805.py:69:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:70:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
67 | class ModelClass:
|
||||
68 | @hybrid_property
|
||||
69 | def bad(cls):
|
||||
68 | class ModelClass:
|
||||
69 | @hybrid_property
|
||||
70 | def bad(cls):
|
||||
| ^^^ N805
|
||||
70 | pass
|
||||
71 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
66 66 |
|
||||
67 67 | class ModelClass:
|
||||
68 68 | @hybrid_property
|
||||
69 |- def bad(cls):
|
||||
69 |+ def bad(self):
|
||||
70 70 | pass
|
||||
71 71 |
|
||||
72 72 | @bad.expression
|
||||
67 67 |
|
||||
68 68 | class ModelClass:
|
||||
69 69 | @hybrid_property
|
||||
70 |- def bad(cls):
|
||||
70 |+ def bad(self):
|
||||
71 71 | pass
|
||||
72 72 |
|
||||
73 73 | @bad.expression
|
||||
|
||||
N805.py:77:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:78:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
76 | @bad.wtf
|
||||
77 | def bad(cls):
|
||||
77 | @bad.wtf
|
||||
78 | def bad(cls):
|
||||
| ^^^ N805
|
||||
78 | pass
|
||||
79 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
74 74 | pass
|
||||
75 75 |
|
||||
76 76 | @bad.wtf
|
||||
77 |- def bad(cls):
|
||||
77 |+ def bad(self):
|
||||
78 78 | pass
|
||||
79 79 |
|
||||
80 80 | @hybrid_property
|
||||
75 75 | pass
|
||||
76 76 |
|
||||
77 77 | @bad.wtf
|
||||
78 |- def bad(cls):
|
||||
78 |+ def bad(self):
|
||||
79 79 | pass
|
||||
80 80 |
|
||||
81 81 | @hybrid_property
|
||||
|
||||
N805.py:93:19: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:94:19: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
92 | @foobar.thisisstatic
|
||||
93 | def badstatic(foo):
|
||||
93 | @foobar.thisisstatic
|
||||
94 | def badstatic(foo):
|
||||
| ^^^ N805
|
||||
94 | pass
|
||||
95 | pass
|
||||
|
|
||||
= help: Rename `foo` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
90 90 | pass
|
||||
91 91 |
|
||||
92 92 | @foobar.thisisstatic
|
||||
93 |- def badstatic(foo):
|
||||
93 |+ def badstatic(self):
|
||||
94 94 | pass
|
||||
95 95 |
|
||||
91 91 | pass
|
||||
92 92 |
|
||||
93 93 | @foobar.thisisstatic
|
||||
94 |- def badstatic(foo):
|
||||
94 |+ def badstatic(self):
|
||||
95 95 | pass
|
||||
96 96 |
|
||||
97 97 | class SelfInArgsClass:
|
||||
|
||||
N805.py:98:26: N805 First argument of a method should be named `self`
|
||||
|
|
||||
@@ -166,66 +167,45 @@ N805.py:110:24: N805 First argument of a method should be named `self`
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
N805.py:115:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:114:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
114 | class RenamingInMethodBodyClass:
|
||||
115 | def bad_method(this):
|
||||
113 | class RenamingInMethodBodyClass:
|
||||
114 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
116 | this = this
|
||||
117 | this
|
||||
115 | this = this
|
||||
116 | this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
111 111 | pass
|
||||
112 112 |
|
||||
113 113 |
|
||||
114 114 | class RenamingInMethodBodyClass:
|
||||
115 |- def bad_method(this):
|
||||
116 |- this = this
|
||||
117 |- this
|
||||
115 |+ def bad_method(self):
|
||||
116 |+ self = self
|
||||
117 |+ self
|
||||
118 118 |
|
||||
119 119 | def bad_method(this):
|
||||
120 120 | self = this
|
||||
113 113 | class RenamingInMethodBodyClass:
|
||||
114 |- def bad_method(this):
|
||||
115 |- this = this
|
||||
116 |- this
|
||||
114 |+ def bad_method(self):
|
||||
115 |+ self = self
|
||||
116 |+ self
|
||||
117 117 |
|
||||
118 118 | def bad_method(this):
|
||||
119 119 | self = this
|
||||
|
||||
N805.py:119:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:118:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
117 | this
|
||||
118 |
|
||||
119 | def bad_method(this):
|
||||
116 | this
|
||||
117 |
|
||||
118 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
120 | self = this
|
||||
119 | self = this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | this = this
|
||||
117 117 | this
|
||||
118 118 |
|
||||
119 |- def bad_method(this):
|
||||
120 |- self = this
|
||||
119 |+ def bad_method(self):
|
||||
120 |+ self = self
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
|
||||
N805.py:124:17: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
123 | class RenamingWithNFKC:
|
||||
124 | def formula(household):
|
||||
| ^^^^^^^^^ N805
|
||||
125 | hºusehold(1)
|
||||
|
|
||||
= help: Rename `household` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
124 |- def formula(household):
|
||||
125 |- hºusehold(1)
|
||||
124 |+ def formula(self):
|
||||
125 |+ self(1)
|
||||
115 115 | this = this
|
||||
116 116 | this
|
||||
117 117 |
|
||||
118 |- def bad_method(this):
|
||||
119 |- self = this
|
||||
118 |+ def bad_method(self):
|
||||
119 |+ self = self
|
||||
|
||||
@@ -20,140 +20,141 @@ N805.py:7:20: N805 [*] First argument of a method should be named `self`
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
|
||||
N805.py:11:30: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:12:30: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
10 | if False:
|
||||
11 | def extra_bad_method(this):
|
||||
11 |
|
||||
12 | def extra_bad_method(this):
|
||||
| ^^^^ N805
|
||||
12 | pass
|
||||
13 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
8 8 | pass
|
||||
9 9 |
|
||||
10 10 | if False:
|
||||
11 |- def extra_bad_method(this):
|
||||
11 |+ def extra_bad_method(self):
|
||||
12 12 | pass
|
||||
13 13 |
|
||||
14 14 | def good_method(self):
|
||||
11 11 |
|
||||
12 |- def extra_bad_method(this):
|
||||
12 |+ def extra_bad_method(self):
|
||||
13 13 | pass
|
||||
14 14 |
|
||||
15 15 | def good_method(self):
|
||||
|
||||
N805.py:30:15: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:31:15: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
29 | @pydantic.validator
|
||||
30 | def lower(cls, my_field: str) -> str:
|
||||
30 | @pydantic.validator
|
||||
31 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
31 | pass
|
||||
32 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
27 27 | return x
|
||||
28 28 |
|
||||
29 29 | @pydantic.validator
|
||||
30 |- def lower(cls, my_field: str) -> str:
|
||||
30 |+ def lower(self, my_field: str) -> str:
|
||||
31 31 | pass
|
||||
32 32 |
|
||||
33 33 | @pydantic.validator("my_field")
|
||||
28 28 | return x
|
||||
29 29 |
|
||||
30 30 | @pydantic.validator
|
||||
31 |- def lower(cls, my_field: str) -> str:
|
||||
31 |+ def lower(self, my_field: str) -> str:
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 34 | @pydantic.validator("my_field")
|
||||
|
||||
N805.py:34:15: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:35:15: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
33 | @pydantic.validator("my_field")
|
||||
34 | def lower(cls, my_field: str) -> str:
|
||||
34 | @pydantic.validator("my_field")
|
||||
35 | def lower(cls, my_field: str) -> str:
|
||||
| ^^^ N805
|
||||
35 | pass
|
||||
36 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
31 31 | pass
|
||||
32 32 |
|
||||
33 33 | @pydantic.validator("my_field")
|
||||
34 |- def lower(cls, my_field: str) -> str:
|
||||
34 |+ def lower(self, my_field: str) -> str:
|
||||
35 35 | pass
|
||||
36 36 |
|
||||
37 37 | def __init__(self):
|
||||
32 32 | pass
|
||||
33 33 |
|
||||
34 34 | @pydantic.validator("my_field")
|
||||
35 |- def lower(cls, my_field: str) -> str:
|
||||
35 |+ def lower(self, my_field: str) -> str:
|
||||
36 36 | pass
|
||||
37 37 |
|
||||
38 38 | def __init__(self):
|
||||
|
||||
N805.py:63:29: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:64:29: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
61 | pass
|
||||
62 |
|
||||
63 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
62 | pass
|
||||
63 |
|
||||
64 | def bad_method_pos_only(this, blah, /, something: str):
|
||||
| ^^^^ N805
|
||||
64 | pass
|
||||
65 | pass
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
60 60 | def good_method_pos_only(self, blah, /, something: str):
|
||||
61 61 | pass
|
||||
62 62 |
|
||||
63 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
63 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
64 64 | pass
|
||||
65 65 |
|
||||
61 61 | def good_method_pos_only(self, blah, /, something: str):
|
||||
62 62 | pass
|
||||
63 63 |
|
||||
64 |- def bad_method_pos_only(this, blah, /, something: str):
|
||||
64 |+ def bad_method_pos_only(self, blah, /, something: str):
|
||||
65 65 | pass
|
||||
66 66 |
|
||||
67 67 |
|
||||
|
||||
N805.py:69:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:70:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
67 | class ModelClass:
|
||||
68 | @hybrid_property
|
||||
69 | def bad(cls):
|
||||
68 | class ModelClass:
|
||||
69 | @hybrid_property
|
||||
70 | def bad(cls):
|
||||
| ^^^ N805
|
||||
70 | pass
|
||||
71 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
66 66 |
|
||||
67 67 | class ModelClass:
|
||||
68 68 | @hybrid_property
|
||||
69 |- def bad(cls):
|
||||
69 |+ def bad(self):
|
||||
70 70 | pass
|
||||
71 71 |
|
||||
72 72 | @bad.expression
|
||||
67 67 |
|
||||
68 68 | class ModelClass:
|
||||
69 69 | @hybrid_property
|
||||
70 |- def bad(cls):
|
||||
70 |+ def bad(self):
|
||||
71 71 | pass
|
||||
72 72 |
|
||||
73 73 | @bad.expression
|
||||
|
||||
N805.py:77:13: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:78:13: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
76 | @bad.wtf
|
||||
77 | def bad(cls):
|
||||
77 | @bad.wtf
|
||||
78 | def bad(cls):
|
||||
| ^^^ N805
|
||||
78 | pass
|
||||
79 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
74 74 | pass
|
||||
75 75 |
|
||||
76 76 | @bad.wtf
|
||||
77 |- def bad(cls):
|
||||
77 |+ def bad(self):
|
||||
78 78 | pass
|
||||
79 79 |
|
||||
80 80 | @hybrid_property
|
||||
75 75 | pass
|
||||
76 76 |
|
||||
77 77 | @bad.wtf
|
||||
78 |- def bad(cls):
|
||||
78 |+ def bad(self):
|
||||
79 79 | pass
|
||||
80 80 |
|
||||
81 81 | @hybrid_property
|
||||
|
||||
N805.py:85:14: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:86:14: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
84 | @good.expression
|
||||
85 | def good(cls):
|
||||
85 | @good.expression
|
||||
86 | def good(cls):
|
||||
| ^^^ N805
|
||||
86 | pass
|
||||
87 | pass
|
||||
|
|
||||
= help: Rename `cls` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
82 82 | pass
|
||||
83 83 |
|
||||
84 84 | @good.expression
|
||||
85 |- def good(cls):
|
||||
85 |+ def good(self):
|
||||
86 86 | pass
|
||||
87 87 |
|
||||
88 88 | @good.wtf
|
||||
83 83 | pass
|
||||
84 84 |
|
||||
85 85 | @good.expression
|
||||
86 |- def good(cls):
|
||||
86 |+ def good(self):
|
||||
87 87 | pass
|
||||
88 88 |
|
||||
89 89 | @good.wtf
|
||||
|
||||
N805.py:98:26: N805 First argument of a method should be named `self`
|
||||
|
|
||||
@@ -204,66 +205,45 @@ N805.py:110:24: N805 First argument of a method should be named `self`
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
N805.py:115:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:114:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
114 | class RenamingInMethodBodyClass:
|
||||
115 | def bad_method(this):
|
||||
113 | class RenamingInMethodBodyClass:
|
||||
114 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
116 | this = this
|
||||
117 | this
|
||||
115 | this = this
|
||||
116 | this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
111 111 | pass
|
||||
112 112 |
|
||||
113 113 |
|
||||
114 114 | class RenamingInMethodBodyClass:
|
||||
115 |- def bad_method(this):
|
||||
116 |- this = this
|
||||
117 |- this
|
||||
115 |+ def bad_method(self):
|
||||
116 |+ self = self
|
||||
117 |+ self
|
||||
118 118 |
|
||||
119 119 | def bad_method(this):
|
||||
120 120 | self = this
|
||||
113 113 | class RenamingInMethodBodyClass:
|
||||
114 |- def bad_method(this):
|
||||
115 |- this = this
|
||||
116 |- this
|
||||
114 |+ def bad_method(self):
|
||||
115 |+ self = self
|
||||
116 |+ self
|
||||
117 117 |
|
||||
118 118 | def bad_method(this):
|
||||
119 119 | self = this
|
||||
|
||||
N805.py:119:20: N805 [*] First argument of a method should be named `self`
|
||||
N805.py:118:20: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
117 | this
|
||||
118 |
|
||||
119 | def bad_method(this):
|
||||
116 | this
|
||||
117 |
|
||||
118 | def bad_method(this):
|
||||
| ^^^^ N805
|
||||
120 | self = this
|
||||
119 | self = this
|
||||
|
|
||||
= help: Rename `this` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
116 116 | this = this
|
||||
117 117 | this
|
||||
118 118 |
|
||||
119 |- def bad_method(this):
|
||||
120 |- self = this
|
||||
119 |+ def bad_method(self):
|
||||
120 |+ self = self
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
|
||||
N805.py:124:17: N805 [*] First argument of a method should be named `self`
|
||||
|
|
||||
123 | class RenamingWithNFKC:
|
||||
124 | def formula(household):
|
||||
| ^^^^^^^^^ N805
|
||||
125 | hºusehold(1)
|
||||
|
|
||||
= help: Rename `household` to `self`
|
||||
|
||||
ℹ Unsafe fix
|
||||
121 121 |
|
||||
122 122 |
|
||||
123 123 | class RenamingWithNFKC:
|
||||
124 |- def formula(household):
|
||||
125 |- hºusehold(1)
|
||||
124 |+ def formula(self):
|
||||
125 |+ self(1)
|
||||
115 115 | this = this
|
||||
116 116 | this
|
||||
117 117 |
|
||||
118 |- def bad_method(this):
|
||||
119 |- self = this
|
||||
118 |+ def bad_method(self):
|
||||
119 |+ self = self
|
||||
|
||||
@@ -44,28 +44,22 @@ use crate::checkers::ast::Checker;
|
||||
/// filtered.extend(x for x in original if x % 2)
|
||||
/// ```
|
||||
#[violation]
|
||||
pub struct ManualListComprehension {
|
||||
is_async: bool,
|
||||
}
|
||||
pub struct ManualListComprehension;
|
||||
|
||||
impl Violation for ManualListComprehension {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let ManualListComprehension { is_async } = self;
|
||||
match is_async {
|
||||
false => format!("Use a list comprehension to create a transformed list"),
|
||||
true => format!("Use an async list comprehension to create a transformed list"),
|
||||
}
|
||||
format!("Use a list comprehension to create a transformed list")
|
||||
}
|
||||
}
|
||||
|
||||
/// PERF401
|
||||
pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::StmtFor) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = &*for_stmt.target else {
|
||||
pub(crate) fn manual_list_comprehension(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (stmt, if_test) = match &*for_stmt.body {
|
||||
let (stmt, if_test) = match body {
|
||||
// ```python
|
||||
// for x in y:
|
||||
// if z:
|
||||
@@ -127,13 +121,10 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`), unless it's async, which
|
||||
// `manual-list-copy` doesn't cover.
|
||||
if !for_stmt.is_async {
|
||||
if if_test.is_none() {
|
||||
if arg.as_name_expr().is_some_and(|arg| arg.id == *id) {
|
||||
return;
|
||||
}
|
||||
// Ignore direct list copies (e.g., `for x in y: filtered.append(x)`).
|
||||
if if_test.is_none() {
|
||||
if arg.as_name_expr().is_some_and(|arg| arg.id == *id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,10 +179,7 @@ pub(crate) fn manual_list_comprehension(checker: &mut Checker, for_stmt: &ast::S
|
||||
return;
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
ManualListComprehension {
|
||||
is_async: for_stmt.is_async,
|
||||
},
|
||||
*range,
|
||||
));
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(ManualListComprehension, *range));
|
||||
}
|
||||
|
||||
@@ -45,16 +45,12 @@ impl Violation for ManualListCopy {
|
||||
}
|
||||
|
||||
/// PERF402
|
||||
pub(crate) fn manual_list_copy(checker: &mut Checker, for_stmt: &ast::StmtFor) {
|
||||
if for_stmt.is_async {
|
||||
return;
|
||||
}
|
||||
|
||||
let Expr::Name(ast::ExprName { id, .. }) = &*for_stmt.target else {
|
||||
pub(crate) fn manual_list_copy(checker: &mut Checker, target: &Expr, body: &[Stmt]) {
|
||||
let Expr::Name(ast::ExprName { id, .. }) = target else {
|
||||
return;
|
||||
};
|
||||
|
||||
let [stmt] = &*for_stmt.body else {
|
||||
let [stmt] = body else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,18 +17,4 @@ PERF401.py:13:9: PERF401 Use a list comprehension to create a transformed list
|
||||
| ^^^^^^^^^^^^^^^^^^^^ PERF401
|
||||
|
|
||||
|
||||
PERF401.py:82:13: PERF401 Use an async list comprehension to create a transformed list
|
||||
|
|
||||
80 | async for i in items:
|
||||
81 | if i % 2:
|
||||
82 | result.append(i) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^ PERF401
|
||||
|
|
||||
|
||||
PERF401.py:89:9: PERF401 Use an async list comprehension to create a transformed list
|
||||
|
|
||||
87 | result = []
|
||||
88 | async for i in items:
|
||||
89 | result.append(i) # PERF401
|
||||
| ^^^^^^^^^^^^^^^^ PERF401
|
||||
|
|
||||
|
||||
@@ -31,21 +31,18 @@ use super::LogicalLine;
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `indent-width` with a value other than `4`.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `indent-width`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#indentation
|
||||
/// [formatter]:https://docs.astral.sh/ruff/formatter/
|
||||
#[violation]
|
||||
pub struct IndentationWithInvalidMultiple {
|
||||
indent_width: usize,
|
||||
indent_size: usize,
|
||||
}
|
||||
|
||||
impl Violation for IndentationWithInvalidMultiple {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { indent_width } = self;
|
||||
format!("Indentation is not a multiple of {indent_width}")
|
||||
let Self { indent_size } = self;
|
||||
format!("Indentation is not a multiple of {indent_size}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,21 +71,18 @@ impl Violation for IndentationWithInvalidMultiple {
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `indent-width` with a value other than `4`.
|
||||
///
|
||||
/// ## Options
|
||||
/// - `indent-width`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#indentation
|
||||
/// [formatter]:https://docs.astral.sh/ruff/formatter/
|
||||
#[violation]
|
||||
pub struct IndentationWithInvalidMultipleComment {
|
||||
indent_width: usize,
|
||||
indent_size: usize,
|
||||
}
|
||||
|
||||
impl Violation for IndentationWithInvalidMultipleComment {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let Self { indent_width } = self;
|
||||
format!("Indentation is not a multiple of {indent_width} (comment)")
|
||||
let Self { indent_size } = self;
|
||||
format!("Indentation is not a multiple of {indent_size} (comment)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,13 +257,9 @@ pub(crate) fn indentation(
|
||||
|
||||
if indent_level % indent_size != 0 {
|
||||
diagnostics.push(if logical_line.is_comment_only() {
|
||||
DiagnosticKind::from(IndentationWithInvalidMultipleComment {
|
||||
indent_width: indent_size,
|
||||
})
|
||||
DiagnosticKind::from(IndentationWithInvalidMultipleComment { indent_size })
|
||||
} else {
|
||||
DiagnosticKind::from(IndentationWithInvalidMultiple {
|
||||
indent_width: indent_size,
|
||||
})
|
||||
DiagnosticKind::from(IndentationWithInvalidMultiple { indent_size })
|
||||
});
|
||||
}
|
||||
let indent_expect = prev_logical_line
|
||||
|
||||
@@ -60,7 +60,7 @@ pub(crate) fn redundant_backslash(
|
||||
let start = locator.line_start(token.start());
|
||||
start_index = continuation_lines
|
||||
.binary_search(&start)
|
||||
.unwrap_or_else(|err_index| err_index);
|
||||
.map_or_else(|err_index| err_index, |ok_index| ok_index);
|
||||
}
|
||||
parens += 1;
|
||||
}
|
||||
@@ -70,7 +70,7 @@ pub(crate) fn redundant_backslash(
|
||||
let end = locator.line_start(token.start());
|
||||
let end_index = continuation_lines
|
||||
.binary_search(&end)
|
||||
.unwrap_or_else(|err_index| err_index);
|
||||
.map_or_else(|err_index| err_index, |ok_index| ok_index);
|
||||
for continuation_line in &continuation_lines[start_index..end_index] {
|
||||
let backslash_end = locator.line_end(*continuation_line);
|
||||
let backslash_start = backslash_end - TextSize::new(1);
|
||||
|
||||
@@ -205,9 +205,6 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test_case(Rule::UnusedVariable, Path::new("F841_4.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("__init__.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_24/__init__.py"))]
|
||||
#[test_case(Rule::UnusedImport, Path::new("F401_25__all/__init__.py"))]
|
||||
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!(
|
||||
"preview__{}_{}",
|
||||
@@ -252,6 +249,19 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_unused_import_opt_in_to_fix() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pyflakes/__init__.py"),
|
||||
&LinterSettings {
|
||||
ignore_init_module_imports: false,
|
||||
..LinterSettings::for_rules(vec![Rule::UnusedImport])
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_builtins() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
@@ -601,7 +611,7 @@ mod tests {
|
||||
&indexer,
|
||||
);
|
||||
let LinterResult {
|
||||
data: mut diagnostics,
|
||||
data: (mut diagnostics, ..),
|
||||
..
|
||||
} = check_path(
|
||||
Path::new("<filename>"),
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
use std::borrow::Cow;
|
||||
use std::iter;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use anyhow::Result;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Stmt, StmtImportFrom};
|
||||
use ruff_python_semantic::{AnyImport, Exceptions, Imported, NodeId, Scope};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix;
|
||||
use crate::registry::Rule;
|
||||
use crate::rules::{isort, isort::ImportSection, isort::ImportType};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
enum UnusedImportContext {
|
||||
ExceptHandler,
|
||||
Init { first_party: bool },
|
||||
Init,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
@@ -96,7 +93,7 @@ impl Violation for UnusedImport {
|
||||
"`{name}` imported but unused; consider using `importlib.util.find_spec` to test for availability"
|
||||
)
|
||||
}
|
||||
Some(UnusedImportContext::Init { .. }) => {
|
||||
Some(UnusedImportContext::Init) => {
|
||||
format!(
|
||||
"`{name}` imported but unused; consider removing, adding to `__all__`, or using a redundant alias"
|
||||
)
|
||||
@@ -107,47 +104,14 @@ impl Violation for UnusedImport {
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
let UnusedImport { name, multiple, .. } = self;
|
||||
let resolution = match self.context {
|
||||
Some(UnusedImportContext::Init { first_party: true }) => "Use a redundant alias",
|
||||
_ => "Remove unused import",
|
||||
};
|
||||
Some(if *multiple {
|
||||
resolution.to_string()
|
||||
"Remove unused import".to_string()
|
||||
} else {
|
||||
format!("{resolution}: `{name}`")
|
||||
format!("Remove unused import: `{name}`")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_first_party(qualified_name: &str, level: u32, checker: &Checker) -> bool {
|
||||
let category = isort::categorize(
|
||||
qualified_name,
|
||||
level,
|
||||
&checker.settings.src,
|
||||
checker.package(),
|
||||
checker.settings.isort.detect_same_package,
|
||||
&checker.settings.isort.known_modules,
|
||||
checker.settings.target_version,
|
||||
checker.settings.isort.no_sections,
|
||||
&checker.settings.isort.section_order,
|
||||
&checker.settings.isort.default_section,
|
||||
);
|
||||
matches! {
|
||||
category,
|
||||
ImportSection::Known(ImportType::FirstParty | ImportType::LocalFolder)
|
||||
}
|
||||
}
|
||||
|
||||
/// For some unused binding in an import statement...
|
||||
///
|
||||
/// __init__.py ∧ 1stpty → safe, convert to redundant-alias
|
||||
/// __init__.py ∧ stdlib → unsafe, remove
|
||||
/// __init__.py ∧ 3rdpty → unsafe, remove
|
||||
///
|
||||
/// ¬__init__.py ∧ 1stpty → safe, remove
|
||||
/// ¬__init__.py ∧ stdlib → safe, remove
|
||||
/// ¬__init__.py ∧ 3rdpty → safe, remove
|
||||
///
|
||||
pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut Vec<Diagnostic>) {
|
||||
// Collect all unused imports by statement.
|
||||
let mut unused: FxHashMap<(NodeId, Exceptions), Vec<ImportBinding>> = FxHashMap::default();
|
||||
@@ -196,82 +160,42 @@ pub(crate) fn unused_import(checker: &Checker, scope: &Scope, diagnostics: &mut
|
||||
}
|
||||
|
||||
let in_init = checker.path().ends_with("__init__.py");
|
||||
let fix_init = checker.settings.preview.is_enabled();
|
||||
let fix_init = !checker.settings.ignore_init_module_imports;
|
||||
|
||||
// Generate a diagnostic for every import, but share fixes across all imports within the same
|
||||
// Generate a diagnostic for every import, but share a fix across all imports within the same
|
||||
// statement (excluding those that are ignored).
|
||||
for ((import_statement, exceptions), bindings) in unused {
|
||||
for ((node_id, exceptions), imports) in unused {
|
||||
let in_except_handler =
|
||||
exceptions.intersects(Exceptions::MODULE_NOT_FOUND_ERROR | Exceptions::IMPORT_ERROR);
|
||||
let multiple = bindings.len() > 1;
|
||||
let level = match checker.semantic().statement(import_statement) {
|
||||
Stmt::Import(_) => 0,
|
||||
Stmt::ImportFrom(StmtImportFrom { level, .. }) => *level,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let multiple = imports.len() > 1;
|
||||
|
||||
// pair each binding with context; divide them by how we want to fix them
|
||||
let (to_reexport, to_remove): (Vec<_>, Vec<_>) = bindings
|
||||
.into_iter()
|
||||
.map(|binding| {
|
||||
let context = if in_except_handler {
|
||||
Some(UnusedImportContext::ExceptHandler)
|
||||
} else if in_init {
|
||||
Some(UnusedImportContext::Init {
|
||||
first_party: is_first_party(
|
||||
&binding.import.qualified_name().to_string(),
|
||||
level,
|
||||
checker,
|
||||
),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(binding, context)
|
||||
})
|
||||
.partition(|(_, context)| {
|
||||
matches!(
|
||||
context,
|
||||
Some(UnusedImportContext::Init { first_party: true })
|
||||
)
|
||||
});
|
||||
|
||||
// generate fixes that are shared across bindings in the statement
|
||||
let (fix_remove, fix_reexport) = if (!in_init || fix_init) && !in_except_handler {
|
||||
(
|
||||
fix_by_removing_imports(
|
||||
checker,
|
||||
import_statement,
|
||||
to_remove.iter().map(|(binding, _)| binding),
|
||||
in_init,
|
||||
)
|
||||
.ok(),
|
||||
fix_by_reexporting(
|
||||
checker,
|
||||
import_statement,
|
||||
to_reexport.iter().map(|(binding, _)| binding),
|
||||
)
|
||||
.ok(),
|
||||
)
|
||||
let fix = if (!in_init || fix_init) && !in_except_handler {
|
||||
fix_imports(checker, node_id, &imports, in_init).ok()
|
||||
} else {
|
||||
(None, None)
|
||||
None
|
||||
};
|
||||
|
||||
for ((binding, context), fix) in iter::Iterator::chain(
|
||||
iter::zip(to_remove, iter::repeat(fix_remove)),
|
||||
iter::zip(to_reexport, iter::repeat(fix_reexport)),
|
||||
) {
|
||||
for ImportBinding {
|
||||
import,
|
||||
range,
|
||||
parent_range,
|
||||
} in imports
|
||||
{
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
UnusedImport {
|
||||
name: binding.import.qualified_name().to_string(),
|
||||
context,
|
||||
name: import.qualified_name().to_string(),
|
||||
context: if in_except_handler {
|
||||
Some(UnusedImportContext::ExceptHandler)
|
||||
} else if in_init {
|
||||
Some(UnusedImportContext::Init)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
multiple,
|
||||
},
|
||||
binding.range,
|
||||
range,
|
||||
);
|
||||
if let Some(range) = binding.parent_range {
|
||||
if let Some(range) = parent_range {
|
||||
diagnostic.set_parent(range.start());
|
||||
}
|
||||
if !in_except_handler {
|
||||
@@ -324,22 +248,20 @@ impl Ranged for ImportBinding<'_> {
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to remove unused imports from a statement.
|
||||
fn fix_by_removing_imports<'a>(
|
||||
fn fix_imports(
|
||||
checker: &Checker,
|
||||
node_id: NodeId,
|
||||
imports: impl Iterator<Item = &'a ImportBinding<'a>>,
|
||||
imports: &[ImportBinding],
|
||||
in_init: bool,
|
||||
) -> Result<Fix> {
|
||||
let statement = checker.semantic().statement(node_id);
|
||||
let parent = checker.semantic().parent_statement(node_id);
|
||||
|
||||
let member_names: Vec<Cow<'_, str>> = imports
|
||||
.iter()
|
||||
.map(|ImportBinding { import, .. }| import)
|
||||
.map(Imported::member_name)
|
||||
.collect();
|
||||
if member_names.is_empty() {
|
||||
bail!("Expected import bindings");
|
||||
}
|
||||
|
||||
let edit = fix::edits::remove_unused_imports(
|
||||
member_names.iter().map(AsRef::as_ref),
|
||||
@@ -349,43 +271,15 @@ fn fix_by_removing_imports<'a>(
|
||||
checker.stylist(),
|
||||
checker.indexer(),
|
||||
)?;
|
||||
|
||||
// It's unsafe to remove things from `__init__.py` because it can break public interfaces
|
||||
let applicability = if in_init {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
};
|
||||
|
||||
Ok(
|
||||
Fix::applicable_edit(edit, applicability).isolate(Checker::isolation(
|
||||
checker.semantic().parent_statement_id(node_id),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] to make bindings in a statement explicit, by changing from `import a` to
|
||||
/// `import a as a`.
|
||||
fn fix_by_reexporting<'a>(
|
||||
checker: &Checker,
|
||||
node_id: NodeId,
|
||||
imports: impl Iterator<Item = &'a ImportBinding<'a>>,
|
||||
) -> Result<Fix> {
|
||||
let statement = checker.semantic().statement(node_id);
|
||||
|
||||
let member_names = imports
|
||||
.map(|binding| binding.import.member_name())
|
||||
.collect::<Vec<_>>();
|
||||
if member_names.is_empty() {
|
||||
bail!("Expected import bindings");
|
||||
}
|
||||
|
||||
let edits = fix::edits::make_redundant_alias(member_names.iter().map(AsRef::as_ref), statement);
|
||||
|
||||
// Only emit a fix if there are edits
|
||||
let mut tail = edits.into_iter();
|
||||
let head = tail.next().ok_or(anyhow!("No edits to make"))?;
|
||||
|
||||
let isolation = Checker::isolation(checker.semantic().parent_statement_id(node_id));
|
||||
Ok(Fix::safe_edits(head, tail).isolate(isolation))
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||
|
|
||||
19 | import sys # F401: remove unused
|
||||
| ^^^ F401
|
||||
|
|
||||
= help: Remove unused import: `sys`
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | import argparse as argparse # Ok: is redundant alias
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 |-import sys # F401: remove unused
|
||||
20 19 |
|
||||
21 20 |
|
||||
22 21 | # first-party
|
||||
|
||||
__init__.py:33:15: F401 [*] `.unused` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||
|
|
||||
33 | from . import unused # F401: change to redundant alias
|
||||
| ^^^^^^ F401
|
||||
|
|
||||
= help: Use a redundant alias: `.unused`
|
||||
|
||||
ℹ Safe fix
|
||||
30 30 | from . import aliased as aliased # Ok: is redundant alias
|
||||
31 31 |
|
||||
32 32 |
|
||||
33 |-from . import unused # F401: change to redundant alias
|
||||
33 |+from . import unused as unused # F401: change to redundant alias
|
||||
34 34 |
|
||||
35 35 |
|
||||
36 36 | from . import renamed as bees # F401: no fix
|
||||
|
||||
__init__.py:36:26: F401 `.renamed` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||
|
|
||||
36 | from . import renamed as bees # F401: no fix
|
||||
| ^^^^ F401
|
||||
|
|
||||
= help: Use a redundant alias: `.renamed`
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||
---
|
||||
__init__.py:19:8: F401 [*] `sys` imported but unused; consider removing, adding to `__all__`, or using a redundant alias
|
||||
|
|
||||
19 | import sys # F401: remove unused
|
||||
| ^^^ F401
|
||||
|
|
||||
= help: Remove unused import: `sys`
|
||||
|
||||
ℹ Unsafe fix
|
||||
16 16 | import argparse # Ok: is exported in __all__
|
||||
17 17 |
|
||||
18 18 |
|
||||
19 |-import sys # F401: remove unused
|
||||
20 19 |
|
||||
21 20 |
|
||||
22 21 | # first-party
|
||||
@@ -64,7 +64,7 @@ pub(crate) fn bad_staticmethod_argument(
|
||||
..
|
||||
} = func;
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(crate) fn no_self_use(
|
||||
scope: &Scope,
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
) {
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ pub(crate) fn singledispatch_method(
|
||||
..
|
||||
} = func;
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ pub(crate) fn singledispatchmethod_function(
|
||||
..
|
||||
} = func;
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ pub(crate) fn super_without_brackets(checker: &mut Checker, func: &Expr) {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(parent) = checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
let Some(parent) = &checker.semantic().first_non_type_parent_scope(scope) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ use anyhow::Result;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_ast::{self as ast, Expr, ExprSlice, ExprSubscript, ExprTuple, Parameters, Stmt};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_source_file::Locator;
|
||||
@@ -29,7 +28,7 @@ use crate::importer::{ImportRequest, Importer};
|
||||
/// import functools
|
||||
///
|
||||
/// nums = [1, 2, 3]
|
||||
/// total = functools.reduce(lambda x, y: x + y, nums)
|
||||
/// sum = functools.reduce(lambda x, y: x + y, nums)
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
@@ -38,8 +37,10 @@ use crate::importer::{ImportRequest, Importer};
|
||||
/// import operator
|
||||
///
|
||||
/// nums = [1, 2, 3]
|
||||
/// total = functools.reduce(operator.add, nums)
|
||||
/// sum = functools.reduce(operator.add, nums)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
#[violation]
|
||||
pub struct ReimplementedOperator {
|
||||
operator: Operator,
|
||||
@@ -70,13 +71,6 @@ impl Violation for ReimplementedOperator {
|
||||
|
||||
/// FURB118
|
||||
pub(crate) fn reimplemented_operator(checker: &mut Checker, target: &FunctionLike) {
|
||||
// Ignore methods.
|
||||
if target.kind() == FunctionLikeKind::Function {
|
||||
if checker.semantic().current_scope().kind.is_class() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(params) = target.parameters() else {
|
||||
return;
|
||||
};
|
||||
@@ -119,7 +113,7 @@ impl Ranged for FunctionLike<'_> {
|
||||
fn range(&self) -> TextRange {
|
||||
match self {
|
||||
Self::Lambda(expr) => expr.range(),
|
||||
Self::Function(stmt) => stmt.identifier(),
|
||||
Self::Function(stmt) => stmt.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,18 +778,28 @@ FURB118.py:33:17: FURB118 [*] Use `operator.itemgetter(slice(None))` instead of
|
||||
35 36 |
|
||||
36 37 | def op_not2(x):
|
||||
|
||||
FURB118.py:36:5: FURB118 Use `operator.not_` instead of defining a function
|
||||
FURB118.py:36:1: FURB118 Use `operator.not_` instead of defining a function
|
||||
|
|
||||
36 | def op_not2(x):
|
||||
| ^^^^^^^ FURB118
|
||||
37 | return not x
|
||||
36 | / def op_not2(x):
|
||||
37 | | return not x
|
||||
| |________________^ FURB118
|
||||
|
|
||||
= help: Replace with `operator.not_`
|
||||
|
||||
FURB118.py:40:5: FURB118 Use `operator.add` instead of defining a function
|
||||
FURB118.py:40:1: FURB118 Use `operator.add` instead of defining a function
|
||||
|
|
||||
40 | def op_add2(x, y):
|
||||
| ^^^^^^^ FURB118
|
||||
41 | return x + y
|
||||
40 | / def op_add2(x, y):
|
||||
41 | | return x + y
|
||||
| |________________^ FURB118
|
||||
|
|
||||
= help: Replace with `operator.add`
|
||||
|
||||
FURB118.py:45:5: FURB118 Use `operator.add` instead of defining a function
|
||||
|
|
||||
44 | class Adder:
|
||||
45 | def add(x, y):
|
||||
| _____^
|
||||
46 | | return x + y
|
||||
| |____________________^ FURB118
|
||||
|
|
||||
= help: Replace with `operator.add`
|
||||
|
||||
@@ -48,7 +48,7 @@ impl SortingStyle {
|
||||
/// an "isort-style sort".
|
||||
///
|
||||
/// An isort-style sort sorts items first according to their casing:
|
||||
/// SCREAMING_SNAKE_CASE names (conventionally used for global constants)
|
||||
/// `SCREAMING_SNAKE_CASE` names (conventionally used for global constants)
|
||||
/// come first, followed by CamelCase names (conventionally used for
|
||||
/// classes), followed by anything else. Within each category,
|
||||
/// a [natural sort](https://en.wikipedia.org/wiki/Natural_sort_order)
|
||||
|
||||
@@ -10,9 +10,6 @@ use itertools::Itertools;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
use ruff_diagnostics::{Applicability, Diagnostic, FixAvailability};
|
||||
use ruff_notebook::Notebook;
|
||||
#[cfg(not(fuzzing))]
|
||||
use ruff_notebook::NotebookError;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_index::Indexer;
|
||||
@@ -32,6 +29,9 @@ use crate::rules::pycodestyle::rules::syntax_error;
|
||||
use crate::settings::types::UnsafeFixes;
|
||||
use crate::settings::{flags, LinterSettings};
|
||||
use crate::source_kind::SourceKind;
|
||||
use ruff_notebook::Notebook;
|
||||
#[cfg(not(fuzzing))]
|
||||
use ruff_notebook::NotebookError;
|
||||
|
||||
#[cfg(not(fuzzing))]
|
||||
pub(crate) fn test_resource_path(path: impl AsRef<Path>) -> std::path::PathBuf {
|
||||
@@ -123,7 +123,7 @@ pub(crate) fn test_contents<'a>(
|
||||
&indexer,
|
||||
);
|
||||
let LinterResult {
|
||||
data: diagnostics,
|
||||
data: (diagnostics, _imports),
|
||||
error,
|
||||
} = check_path(
|
||||
path,
|
||||
@@ -190,7 +190,7 @@ pub(crate) fn test_contents<'a>(
|
||||
);
|
||||
|
||||
let LinterResult {
|
||||
data: fixed_diagnostics,
|
||||
data: (fixed_diagnostics, _),
|
||||
error: fixed_error,
|
||||
} = check_path(
|
||||
path,
|
||||
|
||||
@@ -715,21 +715,17 @@ where
|
||||
/// assert_eq!(format_import_from(1, None), ".".to_string());
|
||||
/// assert_eq!(format_import_from(1, Some("foo")), ".foo".to_string());
|
||||
/// ```
|
||||
pub fn format_import_from(level: u32, module: Option<&str>) -> Cow<str> {
|
||||
match (level, module) {
|
||||
(0, Some(module)) => Cow::Borrowed(module),
|
||||
(level, module) => {
|
||||
let mut module_name =
|
||||
String::with_capacity((level as usize) + module.map_or(0, str::len));
|
||||
for _ in 0..level {
|
||||
module_name.push('.');
|
||||
}
|
||||
if let Some(module) = module {
|
||||
module_name.push_str(module);
|
||||
}
|
||||
Cow::Owned(module_name)
|
||||
pub fn format_import_from(level: u32, module: Option<&str>) -> String {
|
||||
let mut module_name = String::with_capacity(16);
|
||||
if level > 0 {
|
||||
for _ in 0..level {
|
||||
module_name.push('.');
|
||||
}
|
||||
}
|
||||
if let Some(module) = module {
|
||||
module_name.push_str(module);
|
||||
}
|
||||
module_name
|
||||
}
|
||||
|
||||
/// Format the member reference name for a relative import.
|
||||
@@ -744,8 +740,9 @@ pub fn format_import_from(level: u32, module: Option<&str>) -> Cow<str> {
|
||||
/// assert_eq!(format_import_from_member(1, Some("foo"), "bar"), ".foo.bar".to_string());
|
||||
/// ```
|
||||
pub fn format_import_from_member(level: u32, module: Option<&str>, member: &str) -> String {
|
||||
let mut qualified_name =
|
||||
String::with_capacity((level as usize) + module.map_or(0, str::len) + 1 + member.len());
|
||||
let mut qualified_name = String::with_capacity(
|
||||
(level as usize) + module.as_ref().map_or(0, |module| module.len()) + 1 + member.len(),
|
||||
);
|
||||
if level > 0 {
|
||||
for _ in 0..level {
|
||||
qualified_name.push('.');
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use rustc_hash::FxHashMap;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A representation of an individual name imported via any import statement.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AnyImport<'a> {
|
||||
@@ -112,3 +117,60 @@ impl FutureImport for AnyImport<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of a module reference in an import statement.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ModuleImport {
|
||||
module: String,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl ModuleImport {
|
||||
pub fn new(module: String, range: TextRange) -> Self {
|
||||
Self { module, range }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ModuleImport> for TextRange {
|
||||
fn from(import: &ModuleImport) -> TextRange {
|
||||
import.range
|
||||
}
|
||||
}
|
||||
|
||||
/// A representation of the import dependencies between modules.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct ImportMap {
|
||||
/// A map from dot-delimited module name to the list of imports in that module.
|
||||
module_to_imports: FxHashMap<String, Vec<ModuleImport>>,
|
||||
}
|
||||
|
||||
impl ImportMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
module_to_imports: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, module: String, imports_vec: Vec<ModuleImport>) {
|
||||
self.module_to_imports.insert(module, imports_vec);
|
||||
}
|
||||
|
||||
pub fn extend(&mut self, other: Self) {
|
||||
self.module_to_imports.extend(other.module_to_imports);
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> std::collections::hash_map::Iter<String, Vec<ModuleImport>> {
|
||||
self.module_to_imports.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a ImportMap {
|
||||
type IntoIter = std::collections::hash_map::Iter<'a, String, Vec<ModuleImport>>;
|
||||
type Item = (&'a String, &'a Vec<ModuleImport>);
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,7 +1006,7 @@ impl ConversionFlag {
|
||||
pub struct DebugText {
|
||||
/// The text between the `{` and the expression node.
|
||||
pub leading: String,
|
||||
/// The text between the expression and the conversion, the `format_spec`, or the `}`, depending on what's present in the source
|
||||
/// The text between the expression and the conversion, the format_spec, or the `}`, depending on what's present in the source
|
||||
pub trailing: String,
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,21 @@ python scripts/check_ecosystem.py --checkouts target/checkouts --projects github
|
||||
cargo run --bin ruff_dev -- format-dev --stability-check --error-file target/formatter-ecosystem-errors.txt --multi-project target/checkouts
|
||||
```
|
||||
|
||||
**Shrinking** To shrink a formatter error from an entire file to a minimal reproducible example,
|
||||
you can use `ruff_shrinking`:
|
||||
|
||||
```shell
|
||||
cargo run --bin ruff_shrinking -- <your_file> target/shrinking.py "Unstable formatting" "target/debug/ruff_dev format-dev --stability-check target/shrinking.py"
|
||||
```
|
||||
|
||||
The first argument is the input file, the second is the output file where the candidates
|
||||
and the eventual minimized version will be written to. The third argument is a regex matching the
|
||||
error message, e.g. "Unstable formatting" or "Formatter error". The last argument is the command
|
||||
with the error, e.g. running the stability check on the candidate file. The script will try various
|
||||
strategies to remove parts of the code. If the output of the command still matches, it will use that
|
||||
slightly smaller code as starting point for the next iteration, otherwise it will revert and try
|
||||
a different strategy until all strategies are exhausted.
|
||||
|
||||
## Helper structs
|
||||
|
||||
To abstract formatting something into a helper, create a new struct with the data you want to
|
||||
|
||||
75
crates/ruff_python_formatter/shrink_formatter_errors.py
Normal file
75
crates/ruff_python_formatter/shrink_formatter_errors.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""Take `format-dev --stability-check` output and shrink all stability errors into a
|
||||
single Python file. Used to update https://github.com/astral-sh/ruff/issues/5828 ."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
from subprocess import check_output
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
root = Path(
|
||||
check_output(["git", "rev-parse", "--show-toplevel"], text=True).strip(),
|
||||
)
|
||||
target = root.joinpath("target")
|
||||
|
||||
error_report = target.joinpath("formatter-ecosystem-errors.txt")
|
||||
error_lines_prefix = "Unstable formatting "
|
||||
|
||||
|
||||
def get_filenames() -> list[str]:
|
||||
files = []
|
||||
for line in error_report.read_text().splitlines():
|
||||
if not line.startswith(error_lines_prefix):
|
||||
continue
|
||||
files.append(line.removeprefix(error_lines_prefix))
|
||||
return files
|
||||
|
||||
|
||||
def shrink_file(file: str) -> tuple[str, str]:
|
||||
"""Returns filename and minimization"""
|
||||
with NamedTemporaryFile(suffix=".py") as temp_file:
|
||||
print(f"Starting {file}")
|
||||
ruff_dev = target.joinpath("release").joinpath("ruff_dev")
|
||||
check_output(
|
||||
[
|
||||
target.joinpath("release").joinpath("ruff_shrinking"),
|
||||
file,
|
||||
temp_file.name,
|
||||
"Unstable formatting",
|
||||
f"{ruff_dev} format-dev --stability-check {temp_file.name}",
|
||||
],
|
||||
)
|
||||
print(f"Finished {file}")
|
||||
return file, Path(temp_file.name).read_text()
|
||||
|
||||
|
||||
def main():
|
||||
storage = target.joinpath("minimizations.json")
|
||||
output_file = target.joinpath("minimizations.py")
|
||||
if storage.is_file():
|
||||
outputs = json.loads(storage.read_text())
|
||||
else:
|
||||
outputs = {}
|
||||
files = sorted(set(get_filenames()) - set(outputs))
|
||||
# Each process will saturate one core
|
||||
with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
tasks = [executor.submit(shrink_file, file) for file in files]
|
||||
for future in tqdm(as_completed(tasks), total=len(files)):
|
||||
file, output = future.result()
|
||||
outputs[file] = output
|
||||
storage.write_text(json.dumps(outputs, indent=4))
|
||||
|
||||
# Write to one shareable python file
|
||||
with output_file.open("w") as formatted:
|
||||
for file, code in sorted(json.loads(storage.read_text()).items()):
|
||||
file = file.split("/target/checkouts/")[1]
|
||||
formatted.write(f"# {file}\n{code}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -246,7 +246,7 @@ impl<K: std::hash::Hash + Eq, V> MultiMap<K, V> {
|
||||
/// Returns `true` if `key` has any *leading*, *dangling*, or *trailing* parts.
|
||||
#[allow(unused)]
|
||||
pub(super) fn has(&self, key: &K) -> bool {
|
||||
self.index.contains_key(key)
|
||||
self.index.get(key).is_some()
|
||||
}
|
||||
|
||||
/// Returns the *leading*, *dangling*, and *trailing* parts of `key`.
|
||||
@@ -382,16 +382,16 @@ where
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct InOrderEntry {
|
||||
/// Index into the [`MultiMap::parts`] vector where the leading parts of this entry start
|
||||
/// Index into the [MultiMap::parts] vector where the leading parts of this entry start
|
||||
leading_start: PartIndex,
|
||||
|
||||
/// Index into the [`MultiMap::parts`] vector where the dangling parts (and, thus, the leading parts end) start.
|
||||
/// Index into the [MultiMap::parts] vector where the dangling parts (and, thus, the leading parts end) start.
|
||||
dangling_start: PartIndex,
|
||||
|
||||
/// Index into the [`MultiMap::parts`] vector where the trailing parts (and, thus, the dangling parts end) of this entry start
|
||||
/// Index into the [MultiMap::parts] vector where the trailing parts (and, thus, the dangling parts end) of this entry start
|
||||
trailing_start: Option<PartIndex>,
|
||||
|
||||
/// Index into the [`MultiMap::parts`] vector where the trailing parts of this entry end
|
||||
/// Index into the [MultiMap::parts] vector where the trailing parts of this entry end
|
||||
trailing_end: Option<PartIndex>,
|
||||
|
||||
_count: Count<InOrderEntry>,
|
||||
@@ -505,7 +505,7 @@ impl InOrderEntry {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct OutOfOrderEntry {
|
||||
/// Index into the [`MultiMap::out_of_order`] vector at which offset the leaading vec is stored.
|
||||
/// Index into the [MultiMap::out_of_order] vector at which offset the leaading vec is stored.
|
||||
leading_index: usize,
|
||||
_count: Count<OutOfOrderEntry>,
|
||||
}
|
||||
|
||||
@@ -195,9 +195,9 @@ type CommentsMap<'a> = MultiMap<NodeRefEqualityKey<'a>, SourceComment>;
|
||||
/// Cloning `comments` is cheap as it only involves bumping a reference counter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Comments<'a> {
|
||||
/// The implementation uses an [Rc] so that [Comments] has a lifetime independent from the [`crate::Formatter`].
|
||||
/// Independent lifetimes are necessary to support the use case where a (formattable object)[`crate::Format`]
|
||||
/// iterates over all comments, and writes them into the [`crate::Formatter`] (mutably borrowing the [`crate::Formatter`] and in turn its context).
|
||||
/// The implementation uses an [Rc] so that [Comments] has a lifetime independent from the [crate::Formatter].
|
||||
/// Independent lifetimes are necessary to support the use case where a (formattable object)[crate::Format]
|
||||
/// iterates over all comments, and writes them into the [crate::Formatter] (mutably borrowing the [crate::Formatter] and in turn its context).
|
||||
///
|
||||
/// ```block
|
||||
/// for leading in f.context().comments().leading_comments(node) {
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_python_ast::{Expr, ExprAttribute, ExprNumberLiteral, Number};
|
||||
use ruff_python_trivia::{find_only_token_in_range, SimpleTokenKind};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::comments::dangling_comments;
|
||||
use crate::comments::{dangling_comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
|
||||
};
|
||||
@@ -123,6 +123,15 @@ impl FormatNodeRule<ExprAttribute> for FormatExprAttribute {
|
||||
write!(f, [format_inner])
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
// handle in `fmt_fields`
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprAttribute {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprBinOp;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::binary_like::BinaryLike;
|
||||
use crate::expression::has_parentheses;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
@@ -15,6 +16,15 @@ impl FormatNodeRule<ExprBinOp> for FormatExprBinOp {
|
||||
fn fmt_fields(&self, item: &ExprBinOp, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
BinaryLike::Binary(item).fmt(f)
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
// Handled inside of `fmt_fields`
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprBinOp {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::ExprBytesLiteral;
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::parentheses::{
|
||||
in_parentheses_only_group, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
@@ -20,6 +21,15 @@ impl FormatNodeRule<ExprBytesLiteral> for FormatExprBytesLiteral {
|
||||
.fmt(f),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
// Handled as part of `fmt_fields`
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprBytesLiteral {
|
||||
|
||||
@@ -2,7 +2,7 @@ use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{Expr, ExprCall};
|
||||
|
||||
use crate::comments::dangling_comments;
|
||||
use crate::comments::{dangling_comments, SourceComment};
|
||||
use crate::expression::parentheses::{
|
||||
is_expression_parenthesized, NeedsParentheses, OptionalParentheses, Parentheses,
|
||||
};
|
||||
@@ -74,6 +74,14 @@ impl FormatNodeRule<ExprCall> for FormatExprCall {
|
||||
fmt_func.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprCall {
|
||||
|
||||
@@ -2,6 +2,7 @@ use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
|
||||
use ruff_python_ast::AnyNodeRef;
|
||||
use ruff_python_ast::{CmpOp, ExprCompare};
|
||||
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::binary_like::BinaryLike;
|
||||
use crate::expression::has_parentheses;
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
@@ -16,6 +17,16 @@ impl FormatNodeRule<ExprCompare> for FormatExprCompare {
|
||||
fn fmt_fields(&self, item: &ExprCompare, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
BinaryLike::Compare(item).fmt(f)
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
// Node can not have dangling comments
|
||||
debug_assert!(dangling_comments.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprCompare {
|
||||
|
||||
@@ -64,6 +64,15 @@ impl FormatNodeRule<ExprDict> for FormatExprDict {
|
||||
.with_dangling_comments(open_parenthesis_comments)
|
||||
.fmt(f)
|
||||
}
|
||||
|
||||
fn fmt_dangling_comments(
|
||||
&self,
|
||||
_dangling_comments: &[SourceComment],
|
||||
_f: &mut PyFormatter,
|
||||
) -> FormatResult<()> {
|
||||
// Handled by `fmt_fields`
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprDict {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user