Compare commits

...

17 Commits

Author SHA1 Message Date
Charlie Marsh
057414ddd4 Bump version to 0.0.199 2022-12-28 20:58:43 -05:00
Charlie Marsh
ca94e9aa26 Warn the user when max iteration count is reached (#1433) 2022-12-28 20:56:43 -05:00
Charlie Marsh
797b5bd261 Split into lint and lint-and-fix methods (#1432) 2022-12-28 20:14:33 -05:00
Charlie Marsh
a64f62f439 Revert setup.py change 2022-12-28 19:34:20 -05:00
Charlie Marsh
058ee8e6bf Add a --diff flag to dry-run autofixes (#1431) 2022-12-28 19:21:29 -05:00
Charlie Marsh
39fc1f0c1b Add a note on autofix settings 2022-12-28 17:26:38 -05:00
Colin Delahunty
34842b4c4b PyUpgrade: Replace pipes with capture_output=True (#1415) 2022-12-28 16:53:35 -05:00
Charlie Marsh
dfa6fa8f83 Check in updated snapshots 2022-12-28 16:42:55 -05:00
Colin Delahunty
6131c819ed Rewrite xml.etree.cElementTree to xml.etree.ElementTree (#1426) 2022-12-28 16:30:36 -05:00
Hannes Käufler
79ba420faa Extract duplicated logic into method (#1428) 2022-12-28 16:10:53 -05:00
Charlie Marsh
d16ba890ae Turn off wasm-pack tests (#1427) 2022-12-28 12:55:25 -05:00
Charlie Marsh
6b6851bf1f Update JSON schema 2022-12-28 12:27:01 -05:00
Charlie Marsh
056718ce75 Remove stray Plugins doc 2022-12-28 12:24:48 -05:00
Charlie Marsh
4521fdf021 Only test --lib for wasm-pack 2022-12-28 10:28:40 -05:00
Maksudul Haque
8e479628f2 Add Support for GitLab CI Code Quality Report Format (#1424) 2022-12-28 10:10:43 -05:00
Charlie Marsh
2a11c4b1f1 Try increasing wasm-bindgen timeout 2022-12-28 07:39:23 -05:00
Anders Kaseorg
a8cde5a936 Check for keyword arguments before the last star argument (#1420) 2022-12-27 23:20:03 -05:00
35 changed files with 920 additions and 154 deletions

View File

@@ -121,31 +121,35 @@ jobs:
- run: cargo test --all
- run: cargo test --package ruff --test black_compatibility_test -- --ignored
wasm-pack-test:
name: "wasm-pack test"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly-2022-11-01
override: true
- uses: actions/cache@v3
env:
cache-name: cache-cargo
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- uses: jetli/wasm-pack-action@v0.4.0
- uses: jetli/wasm-bindgen-action@v0.2.0
- run: wasm-pack test --node
# TODO(charlie): Re-enable the `wasm-pack` tests.
# See: https://github.com/charliermarsh/ruff/issues/1425
# wasm-pack-test:
# name: "wasm-pack test"
# runs-on: ubuntu-latest
# env:
# WASM_BINDGEN_TEST_TIMEOUT: 60
# steps:
# - uses: actions/checkout@v3
# - uses: actions-rs/toolchain@v1
# with:
# profile: minimal
# toolchain: nightly-2022-11-01
# override: true
# - uses: actions/cache@v3
# env:
# cache-name: cache-cargo
# with:
# path: |
# ~/.cargo/registry
# ~/.cargo/git
# key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/Cargo.lock') }}
# restore-keys: |
# ${{ runner.os }}-build-${{ env.cache-name }}-
# ${{ runner.os }}-build-
# ${{ runner.os }}-
# - uses: jetli/wasm-pack-action@v0.4.0
# - uses: jetli/wasm-bindgen-action@v0.2.0
# - run: wasm-pack test --node
maturin-build:
name: "maturin build"

View File

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

9
Cargo.lock generated
View File

@@ -750,7 +750,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.198-dev.0"
version = "0.0.199-dev.0"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1878,7 +1878,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.198"
version = "0.0.199"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1930,6 +1930,7 @@ dependencies = [
"serde-wasm-bindgen",
"serde_json",
"shellexpand",
"similar",
"strum",
"strum_macros",
"test-case",
@@ -1945,7 +1946,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.198"
version = "0.0.199"
dependencies = [
"anyhow",
"clap 4.0.32",
@@ -1966,7 +1967,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.198"
version = "0.0.199"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,9 +6,15 @@ members = [
[package]
name = "ruff"
version = "0.0.198"
version = "0.0.199"
authors = ["Charlie Marsh <charlie.r.marsh@gmail.com>"]
edition = "2021"
rust-version = "1.65.0"
documentation = "https://github.com/charliermarsh/ruff"
homepage = "https://github.com/charliermarsh/ruff"
repository = "https://github.com/charliermarsh/ruff"
readme = "README.md"
license = "MIT"
[lib]
name = "ruff"
@@ -45,7 +51,7 @@ path-absolutize = { version = "3.0.14", features = ["once_cell_cache", "use_unix
quick-junit = { version = "0.3.2" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.198", path = "ruff_macros" }
ruff_macros = { version = "0.0.199", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "68d26955b3e24198a150315e7959719b03709dee" }
@@ -55,6 +61,7 @@ semver = { version = "1.0.16" }
serde = { version = "1.0.147", features = ["derive"] }
serde_json = { version = "1.0.87" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.2.1" }
strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
textwrap = { version = "0.16.0" }

View File

@@ -167,7 +167,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.0.198'
rev: 'v0.0.199'
hooks:
- id: ruff
# Respect `exclude` and `extend-exclude` settings.
@@ -323,6 +323,8 @@ Options:
Attempt to automatically fix lint errors
--fix-only
Fix any fixable lint errors, but don't report on leftover violations. Implies `--fix`
--diff
Avoid writing any fixed files back; instead, output a diff for each changed file to stdout
-n, --no-cache
Disable cache reads
--select <SELECT>
@@ -344,7 +346,7 @@ Options:
--per-file-ignores <PER_FILE_IGNORES>
List of mappings from file pattern to code to exclude
--format <FORMAT>
Output serialization format for error messages [possible values: text, json, junit, grouped, github]
Output serialization format for error messages [possible values: text, json, junit, grouped, github, gitlab]
--show-source
Show violations with source code
--respect-gitignore
@@ -671,6 +673,8 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP019 | TypingTextStrAlias | `typing.Text` is deprecated, use `str` | 🛠 |
| UP020 | OpenAlias | Use builtin `open` | 🛠 |
| UP021 | ReplaceUniversalNewlines | `universal_newlines` is deprecated, use `text` | 🛠 |
| UP022 | ReplaceStdoutStderr | Sending stdout and stderr to pipe is deprecated, use `capture_output` | 🛠 |
| UP023 | RewriteCElementTree | `cElementTree` is deprecated, use `ElementTree` | 🛠 |
### pep8-naming (N)
@@ -1929,8 +1933,9 @@ force-exclude = true
The style in which violation messages should be formatted: `"text"`
(default), `"grouped"` (group messages by file), `"json"`
(machine-readable), `"junit"` (machine-readable XML), or `"github"`
(GitHub Actions annotations).
(machine-readable), `"junit"` (machine-readable XML), `"github"`
(GitHub Actions annotations) or `"gitlab"`
(GitLab CI code quality report).
**Default value**: `"text"`

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
from subprocess import run
import subprocess
output = run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = subprocess.run(["foo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = subprocess.run(stdout=subprocess.PIPE, args=["foo"], stderr=subprocess.PIPE)
output = subprocess.run(
["foo"], stdout=subprocess.PIPE, check=True, stderr=subprocess.PIPE
)
output = subprocess.run(
["foo"], stderr=subprocess.PIPE, check=True, stdout=subprocess.PIPE
)
output = subprocess.run(
["foo"],
stdout=subprocess.PIPE,
check=True,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
close_fds=True,
)
if output:
output = subprocess.run(
["foo"],
stdout=subprocess.PIPE,
check=True,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
)
# Examples that should NOT trigger the rule
from foo import PIPE
subprocess.run(["foo"], stdout=PIPE, stderr=PIPE)
run(["foo"], stdout=None, stderr=PIPE)

View File

@@ -0,0 +1,31 @@
# These two imports have something after cElementTree, so they should be fixed.
from xml.etree.cElementTree import XML, Element, SubElement
import xml.etree.cElementTree as ET
# Weird spacing should not cause issues.
from xml.etree.cElementTree import XML
import xml.etree.cElementTree as ET
# Multi line imports should also work fine.
from xml.etree.cElementTree import (
XML,
Element,
SubElement,
)
if True:
import xml.etree.cElementTree as ET
from xml.etree import cElementTree as CET
from xml.etree import cElementTree as ET
import contextlib, xml.etree.cElementTree as ET
# This should fix the second, but not the first invocation.
import xml.etree.cElementTree, xml.etree.cElementTree as ET
# The below items should NOT be changed.
import xml.etree.cElementTree
from .xml.etree.cElementTree import XML
from xml.etree import cElementTree

View File

@@ -5,9 +5,11 @@ def f(*args, **kwargs):
a = (1, 2)
b = (3, 4)
c = (5, 6)
d = (7, 8)
f(a, b)
f(a, kw=b)
f(*a, kw=b)
f(kw=a, *b)
f(kw=a, *b, *c)
f(*a, kw=b, *c, kw1=d)

View File

@@ -111,7 +111,7 @@
}
},
"flake8-annotations": {
"description": "Plugins Options for the `flake8-annotations` plugin.",
"description": "Options for the `flake8-annotations` plugin.",
"anyOf": [
{
"$ref": "#/definitions/Flake8AnnotationsOptions"
@@ -195,7 +195,7 @@
]
},
"format": {
"description": "The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), or `\"github\"` (GitHub Actions annotations).",
"description": "The style in which violation messages should be formatted: `\"text\"` (default), `\"grouped\"` (group messages by file), `\"json\"` (machine-readable), `\"junit\"` (machine-readable XML), `\"github\"` (GitHub Actions annotations) or `\"gitlab\"` (GitLab CI code quality report).",
"anyOf": [
{
"$ref": "#/definitions/SerializationFormat"
@@ -881,6 +881,8 @@
"UP02",
"UP020",
"UP021",
"UP022",
"UP023",
"W",
"W2",
"W29",
@@ -1264,7 +1266,8 @@
"json",
"junit",
"grouped",
"github"
"github",
"gitlab"
]
},
"Strictness": {

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ use crate::source_code_locator::SourceCodeLocator;
pub enum Mode {
Generate,
Apply,
Diff,
None,
}

View File

@@ -12,7 +12,6 @@ use once_cell::sync::Lazy;
use path_absolutize::Absolutize;
use serde::{Deserialize, Serialize};
use crate::autofix::fixer;
use crate::message::Message;
use crate::settings::{flags, Settings};
@@ -48,7 +47,7 @@ fn content_dir() -> &'static Path {
Path::new("content")
}
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: fixer::Mode) -> u64 {
fn cache_key<P: AsRef<Path>>(path: P, settings: &Settings, autofix: flags::Autofix) -> u64 {
let mut hasher = DefaultHasher::new();
CARGO_PKG_VERSION.hash(&mut hasher);
path.as_ref().absolutize().unwrap().hash(&mut hasher);
@@ -93,13 +92,8 @@ pub fn get<P: AsRef<Path>>(
path: P,
metadata: &Metadata,
settings: &Settings,
autofix: fixer::Mode,
cache: flags::Cache,
autofix: flags::Autofix,
) -> Option<Vec<Message>> {
if matches!(cache, flags::Cache::Disabled) {
return None;
};
let encoded = read_sync(&settings.cache_dir, cache_key(path, settings, autofix)).ok()?;
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
Ok(CheckResult {
@@ -122,14 +116,9 @@ pub fn set<P: AsRef<Path>>(
path: P,
metadata: &Metadata,
settings: &Settings,
autofix: fixer::Mode,
autofix: flags::Autofix,
messages: &[Message],
cache: flags::Cache,
) {
if matches!(cache, flags::Cache::Disabled) {
return;
};
let check_result = CheckResultRef {
metadata: &CacheMetadata {
mtime: FileTime::from_last_modification_time(metadata).unix_seconds(),

View File

@@ -650,6 +650,9 @@ where
));
}
}
if self.settings.enabled.contains(&CheckCode::UP023) {
pyupgrade::plugins::replace_c_element_tree(self, stmt);
}
for alias in names {
if alias.node.name.contains('.') && alias.node.asname.is_none() {
@@ -819,6 +822,9 @@ where
} => {
// Track `import from` statements, to ensure that we can correctly attribute
// references like `from typing import Union`.
if self.settings.enabled.contains(&CheckCode::UP023) {
pyupgrade::plugins::replace_c_element_tree(self, stmt);
}
if level.map(|level| level == 0).unwrap_or(true) {
if let Some(module) = module {
self.from_imports
@@ -1552,9 +1558,6 @@ where
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
}
if self.settings.enabled.contains(&CheckCode::UP019) {
pyupgrade::plugins::typing_text_str_alias(self, expr);
}
if self.settings.enabled.contains(&CheckCode::UP016) {
pyupgrade::plugins::remove_six_compat(self, expr);
}
@@ -1564,7 +1567,9 @@ where
{
pyupgrade::plugins::datetime_utc_alias(self, expr);
}
if self.settings.enabled.contains(&CheckCode::UP019) {
pyupgrade::plugins::typing_text_str_alias(self, expr);
}
if self.settings.enabled.contains(&CheckCode::YTT202) {
flake8_2020::plugins::name_or_attribute(self, expr);
}
@@ -1674,6 +1679,9 @@ where
if self.settings.enabled.contains(&CheckCode::UP021) {
pyupgrade::plugins::replace_universal_newlines(self, expr, keywords);
}
if self.settings.enabled.contains(&CheckCode::UP022) {
pyupgrade::plugins::replace_stdout_stderr(self, expr, keywords);
}
// flake8-print
if self.settings.enabled.contains(&CheckCode::T201)

View File

@@ -229,6 +229,8 @@ pub enum CheckCode {
UP019,
UP020,
UP021,
UP022,
UP023,
// pydocstyle
D100,
D101,
@@ -844,6 +846,8 @@ pub enum CheckKind {
NativeLiterals,
OpenAlias,
ReplaceUniversalNewlines,
ReplaceStdoutStderr,
RewriteCElementTree,
// pydocstyle
BlankLineAfterLastSection(String),
BlankLineAfterSection(String),
@@ -1223,6 +1227,8 @@ impl CheckCode {
CheckCode::UP019 => CheckKind::TypingTextStrAlias,
CheckCode::UP020 => CheckKind::OpenAlias,
CheckCode::UP021 => CheckKind::ReplaceUniversalNewlines,
CheckCode::UP022 => CheckKind::ReplaceStdoutStderr,
CheckCode::UP023 => CheckKind::RewriteCElementTree,
// pydocstyle
CheckCode::D100 => CheckKind::PublicModule,
CheckCode::D101 => CheckKind::PublicClass,
@@ -1647,6 +1653,8 @@ impl CheckCode {
CheckCode::UP019 => CheckCategory::Pyupgrade,
CheckCode::UP020 => CheckCategory::Pyupgrade,
CheckCode::UP021 => CheckCategory::Pyupgrade,
CheckCode::UP022 => CheckCategory::Pyupgrade,
CheckCode::UP023 => CheckCategory::Pyupgrade,
CheckCode::W292 => CheckCategory::Pycodestyle,
CheckCode::W605 => CheckCategory::Pycodestyle,
CheckCode::YTT101 => CheckCategory::Flake82020,
@@ -1862,6 +1870,8 @@ impl CheckKind {
CheckKind::TypingTextStrAlias => &CheckCode::UP019,
CheckKind::OpenAlias => &CheckCode::UP020,
CheckKind::ReplaceUniversalNewlines => &CheckCode::UP021,
CheckKind::ReplaceStdoutStderr => &CheckCode::UP022,
CheckKind::RewriteCElementTree => &CheckCode::UP023,
// pydocstyle
CheckKind::BlankLineAfterLastSection(..) => &CheckCode::D413,
CheckKind::BlankLineAfterSection(..) => &CheckCode::D410,
@@ -2595,6 +2605,12 @@ impl CheckKind {
CheckKind::ReplaceUniversalNewlines => {
"`universal_newlines` is deprecated, use `text`".to_string()
}
CheckKind::ReplaceStdoutStderr => {
"Sending stdout and stderr to pipe is deprecated, use `capture_output`".to_string()
}
CheckKind::RewriteCElementTree => {
"`cElementTree` is deprecated, use `ElementTree`".to_string()
}
CheckKind::ConvertNamedTupleFunctionalToClass(name) => {
format!("Convert `{name}` from `NamedTuple` functional to class syntax")
}
@@ -3040,6 +3056,8 @@ impl CheckKind {
| CheckKind::OpenAlias
| CheckKind::NewLineAfterLastParagraph
| CheckKind::ReplaceUniversalNewlines
| CheckKind::ReplaceStdoutStderr
| CheckKind::RewriteCElementTree
| CheckKind::NewLineAfterSectionName(..)
| CheckKind::NoBlankLineAfterFunction(..)
| CheckKind::NoBlankLineBeforeClass(..)

View File

@@ -532,6 +532,8 @@ pub enum CheckCodePrefix {
UP02,
UP020,
UP021,
UP022,
UP023,
W,
W2,
W29,
@@ -759,6 +761,8 @@ impl CheckCodePrefix {
CheckCode::UP019,
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
CheckCode::D100,
CheckCode::D101,
CheckCode::D102,
@@ -2415,6 +2419,8 @@ impl CheckCodePrefix {
CheckCode::UP019,
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
]
}
CheckCodePrefix::U0 => {
@@ -2445,6 +2451,8 @@ impl CheckCodePrefix {
CheckCode::UP019,
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
]
}
CheckCodePrefix::U00 => {
@@ -2659,6 +2667,8 @@ impl CheckCodePrefix {
CheckCode::UP019,
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
],
CheckCodePrefix::UP0 => vec![
CheckCode::UP001,
@@ -2681,6 +2691,8 @@ impl CheckCodePrefix {
CheckCode::UP019,
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
],
CheckCodePrefix::UP00 => vec![
CheckCode::UP001,
@@ -2722,9 +2734,16 @@ impl CheckCodePrefix {
CheckCodePrefix::UP017 => vec![CheckCode::UP017],
CheckCodePrefix::UP018 => vec![CheckCode::UP018],
CheckCodePrefix::UP019 => vec![CheckCode::UP019],
CheckCodePrefix::UP02 => vec![CheckCode::UP020, CheckCode::UP021],
CheckCodePrefix::UP02 => vec![
CheckCode::UP020,
CheckCode::UP021,
CheckCode::UP022,
CheckCode::UP023,
],
CheckCodePrefix::UP020 => vec![CheckCode::UP020],
CheckCodePrefix::UP021 => vec![CheckCode::UP021],
CheckCodePrefix::UP022 => vec![CheckCode::UP022],
CheckCodePrefix::UP023 => vec![CheckCode::UP023],
CheckCodePrefix::W => vec![CheckCode::W292, CheckCode::W605],
CheckCodePrefix::W2 => vec![CheckCode::W292],
CheckCodePrefix::W29 => vec![CheckCode::W292],
@@ -3288,6 +3307,8 @@ impl CheckCodePrefix {
CheckCodePrefix::UP02 => SuffixLength::Two,
CheckCodePrefix::UP020 => SuffixLength::Three,
CheckCodePrefix::UP021 => SuffixLength::Three,
CheckCodePrefix::UP022 => SuffixLength::Three,
CheckCodePrefix::UP023 => SuffixLength::Three,
CheckCodePrefix::W => SuffixLength::Zero,
CheckCodePrefix::W2 => SuffixLength::One,
CheckCodePrefix::W29 => SuffixLength::Two,

View File

@@ -50,6 +50,10 @@ pub struct Cli {
fix_only: bool,
#[clap(long, overrides_with("fix_only"), hide = true)]
no_fix_only: bool,
/// Avoid writing any fixed files back; instead, output a diff for each
/// changed file to stdout.
#[arg(long)]
pub diff: bool,
/// Disable cache reads.
#[arg(short, long)]
pub no_cache: bool,
@@ -154,6 +158,7 @@ impl Cli {
add_noqa: self.add_noqa,
autoformat: self.autoformat,
config: self.config,
diff: self.diff,
exit_zero: self.exit_zero,
explain: self.explain,
files: self.files,
@@ -213,6 +218,7 @@ pub struct Arguments {
pub add_noqa: bool,
pub autoformat: bool,
pub config: Option<PathBuf>,
pub diff: bool,
pub exit_zero: bool,
pub explain: Option<CheckCode>,
pub files: Vec<PathBuf>,

View File

@@ -332,6 +332,9 @@ pub fn explain(code: &CheckCode, format: &SerializationFormat) -> Result<()> {
SerializationFormat::Github => {
bail!("`--explain` does not support GitHub format")
}
SerializationFormat::Gitlab => {
bail!("`--explain` does not support GitLab format")
}
};
Ok(())
}

View File

@@ -5,8 +5,10 @@ use std::ops::AddAssign;
use std::path::Path;
use anyhow::Result;
use colored::Colorize;
use log::debug;
use rustpython_parser::lexer::LexResult;
use similar::TextDiff;
use crate::ast::types::Range;
use crate::autofix::fixer;
@@ -26,6 +28,9 @@ use crate::source_code_locator::SourceCodeLocator;
use crate::source_code_style::SourceCodeStyleDetector;
use crate::{cache, directives, fs, rustpython_helpers};
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
const CARGO_PKG_REPOSITORY: &str = env!("CARGO_PKG_REPOSITORY");
#[derive(Debug, Default)]
pub struct Diagnostics {
pub messages: Vec<Message>,
@@ -183,26 +188,47 @@ pub fn lint_path(
// Validate the `Settings` and return any errors.
settings.validate()?;
let metadata = path.metadata()?;
// Check the cache.
if let Some(messages) = cache::get(path, &metadata, settings, autofix, cache) {
debug!("Cache hit for: {}", path.to_string_lossy());
return Ok(Diagnostics::new(messages));
}
let metadata = if matches!(cache, flags::Cache::Enabled) {
let metadata = path.metadata()?;
if let Some(messages) = cache::get(path, &metadata, settings, autofix.into()) {
debug!("Cache hit for: {}", path.to_string_lossy());
return Ok(Diagnostics::new(messages));
}
Some(metadata)
} else {
None
};
// Read the file from disk.
let contents = fs::read_file(path)?;
// Lint the file.
let (contents, fixed, messages) = lint(contents, path, package, settings, autofix)?;
let (messages, fixed) = if matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff) {
let (transformed, fixed, messages) = lint_fix(&contents, path, package, settings)?;
if fixed > 0 {
if matches!(autofix, fixer::Mode::Apply) {
write(path, transformed)?;
} else if matches!(autofix, fixer::Mode::Diff) {
let mut stdout = io::stdout().lock();
TextDiff::from_lines(&contents, &transformed)
.unified_diff()
.header(&fs::relativize_path(path), &fs::relativize_path(path))
.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
(messages, fixed)
} else {
let messages = lint_only(&contents, path, package, settings, autofix.into())?;
let fixed = 0;
(messages, fixed)
};
// Re-populate the cache.
cache::set(path, &metadata, settings, autofix, &messages, cache);
// If we applied any fixes, write the contents back to disk.
if fixed > 0 {
write(path, contents)?;
if let Some(metadata) = metadata {
cache::set(path, &metadata, settings, autofix.into(), &messages);
}
Ok(Diagnostics { messages, fixed })
@@ -286,40 +312,121 @@ pub fn autoformat_path(path: &Path, settings: &Settings) -> Result<()> {
pub fn lint_stdin(
path: Option<&Path>,
package: Option<&Path>,
stdin: &str,
contents: &str,
settings: &Settings,
autofix: fixer::Mode,
) -> Result<Diagnostics> {
// Validate the `Settings` and return any errors.
settings.validate()?;
// Read the file from disk.
let contents = stdin.to_string();
// Lint the inputs.
let (messages, fixed) = if matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff) {
let (transformed, fixed, messages) = lint_fix(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
)?;
// Lint the file.
let (contents, fixed, messages) = lint(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
autofix,
)?;
if matches!(autofix, fixer::Mode::Apply) {
// Write the contents to stdout, regardless of whether any errors were fixed.
io::stdout().write_all(transformed.as_bytes())?;
} else if matches!(autofix, fixer::Mode::Diff) {
// But only write a diff if it's non-empty.
if fixed > 0 {
let text_diff = TextDiff::from_lines(contents, &transformed);
let mut unified_diff = text_diff.unified_diff();
if let Some(path) = path {
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
}
// Write the fixed contents to stdout.
if matches!(autofix, fixer::Mode::Apply) {
io::stdout().write_all(contents.as_bytes())?;
}
let mut stdout = io::stdout().lock();
unified_diff.to_writer(&mut stdout)?;
stdout.write_all(b"\n")?;
stdout.flush()?;
}
}
(messages, fixed)
} else {
let messages = lint_only(
contents,
path.unwrap_or_else(|| Path::new("-")),
package,
settings,
autofix.into(),
)?;
let fixed = 0;
(messages, fixed)
};
Ok(Diagnostics { messages, fixed })
}
fn lint(
mut contents: String,
/// Generate a list of `Check` violations (optionally including any autofix
/// patches) from source code content.
fn lint_only(
contents: &str,
path: &Path,
package: Option<&Path>,
settings: &Settings,
autofix: flags::Autofix,
) -> Result<Vec<Message>> {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(contents);
// Map row and column locations to byte slices (lazily).
let locator = SourceCodeLocator::new(contents);
// Detect the current code style (lazily).
let stylist = SourceCodeStyleDetector::from_contents(contents, &locator);
// Extract the `# noqa` and `# isort: skip` directives from the source.
let directives = directives::extract_directives(
&tokens,
&locator,
directives::Flags::from_settings(settings),
);
// Generate checks.
let checks = check_path(
path,
package,
contents,
tokens,
&locator,
&stylist,
&directives,
settings,
autofix,
flags::Noqa::Enabled,
)?;
// Convert from checks to messages.
let path_lossy = path.to_string_lossy();
Ok(checks
.into_iter()
.map(|check| {
let source = if settings.show_source {
Some(Source::from_check(&check, &locator))
} else {
None
};
Message::from_check(check, path_lossy.to_string(), source)
})
.collect())
}
/// Generate a list of `Check` violations from source code content, iteratively
/// autofixing any violations until stable.
fn lint_fix(
contents: &str,
path: &Path,
package: Option<&Path>,
settings: &Settings,
autofix: fixer::Mode,
) -> Result<(String, usize, Vec<Message>)> {
let mut contents = contents.to_string();
// Track the number of fixed errors across iterations.
let mut fixed = 0;
@@ -327,7 +434,7 @@ fn lint(
let mut iterations = 0;
// Continuously autofix until the source code stabilizes.
let messages = loop {
loop {
// Tokenize once.
let tokens: Vec<LexResult> = rustpython_helpers::tokenize(&contents);
@@ -354,13 +461,13 @@ fn lint(
&stylist,
&directives,
settings,
autofix.into(),
flags::Autofix::Enabled,
flags::Noqa::Enabled,
)?;
// Apply autofix.
if matches!(autofix, fixer::Mode::Apply) && iterations < MAX_ITERATIONS {
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
if let Some((fixed_contents, applied)) = fix_file(&checks, &locator) {
if iterations < MAX_ITERATIONS {
// Count the number of fixed errors.
fixed += applied;
@@ -373,11 +480,29 @@ fn lint(
// Re-run the linter pass (by avoiding the break).
continue;
}
eprintln!(
"
{}: Failed to converge after {} iterations.
This likely indicates a bug in `{}`. If you could open an issue at:
{}/issues
quoting the contents of `{}`, along with the `pyproject.toml` settings and executed command, we'd \
be very appreciative!
",
"warning".yellow().bold(),
MAX_ITERATIONS,
CARGO_PKG_NAME,
CARGO_PKG_REPOSITORY,
fs::relativize_path(path),
);
}
// Convert to messages.
let filename = path.to_string_lossy().to_string();
break checks
let path_lossy = path.to_string_lossy();
let messages = checks
.into_iter()
.map(|check| {
let source = if settings.show_source {
@@ -385,12 +510,11 @@ fn lint(
} else {
None
};
Message::from_check(check, filename.clone(), source)
Message::from_check(check, path_lossy.to_string(), source)
})
.collect();
};
Ok((contents, fixed, messages))
return Ok((contents, fixed, messages));
}
}
#[cfg(test)]

View File

@@ -123,19 +123,6 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
(settings.fix, settings.fix_only, settings.format)
}
};
let autofix = if fix || fix_only {
fixer::Mode::Apply
} else if matches!(format, SerializationFormat::Json) {
fixer::Mode::Generate
} else {
fixer::Mode::None
};
let violations = if fix_only {
Violations::Hide
} else {
Violations::Show
};
let cache = !cli.no_cache;
if let Some(code) = cli.explain {
commands::explain(&code, &format)?;
@@ -150,9 +137,35 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
return Ok(ExitCode::SUCCESS);
}
// Autofix rules are as follows:
// - If `--fix` or `--fix-only` is set, always apply fixes to the filesystem (or
// print them to stdout, if we're reading from stdin).
// - Otherwise, if `--format json` is set, generate the fixes (so we print them
// out as part of the JSON payload), but don't write them to disk.
// - If `--diff` or `--fix-only` are set, don't print any violations (only
// fixes).
// TODO(charlie): Consider adding ESLint's `--fix-dry-run`, which would generate
// but not apply fixes. That would allow us to avoid special-casing JSON
// here.
let autofix = if cli.diff {
fixer::Mode::Diff
} else if fix || fix_only {
fixer::Mode::Apply
} else if matches!(format, SerializationFormat::Json) {
fixer::Mode::Generate
} else {
fixer::Mode::None
};
let violations = if cli.diff || fix_only {
Violations::Hide
} else {
Violations::Show
};
let cache = !cli.no_cache;
let printer = Printer::new(&format, &log_level, &autofix, &violations);
if cli.watch {
if matches!(autofix, fixer::Mode::Generate | fixer::Mode::Apply) {
if !matches!(autofix, fixer::Mode::None) {
eprintln!("Warning: --fix is not enabled in watch mode.");
}
if cli.add_noqa {
@@ -251,7 +264,7 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
// Always try to print violations (the printer itself may suppress output),
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
if !(is_stdin && matches!(autofix, fixer::Mode::Apply | fixer::Mode::Diff)) {
printer.write_once(&diagnostics)?;
}
@@ -261,8 +274,14 @@ pub(crate) fn inner_main() -> Result<ExitCode> {
drop(updates::check_for_updates());
}
if !diagnostics.messages.is_empty() && !cli.exit_zero && !fix_only {
return Ok(ExitCode::FAILURE);
if !cli.exit_zero {
if cli.diff || fix_only {
if diagnostics.fixed > 0 {
return Ok(ExitCode::FAILURE);
}
} else if !diagnostics.messages.is_empty() {
return Ok(ExitCode::FAILURE);
}
}
}

View File

@@ -8,6 +8,7 @@ use colored::Colorize;
use itertools::iterate;
use rustpython_parser::ast::Location;
use serde::Serialize;
use serde_json::json;
use crate::autofix::{fixer, Fix};
use crate::checks::CheckCode;
@@ -89,7 +90,11 @@ impl<'a> Printer<'a> {
Violations::Hide => {
let fixed = diagnostics.fixed;
if fixed > 0 {
println!("Fixed {fixed} error(s).");
if matches!(self.autofix, fixer::Mode::Apply) {
println!("Fixed {fixed} error(s).");
} else if matches!(self.autofix, fixer::Mode::Diff) {
println!("Would fix {fixed} error(s).");
}
}
}
}
@@ -134,17 +139,8 @@ impl<'a> Printer<'a> {
SerializationFormat::Junit => {
use quick_junit::{NonSuccessKind, Report, TestCase, TestCaseStatus, TestSuite};
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
let mut report = Report::new("ruff");
for (filename, messages) in grouped_messages {
for (filename, messages) in group_messages_by_filename(&diagnostics.messages) {
let mut test_suite = TestSuite::new(filename);
test_suite
.extra
@@ -183,16 +179,7 @@ impl<'a> Printer<'a> {
self.post_text(diagnostics);
}
SerializationFormat::Grouped => {
// Group by filename.
let mut grouped_messages = BTreeMap::default();
for message in &diagnostics.messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
for (filename, messages) in grouped_messages {
for (filename, messages) in group_messages_by_filename(&diagnostics.messages) {
// Compute the maximum number of digits in the row and column, for messages in
// this file.
let row_length = num_digits(
@@ -239,6 +226,34 @@ impl<'a> Printer<'a> {
);
});
}
SerializationFormat::Gitlab => {
// Generate JSON with errors in GitLab CI format
// https://docs.gitlab.com/ee/ci/testing/code_quality.html#implementing-a-custom-tool
println!(
"{}",
serde_json::to_string_pretty(
&diagnostics
.messages
.iter()
.map(|message| {
json!({
"description": format!("({}) {}", message.kind.code(), message.kind.body()),
"severity": "major",
"fingerprint": message.kind.code(),
"location": {
"path": relativize_path(Path::new(&message.filename)),
"lines": {
"begin": message.location.row(),
"end": message.end_location.row()
}
}
})
}
)
.collect::<Vec<_>>()
)?
);
}
}
Ok(())
@@ -275,6 +290,17 @@ impl<'a> Printer<'a> {
}
}
fn group_messages_by_filename(messages: &Vec<Message>) -> BTreeMap<&String, Vec<&Message>> {
let mut grouped_messages = BTreeMap::default();
for message in messages {
grouped_messages
.entry(&message.filename)
.or_insert_with(Vec::new)
.push(message);
}
grouped_messages
}
fn num_digits(n: usize) -> usize {
iterate(n, |&n| n / 10)
.take_while(|&n| n > 0)

View File

@@ -40,6 +40,8 @@ mod tests {
#[test_case(CheckCode::UP018, Path::new("UP018.py"); "UP018")]
#[test_case(CheckCode::UP019, Path::new("UP019.py"); "UP019")]
#[test_case(CheckCode::UP021, Path::new("UP021.py"); "UP021")]
#[test_case(CheckCode::UP022, Path::new("UP022.py"); "UP022")]
#[test_case(CheckCode::UP023, Path::new("UP023.py"); "UP023")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View File

@@ -6,7 +6,9 @@ pub use native_literals::native_literals;
pub use open_alias::open_alias;
pub use redundant_open_modes::redundant_open_modes;
pub use remove_six_compat::remove_six_compat;
pub use replace_stdout_stderr::replace_stdout_stderr;
pub use replace_universal_newlines::replace_universal_newlines;
pub use rewrite_c_element_tree::replace_c_element_tree;
pub use super_call_with_parameters::super_call_with_parameters;
pub use type_of_primitive::type_of_primitive;
pub use typing_text_str_alias::typing_text_str_alias;
@@ -26,7 +28,9 @@ mod native_literals;
mod open_alias;
mod redundant_open_modes;
mod remove_six_compat;
mod replace_stdout_stderr;
mod replace_universal_newlines;
mod rewrite_c_element_tree;
mod super_call_with_parameters;
mod type_of_primitive;
mod typing_text_str_alias;

View File

@@ -0,0 +1,112 @@
use rustpython_ast::{Expr, Keyword};
use crate::ast::helpers::{find_keyword, match_module_member};
use crate::ast::types::Range;
use crate::ast::whitespace::indentation;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
#[derive(Debug)]
struct MiddleContent<'a> {
contents: &'a str,
multi_line: bool,
}
/// Return the number of "dirty" characters.
fn dirty_count(iter: impl Iterator<Item = char>) -> usize {
let mut the_count = 0;
for current_char in iter {
if current_char == ' ' || current_char == ',' || current_char == '\n' {
the_count += 1;
} else {
break;
}
}
the_count
}
/// Extract the `Middle` content between two arguments.
fn extract_middle(contents: &str) -> Option<MiddleContent> {
let multi_line = contents.contains('\n');
let start_gap = dirty_count(contents.chars());
if contents.len() == start_gap {
return None;
}
let end_gap = dirty_count(contents.chars().rev());
Some(MiddleContent {
contents: &contents[start_gap..contents.len() - end_gap],
multi_line,
})
}
/// UP022
pub fn replace_stdout_stderr(checker: &mut Checker, expr: &Expr, kwargs: &[Keyword]) {
if match_module_member(
expr,
"subprocess",
"run",
&checker.from_imports,
&checker.import_aliases,
) {
// Find `stdout` and `stderr` kwargs.
let Some(stdout) = find_keyword(kwargs, "stdout") else {
return;
};
let Some(stderr) = find_keyword(kwargs, "stderr") else {
return;
};
// Verify that they're both set to `subprocess.PIPE`.
if !match_module_member(
&stdout.node.value,
"subprocess",
"PIPE",
&checker.from_imports,
&checker.import_aliases,
) || !match_module_member(
&stderr.node.value,
"subprocess",
"PIPE",
&checker.from_imports,
&checker.import_aliases,
) {
return;
}
let mut check = Check::new(CheckKind::ReplaceStdoutStderr, Range::from_located(expr));
if checker.patch(check.kind.code()) {
let first = if stdout.location < stderr.location {
stdout
} else {
stderr
};
let last = if stdout.location > stderr.location {
stdout
} else {
stderr
};
let mut contents = String::from("capture_output=True");
if let Some(middle) = extract_middle(&checker.locator.slice_source_code_range(&Range {
location: first.end_location.unwrap(),
end_location: last.location,
})) {
if middle.multi_line {
contents.push(',');
contents.push('\n');
contents.push_str(&indentation(checker, first));
} else {
contents.push(',');
contents.push(' ');
}
contents.push_str(middle.contents);
}
check.amend(Fix::replacement(
contents,
first.location,
last.end_location.unwrap(),
));
}
checker.add_check(check);
}
}

View File

@@ -0,0 +1,57 @@
use rustpython_ast::{Located, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checkers::ast::Checker;
use crate::checks::{Check, CheckKind};
fn add_check_for_node<T>(checker: &mut Checker, node: &Located<T>) {
let mut check = Check::new(CheckKind::RewriteCElementTree, Range::from_located(node));
if checker.patch(check.kind.code()) {
let contents = checker
.locator
.slice_source_code_range(&Range::from_located(node));
check.amend(Fix::replacement(
contents.replacen("cElementTree", "ElementTree", 1),
node.location,
node.end_location.unwrap(),
));
}
checker.add_check(check);
}
/// UP023
pub fn replace_c_element_tree(checker: &mut Checker, stmt: &Stmt) {
match &stmt.node {
StmtKind::Import { names } => {
// Ex) `import xml.etree.cElementTree as ET`
for name in names {
if name.node.name == "xml.etree.cElementTree" && name.node.asname.is_some() {
add_check_for_node(checker, name);
}
}
}
StmtKind::ImportFrom {
module,
names,
level,
} => {
if level.map_or(false, |level| level > 0) {
// Ex) `import .xml.etree.cElementTree as ET`
} else if let Some(module) = module {
if module == "xml.etree.cElementTree" {
// Ex) `from xml.etree.cElementTree import XML`
add_check_for_node(checker, stmt);
} else if module == "xml.etree" {
// Ex) `from xml.etree import cElementTree as ET`
for name in names {
if name.node.name == "cElementTree" && name.node.asname.is_some() {
add_check_for_node(checker, name);
}
}
}
}
}
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
}
}

View File

@@ -0,0 +1,110 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: ReplaceStdoutStderr
location:
row: 4
column: 9
end_location:
row: 4
column: 69
fix:
content: capture_output=True
location:
row: 4
column: 22
end_location:
row: 4
column: 68
- kind: ReplaceStdoutStderr
location:
row: 6
column: 9
end_location:
row: 6
column: 80
fix:
content: capture_output=True
location:
row: 6
column: 33
end_location:
row: 6
column: 79
- kind: ReplaceStdoutStderr
location:
row: 8
column: 9
end_location:
row: 8
column: 86
fix:
content: "capture_output=True, args=[\"foo\"]"
location:
row: 8
column: 24
end_location:
row: 8
column: 85
- kind: ReplaceStdoutStderr
location:
row: 10
column: 9
end_location:
row: 12
column: 1
fix:
content: "capture_output=True, check=True"
location:
row: 11
column: 13
end_location:
row: 11
column: 71
- kind: ReplaceStdoutStderr
location:
row: 14
column: 9
end_location:
row: 16
column: 1
fix:
content: "capture_output=True, check=True"
location:
row: 15
column: 13
end_location:
row: 15
column: 71
- kind: ReplaceStdoutStderr
location:
row: 18
column: 9
end_location:
row: 26
column: 1
fix:
content: "capture_output=True,\n check=True"
location:
row: 20
column: 4
end_location:
row: 22
column: 26
- kind: ReplaceStdoutStderr
location:
row: 29
column: 13
end_location:
row: 36
column: 5
fix:
content: "capture_output=True,\n check=True"
location:
row: 31
column: 8
end_location:
row: 33
column: 30

View File

@@ -0,0 +1,155 @@
---
source: src/pyupgrade/mod.rs
expression: checks
---
- kind: RewriteCElementTree
location:
row: 2
column: 0
end_location:
row: 2
column: 59
fix:
content: "from xml.etree.ElementTree import XML, Element, SubElement"
location:
row: 2
column: 0
end_location:
row: 2
column: 59
- kind: RewriteCElementTree
location:
row: 3
column: 7
end_location:
row: 3
column: 35
fix:
content: xml.etree.ElementTree as ET
location:
row: 3
column: 7
end_location:
row: 3
column: 35
- kind: RewriteCElementTree
location:
row: 6
column: 0
end_location:
row: 6
column: 44
fix:
content: from xml.etree.ElementTree import XML
location:
row: 6
column: 0
end_location:
row: 6
column: 44
- kind: RewriteCElementTree
location:
row: 7
column: 10
end_location:
row: 7
column: 49
fix:
content: xml.etree.ElementTree as ET
location:
row: 7
column: 10
end_location:
row: 7
column: 49
- kind: RewriteCElementTree
location:
row: 10
column: 0
end_location:
row: 14
column: 1
fix:
content: "from xml.etree.ElementTree import (\n XML,\n Element,\n SubElement,\n)"
location:
row: 10
column: 0
end_location:
row: 14
column: 1
- kind: RewriteCElementTree
location:
row: 16
column: 11
end_location:
row: 16
column: 39
fix:
content: xml.etree.ElementTree as ET
location:
row: 16
column: 11
end_location:
row: 16
column: 39
- kind: RewriteCElementTree
location:
row: 17
column: 26
end_location:
row: 17
column: 45
fix:
content: ElementTree as CET
location:
row: 17
column: 26
end_location:
row: 17
column: 45
- kind: RewriteCElementTree
location:
row: 19
column: 22
end_location:
row: 19
column: 40
fix:
content: ElementTree as ET
location:
row: 19
column: 22
end_location:
row: 19
column: 40
- kind: RewriteCElementTree
location:
row: 21
column: 19
end_location:
row: 21
column: 47
fix:
content: xml.etree.ElementTree as ET
location:
row: 21
column: 19
end_location:
row: 21
column: 47
- kind: RewriteCElementTree
location:
row: 24
column: 31
end_location:
row: 24
column: 59
fix:
content: xml.etree.ElementTree as ET
location:
row: 24
column: 31
end_location:
row: 24
column: 59

View File

@@ -1686,7 +1686,7 @@ pub fn keyword_argument_before_star_argument(args: &[Expr], keywords: &[Keyword]
let mut checks = vec![];
if let Some(arg) = args
.iter()
.find(|arg| matches!(arg.node, ExprKind::Starred { .. }))
.rfind(|arg| matches!(arg.node, ExprKind::Starred { .. }))
{
for keyword in keywords {
if keyword.location < arg.location {

View File

@@ -5,19 +5,28 @@ expression: checks
- kind:
KeywordArgumentBeforeStarArgument: kw
location:
row: 12
row: 13
column: 2
end_location:
row: 12
row: 13
column: 6
fix: ~
- kind:
KeywordArgumentBeforeStarArgument: kw
location:
row: 13
row: 14
column: 2
end_location:
row: 13
row: 14
column: 6
fix: ~
- kind:
KeywordArgumentBeforeStarArgument: kw
location:
row: 15
column: 6
end_location:
row: 15
column: 10
fix: ~

View File

@@ -1,7 +1,7 @@
/// Simple flags used to drive program behavior.
use crate::autofix::fixer;
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Hash)]
pub enum Autofix {
Enabled,
Disabled,
@@ -20,13 +20,13 @@ impl From<bool> for Autofix {
impl From<fixer::Mode> for Autofix {
fn from(value: fixer::Mode) -> Self {
match value {
fixer::Mode::Generate | fixer::Mode::Apply => Autofix::Enabled,
fixer::Mode::Generate | fixer::Mode::Diff | fixer::Mode::Apply => Autofix::Enabled,
fixer::Mode::None => Autofix::Disabled,
}
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Hash)]
pub enum Noqa {
Enabled,
Disabled,
@@ -42,7 +42,7 @@ impl From<bool> for Noqa {
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, Hash)]
pub enum Cache {
Enabled,
Disabled,

View File

@@ -138,7 +138,7 @@ impl Settings {
}]
.into_iter(),
),
format: config.format.unwrap_or(SerializationFormat::Text),
format: config.format.unwrap_or_default(),
force_exclude: config.force_exclude.unwrap_or(false),
ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(),
line_length: config.line_length.unwrap_or(88),

View File

@@ -156,8 +156,9 @@ pub struct Options {
)]
/// The style in which violation messages should be formatted: `"text"`
/// (default), `"grouped"` (group messages by file), `"json"`
/// (machine-readable), `"junit"` (machine-readable XML), or `"github"`
/// (GitHub Actions annotations).
/// (machine-readable), `"junit"` (machine-readable XML), `"github"`
/// (GitHub Actions annotations) or `"gitlab"`
/// (GitLab CI code quality report).
pub format: Option<SerializationFormat>,
#[option(
default = r#"false"#,
@@ -338,7 +339,6 @@ pub struct Options {
/// This setting will override even the `RUFF_CACHE_DIR` environment
/// variable, if set.
pub cache_dir: Option<String>,
/// Plugins
#[option_group]
/// Options for the `flake8-annotations` plugin.
pub flake8_annotations: Option<flake8_annotations::settings::Options>,

View File

@@ -154,6 +154,7 @@ pub enum SerializationFormat {
Junit,
Grouped,
Github,
Gitlab,
}
impl Default for SerializationFormat {
@@ -163,6 +164,12 @@ impl Default for SerializationFormat {
return Self::Github;
}
}
if let Ok(gitlab_ci) = env::var("GITLAB_CI") {
if gitlab_ci == "true" {
return Self::Gitlab;
}
}
Self::Text
}
}