Compare commits

...

7 Commits

Author SHA1 Message Date
Charlie Marsh
19e9eb1af8 Bump version to 0.0.177 2022-12-11 22:38:52 -05:00
Anders Kaseorg
e57044800c Fix quotes in SIM118 error message (#1204) 2022-12-11 22:30:39 -05:00
Charlie Marsh
ae8ff7cb7f Add notes around python-lsp-ruff (#1202) 2022-12-11 17:36:20 -05:00
Charlie Marsh
c05914f222 Avoid inserting extra newlines for comment-delimited import blocks (#1201) 2022-12-11 17:13:09 -05:00
Charlie Marsh
24179655b8 Fix 'a test' reference 2022-12-11 13:31:14 -05:00
Charlie Marsh
d27b419e68 Run cargo dev generate-options 2022-12-11 10:34:52 -05:00
Charlie Marsh
9fc7a32a24 Sort list in README 2022-12-11 10:25:27 -05:00
20 changed files with 229 additions and 96 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.176
rev: v0.0.177
hooks:
- id: ruff

8
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.176-dev.0"
version = "0.0.177-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.176"
version = "0.0.177"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1874,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.176"
version = "0.0.177"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.176"
version = "0.0.177"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.176"
version = "0.0.177"
edition = "2021"
rust-version = "1.65.0"
@@ -41,7 +41,7 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.176", path = "ruff_macros" }
ruff_macros = { version = "0.0.177", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "2edd0d264c50c7807bcff03a52a6509e8b7f187f" }

View File

@@ -155,7 +155,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.176
rev: v0.0.177
hooks:
- id: ruff
```
@@ -778,7 +778,7 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| SIM118 | KeyInDict | Use 'key in dict' instead of 'key in dict.keys() | 🛠 |
| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 |
### flake8-tidy-imports (TID)
@@ -852,6 +852,37 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
Download the [Ruff VS Code extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff).
### Language Server Protocol
Ruff is available as a [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/)
server, distributed as the [`python-lsp-ruff`](https://github.com/python-lsp/python-lsp-ruff) plugin
for [`python-lsp-server`](https://github.com/python-lsp/python-lsp-server), both of which are
installable via [PyPI](https://pypi.org/project/python-lsp-ruff/):
```shell
pip install python-lsp-server python-lsp-ruff
```
The LSP server can be used with any editor that supports the Language Server Protocol. For example,
to use it with Neovim, you would add something like the following to your `init.lua`:
```lua
require'lspconfig'.pylsp.setup {
settings = {
pylsp = {
plugins = {
ruff = {
enabled = true
}
}
}
},
}
```
[`ruffd`](https://github.com/Seamooo/ruffd) is another implementation of the Language Server
Protocol (LSP) for Ruff, written in Rust.
### PyCharm
Ruff can be installed as an [External Tool](https://www.jetbrains.com/help/pycharm/configuring-third-party-tools.html)
@@ -864,10 +895,13 @@ Ruff should then appear as a runnable action:
![Ruff as a runnable action](https://user-images.githubusercontent.com/1309177/193156026-732b0aaf-3dd9-4549-9b4d-2de6d2168a33.png)
### Vim & Neovim (Unofficial)
### Vim & Neovim
Ruff is available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright) extension
for coc.nvim.
Ruff can be integrated into any editor that supports the Language Server Protocol (LSP) (see:
[Language Server Protocol](#language-server-protocol)).
Ruff is also available as part of the [coc-pyright](https://github.com/fannheyward/coc-pyright)
extension for `coc.nvim`.
<details>
<summary>Ruff can also be integrated via <a href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#efm"><code>efm</code></a> in just a <a href="https://github.com/JafarAbdi/myconfigs/blob/6f0b6b2450e92ec8fc50422928cd22005b919110/efm-langserver/config.yaml#L14-L20">few lines</a>.</summary>
@@ -923,11 +957,6 @@ null_ls.setup({
</details>
### Language Server Protocol (Unofficial)
[`ruffd`](https://github.com/Seamooo/ruffd) is a Rust-based language server for Ruff that implements
the Language Server Protocol (LSP).
### GitHub Actions
GitHub Actions has everything you need to run Ruff out-of-the-box:
@@ -975,9 +1004,8 @@ Under those conditions, Ruff implements every rule in Flake8.
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
natively, including:
- [`isort`](https://pypi.org/project/isort/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
@@ -995,12 +1023,13 @@ natively, including:
- [`flake8-return`](https://pypi.org/project/flake8-return/)
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`isort`](https://pypi.org/project/isort/)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`yesqa`](https://github.com/asottile/yesqa)
- [`eradicate`](https://pypi.org/project/eradicate/)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (16/33)
- [`yesqa`](https://github.com/asottile/yesqa)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
originating Flake8 plugins. For example, Ruff uses `TID252` to represent the `I252` rule from
@@ -1031,8 +1060,6 @@ Pylint parity is being tracked in [#689](https://github.com/charliermarsh/ruff/i
Today, Ruff can be used to replace Flake8 when used with any of the following plugins:
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
@@ -1051,6 +1078,8 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-super`](https://pypi.org/project/flake8-super/)
- [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) (1/3)
- [`mccabe`](https://pypi.org/project/mccabe/)
- [`pep8-naming`](https://pypi.org/project/pep8-naming/)
- [`pydocstyle`](https://pypi.org/project/pydocstyle/)
Ruff can also replace [`isort`](https://pypi.org/project/isort/),
[`yesqa`](https://github.com/asottile/yesqa), [`eradicate`](https://pypi.org/project/eradicate/),

View File

@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8_to_ruff"
version = "0.0.176"
version = "0.0.177"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.176"
version = "0.0.177"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.176-dev.0"
version = "0.0.177-dev.0"
edition = "2021"
[lib]

View File

@@ -39,3 +39,27 @@ if True:
import collections
import typing
def f(): pass
import os
# Comment goes here.
def f():
pass
import os
# Comment goes here.
def f():
pass
import os
# Comment goes here.
# And another.
def f():
pass

View File

@@ -39,3 +39,27 @@ if True:
import collections
import typing
def f(): pass
import os
# Comment goes here.
def f():
pass
import os
# Comment goes here.
def f():
pass
import os
# Comment goes here.
# And another.
def f():
pass

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_dev"
version = "0.0.176"
version = "0.0.177"
edition = "2021"
[dependencies]

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.176"
version = "0.0.177"
edition = "2021"
[lib]

View File

@@ -37,7 +37,7 @@ pub fn check_imports(
autofix: bool,
path: &Path,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(directives, path);
let mut tracker = ImportTracker::new(locator, directives, path);
for stmt in python_ast {
tracker.visit_stmt(stmt);
}

View File

@@ -2334,7 +2334,7 @@ impl CheckKind {
}
// flake8-simplify
CheckKind::KeyInDict(key, dict) => {
format!("Use '{key} in {dict}' instead of '{key} in {dict}.keys()")
format!("Use `{key} in {dict}` instead of `{key} in {dict}.keys()`")
}
// pyupgrade
CheckKind::TypeOfPrimitive(primitive) => {

55
src/isort/helpers.rs Normal file
View File

@@ -0,0 +1,55 @@
use rustpython_ast::Stmt;
use crate::source_code_locator::SourceCodeLocator;
/// Return `true` if a `Stmt` is preceded by a "comment break"
pub fn has_comment_break(stmt: &Stmt, locator: &SourceCodeLocator) -> bool {
// Starting from the `Stmt` (`def f(): pass`), we want to detect patterns like
// this:
//
// import os
//
// # Detached comment.
//
// def f(): pass
// This should also be detected:
//
// import os
//
// # Detached comment.
//
// # Direct comment.
// def f(): pass
// But this should not:
//
// import os
//
// # Direct comment.
// def f(): pass
let mut seen_blank = false;
for line in locator
.slice_source_code_until(&stmt.location)
.lines()
.rev()
{
let line = line.trim();
if seen_blank {
if line.starts_with('#') {
return true;
} else if !line.is_empty() {
break;
}
} else {
if line.is_empty() {
seen_blank = true;
} else if line.starts_with('#') || line.starts_with('@') {
continue;
} else {
break;
}
}
}
false
}

View File

@@ -18,6 +18,7 @@ use crate::isort::types::{
mod categorize;
mod comments;
pub mod format;
mod helpers;
pub mod plugins;
pub mod settings;
mod sorting;

View File

@@ -49,32 +49,17 @@ expression: checks
column: 0
- kind: UnsortedImports
location:
row: 33
row: 52
column: 0
end_location:
row: 35
row: 54
column: 0
fix:
content: " import collections\n import typing\n\n"
content: "import os\n\n\n"
location:
row: 33
row: 52
column: 0
end_location:
row: 35
column: 0
- kind: UnsortedImports
location:
row: 39
column: 0
end_location:
row: 41
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 39
column: 0
end_location:
row: 41
row: 54
column: 0

View File

@@ -47,19 +47,4 @@ expression: checks
end_location:
row: 16
column: 0
- kind: UnsortedImports
location:
row: 33
column: 0
end_location:
row: 35
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 33
column: 0
end_location:
row: 35
column: 0

View File

@@ -25,7 +25,7 @@ expression: checks
row: 6
column: 13
fix:
content: " import os\n import sys\n\n"
content: " import os\n import sys\n"
location:
row: 5
column: 0

View File

@@ -8,20 +8,24 @@ use rustpython_ast::{
use crate::ast::visitor::Visitor;
use crate::directives::IsortDirectives;
use crate::isort::helpers;
use crate::source_code_locator::SourceCodeLocator;
#[derive(Debug)]
pub enum Trailer {
Sibling,
ClassDef,
FunctionDef,
}
#[derive(Default)]
#[derive(Debug, Default)]
pub struct Block<'a> {
pub imports: Vec<&'a Stmt>,
pub trailer: Option<Trailer>,
}
pub struct ImportTracker<'a> {
locator: &'a SourceCodeLocator<'a>,
directives: &'a IsortDirectives,
pyi: bool,
blocks: Vec<Block<'a>>,
@@ -30,8 +34,13 @@ pub struct ImportTracker<'a> {
}
impl<'a> ImportTracker<'a> {
pub fn new(directives: &'a IsortDirectives, path: &'a Path) -> Self {
pub fn new(
locator: &'a SourceCodeLocator<'a>,
directives: &'a IsortDirectives,
path: &'a Path,
) -> Self {
Self {
locator,
directives,
pyi: path.extension().map_or(false, |ext| ext == "pyi"),
blocks: vec![Block::default()],
@@ -46,31 +55,46 @@ impl<'a> ImportTracker<'a> {
}
fn trailer_for(&self, stmt: &'a Stmt) -> Option<Trailer> {
if self.pyi {
// Black treats interface files differently, limiting to one newline
// (`Trailing::Sibling`), and avoiding inserting any newlines in nested function
// blocks.
if self.nested
&& matches!(
stmt.node,
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
)
{
None
} else {
Some(Trailer::Sibling)
}
} else if self.nested {
Some(Trailer::Sibling)
} else {
Some(match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
})
// No need to compute trailers if we won't be finalizing anything.
let index = self.blocks.len() - 1;
if self.blocks[index].imports.is_empty() {
return None;
}
// Similar to isort, avoid enforcing any newline behaviors in nested blocks.
if self.nested {
return None;
}
Some(if self.pyi {
// Black treats interface files differently, limiting to one newline
// (`Trailing::Sibling`).
Trailer::Sibling
} else {
// If the import block is followed by a class or function, we want to enforce
// two blank lines. The exception: if, between the import and the class or
// function, we have at least one commented line, followed by at
// least one blank line. In that case, we treat it as a regular
// sibling (i.e., as if the comment is the next statement, as
// opposed to the class or function).
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
if helpers::has_comment_break(stmt, self.locator) {
Trailer::Sibling
} else {
Trailer::FunctionDef
}
}
StmtKind::ClassDef { .. } => {
if helpers::has_comment_break(stmt, self.locator) {
Trailer::Sibling
} else {
Trailer::ClassDef
}
}
_ => Trailer::Sibling,
}
})
}
fn finalize(&mut self, trailer: Option<Trailer>) {

View File

@@ -20,7 +20,7 @@ pub struct Options {
`RUF002`, and `RUF003`.
"#,
default = r#"[]"#,
value_type = "Vec<a test>",
value_type = "Vec<char>",
example = r#"
# Allow minus-sign (U+2212), greek-small-letter-rho (U+03C1), and the asterisk-operator (U+2217),
# which could be confused for "-", "p", and "*", respectively.

View File

@@ -31,6 +31,12 @@ impl<'a> SourceCodeLocator<'a> {
Cow::from(rope.slice(offset..))
}
pub fn slice_source_code_until(&self, location: &Location) -> Cow<'_, str> {
let rope = self.get_or_init_rope();
let offset = rope.line_to_char(location.row() - 1) + location.column();
Cow::from(rope.slice(..offset))
}
pub fn slice_source_code_range(&self, range: &Range) -> Cow<'_, str> {
let rope = self.get_or_init_rope();
let start = rope.line_to_char(range.location.row() - 1) + range.location.column();