Compare commits
2 Commits
v0.0.291
...
string-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d2513e5a9 | ||
|
|
272306bf5a |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -208,9 +208,3 @@ cython_debug/
|
||||
# VIM
|
||||
.*.sw?
|
||||
.sw?
|
||||
|
||||
# Custom re-inclusions for the resolver test cases
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/_watchdog_fsevents.cpython-311-darwin.so
|
||||
!crates/ruff_python_resolver/resources/test/airflow/venv/lib/python3.11/site-packages/orjson/orjson.cpython-311-darwin.so
|
||||
|
||||
41
Cargo.lock
generated
41
Cargo.lock
generated
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1035,9 +1035,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indicatif"
|
||||
version = "0.17.7"
|
||||
version = "0.17.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25"
|
||||
checksum = "0b297dc40733f23a0e52728a58fa9489a5b7638a324932de16b41adc3ef80730"
|
||||
dependencies = [
|
||||
"console",
|
||||
"instant",
|
||||
@@ -1075,9 +1075,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.32.0"
|
||||
version = "1.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
|
||||
checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
@@ -1484,6 +1484,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"
|
||||
@@ -1888,9 +1898,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
@@ -1898,12 +1908,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2051,7 +2063,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2187,7 +2199,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2336,7 +2348,6 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
@@ -2524,9 +2535,7 @@ dependencies = [
|
||||
"ruff_formatter",
|
||||
"ruff_linter",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2807,9 +2816,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.1"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
|
||||
@@ -21,7 +21,7 @@ filetime = { version = "0.2.20" }
|
||||
glob = { version = "0.3.1" }
|
||||
globset = { version = "0.4.10" }
|
||||
ignore = { version = "0.4.20" }
|
||||
insta = { version = "1.32.0", feature = ["filters", "glob"] }
|
||||
insta = { version = "1.31.0", feature = ["filters", "glob"] }
|
||||
is-macro = { version = "0.3.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
log = { version = "0.4.17" }
|
||||
@@ -39,7 +39,7 @@ serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = { version = "1.0.107" }
|
||||
shellexpand = { version = "3.0.0" }
|
||||
similar = { version = "2.2.1", features = ["inline"] }
|
||||
smallvec = { version = "1.11.1" }
|
||||
smallvec = { version = "1.10.0" }
|
||||
static_assertions = "1.1.0"
|
||||
strum = { version = "0.25.0", features = ["strum_macros"] }
|
||||
strum_macros = { version = "0.25.2" }
|
||||
|
||||
@@ -30,7 +30,7 @@ An extremely fast Python linter, written in Rust.
|
||||
- 🤝 Python 3.11 compatibility
|
||||
- 📦 Built-in caching, to avoid re-analyzing unchanged files
|
||||
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
|
||||
- 📏 Over [700 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- 📏 Over [600 built-in rules](https://docs.astral.sh/ruff/rules/)
|
||||
- ⚖️ [Near-parity](https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-flake8) with the
|
||||
built-in Flake8 rule set
|
||||
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -233,7 +233,7 @@ linting command.
|
||||
|
||||
<!-- Begin section: Rules -->
|
||||
|
||||
**Ruff supports over 700 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
**Ruff supports over 600 lint rules**, many of which are inspired by popular tools like Flake8,
|
||||
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
|
||||
Rust as a first-party feature.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
@@ -48,7 +48,7 @@ itoa = { version = "1.0.6" }
|
||||
log = { workspace = true }
|
||||
notify = { version = "6.1.1" }
|
||||
path-absolutize = { workspace = true, features = ["once_cell_cache"] }
|
||||
rayon = { version = "1.8.0" }
|
||||
rayon = { version = "1.7.0" }
|
||||
regex = { workspace = true }
|
||||
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
|
||||
rustc-hash = { workspace = true }
|
||||
|
||||
3
crates/ruff_cli/resources/test/fixtures/cache_mutable/source.py
vendored
Normal file
3
crates/ruff_cli/resources/test/fixtures/cache_mutable/source.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
a = 1
|
||||
|
||||
__all__ = list(["a", "b"])
|
||||
@@ -1,13 +1,12 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
#[allow(clippy::print_stdout)]
|
||||
pub(crate) fn config(key: Option<&str>) -> Result<()> {
|
||||
match key {
|
||||
None => print!("{}", Options::metadata()),
|
||||
Some(key) => match Options::metadata().find(key) {
|
||||
Some(key) => match Options::metadata().get(key) {
|
||||
None => {
|
||||
return Err(anyhow!("Unknown option: {key}"));
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ use ruff_linter::fs;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module, FormatModuleError};
|
||||
use ruff_python_formatter::{format_module, FormatModuleError, PyFormatOptions};
|
||||
use ruff_source_file::{find_newline, LineEnding};
|
||||
use ruff_workspace::resolver::python_files_in_path;
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::panic::{catch_unwind, PanicError};
|
||||
@@ -73,17 +73,15 @@ pub(crate) fn format(
|
||||
};
|
||||
|
||||
let resolved_settings = resolver.resolve(path, &pyproject_config);
|
||||
let options = resolved_settings.formatter.to_format_options(source_type);
|
||||
debug!("Formatting {} with {:?}", path.display(), options);
|
||||
|
||||
Some(
|
||||
match catch_unwind(|| {
|
||||
format_path(path, &resolved_settings.formatter, source_type, mode)
|
||||
}) {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
||||
}
|
||||
},
|
||||
)
|
||||
Some(match catch_unwind(|| format_path(path, options, mode)) {
|
||||
Ok(inner) => inner,
|
||||
Err(error) => {
|
||||
Err(FormatCommandError::Panic(Some(path.to_path_buf()), error))
|
||||
}
|
||||
})
|
||||
}
|
||||
Err(err) => Some(Err(FormatCommandError::Ignore(err))),
|
||||
}
|
||||
@@ -141,15 +139,19 @@ pub(crate) fn format(
|
||||
#[tracing::instrument(skip_all, fields(path = %path.display()))]
|
||||
fn format_path(
|
||||
path: &Path,
|
||||
settings: &FormatterSettings,
|
||||
source_type: PySourceType,
|
||||
options: PyFormatOptions,
|
||||
mode: FormatMode,
|
||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
||||
let unformatted = std::fs::read_to_string(path)
|
||||
.map_err(|err| FormatCommandError::Read(Some(path.to_path_buf()), err))?;
|
||||
|
||||
let options = settings.to_format_options(source_type, &unformatted);
|
||||
debug!("Formatting {} with {:?}", path.display(), options);
|
||||
let line_ending = match find_newline(&unformatted) {
|
||||
Some((_, LineEnding::Lf)) | None => ruff_formatter::printer::LineEnding::LineFeed,
|
||||
Some((_, LineEnding::Cr)) => ruff_formatter::printer::LineEnding::CarriageReturn,
|
||||
Some((_, LineEnding::CrLf)) => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
||||
};
|
||||
|
||||
let options = options.with_line_ending(line_ending);
|
||||
|
||||
let formatted = format_module(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(Some(path.to_path_buf()), err))?;
|
||||
|
||||
@@ -5,9 +5,8 @@ use anyhow::Result;
|
||||
use log::warn;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_formatter::format_module;
|
||||
use ruff_python_formatter::{format_module, PyFormatOptions};
|
||||
use ruff_workspace::resolver::python_file_at_path;
|
||||
use ruff_workspace::FormatterSettings;
|
||||
|
||||
use crate::args::{CliOverrides, FormatArguments};
|
||||
use crate::commands::format::{FormatCommandError, FormatCommandResult, FormatMode};
|
||||
@@ -38,7 +37,12 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
// Format the file.
|
||||
let path = cli.stdin_filename.as_deref();
|
||||
|
||||
match format_source(path, &pyproject_config.settings.formatter, mode) {
|
||||
let options = pyproject_config
|
||||
.settings
|
||||
.formatter
|
||||
.to_format_options(path.map(PySourceType::from).unwrap_or_default());
|
||||
|
||||
match format_source(path, options, mode) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check => {
|
||||
@@ -59,30 +63,23 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
|
||||
/// Format source code read from `stdin`.
|
||||
fn format_source(
|
||||
path: Option<&Path>,
|
||||
settings: &FormatterSettings,
|
||||
options: PyFormatOptions,
|
||||
mode: FormatMode,
|
||||
) -> Result<FormatCommandResult, FormatCommandError> {
|
||||
let unformatted = read_from_stdin()
|
||||
.map_err(|err| FormatCommandError::Read(path.map(Path::to_path_buf), err))?;
|
||||
|
||||
let options = settings.to_format_options(
|
||||
path.map(PySourceType::from).unwrap_or_default(),
|
||||
&unformatted,
|
||||
);
|
||||
|
||||
let formatted = format_module(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
|
||||
let formatted = formatted.as_code();
|
||||
|
||||
if mode.is_write() {
|
||||
stdout()
|
||||
.lock()
|
||||
.write_all(formatted.as_bytes())
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
if formatted.len() == unformatted.len() && formatted == unformatted {
|
||||
Ok(FormatCommandResult::Unchanged)
|
||||
} else {
|
||||
if mode.is_write() {
|
||||
stdout()
|
||||
.lock()
|
||||
.write_all(formatted.as_bytes())
|
||||
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
Ok(FormatCommandResult::Formatted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::str;
|
||||
|
||||
use anyhow::Result;
|
||||
use insta_cmd::{assert_cmd_snapshot, get_cargo_bin};
|
||||
use tempfile::TempDir;
|
||||
|
||||
const BIN_NAME: &str = "ruff";
|
||||
|
||||
#[test]
|
||||
fn default_options() {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--isolated"])
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print('Should\'t change quotes')
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print('Hy "Micha"') # Should not change quotes
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Should't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Hy "Micha"') # Should not change quotes
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
quote-style = "single"
|
||||
skip-magic-trailing-comma = true
|
||||
line-ending = "cr-lf"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print("Should change quotes")
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(arg1, arg2):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_option_inheritance() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
let base_toml = tempdir.path().join("base.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend = "base.toml"
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
fs::write(
|
||||
base_toml,
|
||||
r#"
|
||||
[format]
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
def foo(arg1, arg2,):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
|
||||
print("Should change quotes")
|
||||
|
||||
"#), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def foo(
|
||||
arg1,
|
||||
arg2,
|
||||
):
|
||||
print("Shouldn't change quotes")
|
||||
|
||||
|
||||
if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: `ruff format` is a work-in-progress, subject to change at any time, and intended only for experimentation.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that the legacy `format` option continues to work but emits a warning.
|
||||
#[test]
|
||||
fn legacy_format_option() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
format = "json"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["check", "--select", "F401", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg("-")
|
||||
.pass_stdin(r#"
|
||||
import os
|
||||
"#), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
[
|
||||
{
|
||||
"code": "F401",
|
||||
"end_location": {
|
||||
"column": 10,
|
||||
"row": 2
|
||||
},
|
||||
"filename": "-",
|
||||
"fix": {
|
||||
"applicability": "Automatic",
|
||||
"edits": [
|
||||
{
|
||||
"content": "",
|
||||
"end_location": {
|
||||
"column": 1,
|
||||
"row": 3
|
||||
},
|
||||
"location": {
|
||||
"column": 1,
|
||||
"row": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": "Remove unused import: `os`"
|
||||
},
|
||||
"location": {
|
||||
"column": 8,
|
||||
"row": 2
|
||||
},
|
||||
"message": "`os` imported but unused",
|
||||
"noqa_row": 2,
|
||||
"url": "https://docs.astral.sh/ruff/rules/unused-import"
|
||||
}
|
||||
]
|
||||
----- stderr -----
|
||||
warning: The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -28,12 +28,12 @@ ruff_workspace = { path = "../ruff_workspace", features = ["schemars"]}
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
ignore = { workspace = true }
|
||||
indicatif = "0.17.7"
|
||||
indicatif = "0.17.5"
|
||||
itertools = { workspace = true }
|
||||
libcst = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
pretty_assertions = { version = "1.3.0" }
|
||||
rayon = "1.8.0"
|
||||
rayon = "1.7.0"
|
||||
regex = { workspace = true }
|
||||
schemars = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -549,6 +549,7 @@ fn format_dir_entry(
|
||||
|
||||
let settings = resolver.resolve(&path, pyproject_config);
|
||||
// That's a bad way of doing this but it's not worth doing something better for format_dev
|
||||
// TODO(micha) use formatter settings instead
|
||||
if settings.formatter.line_width != LineWidth::default() {
|
||||
options = options.with_line_width(settings.formatter.line_width);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ use strum::IntoEnumIterator;
|
||||
use ruff_diagnostics::AutofixKind;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
use crate::ROOT_DIR;
|
||||
|
||||
@@ -97,7 +96,10 @@ fn process_documentation(documentation: &str, out: &mut String) {
|
||||
if let Some(rest) = line.strip_prefix("- `") {
|
||||
let option = rest.trim_end().trim_end_matches('`');
|
||||
|
||||
assert!(Options::metadata().has(option), "unknown option {option}");
|
||||
assert!(
|
||||
Options::metadata().get(option).is_some(),
|
||||
"unknown option {option}"
|
||||
);
|
||||
|
||||
let anchor = option.replace('.', "-");
|
||||
out.push_str(&format!("- [`{option}`][{option}]\n"));
|
||||
|
||||
@@ -1,74 +1,9 @@
|
||||
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
|
||||
//!
|
||||
//! Used for <https://docs.astral.sh/ruff/settings/>.
|
||||
use std::fmt::Write;
|
||||
|
||||
use itertools::Itertools;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::{OptionField, OptionSet, OptionsMetadata, Visit};
|
||||
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output = String::new();
|
||||
generate_set(&mut output, &Set::Toplevel(Options::metadata()));
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
fn generate_set(output: &mut String, set: &Set) {
|
||||
writeln!(output, "### {title}\n", title = set.title()).unwrap();
|
||||
|
||||
if let Some(documentation) = set.metadata().documentation() {
|
||||
output.push_str(documentation);
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
let mut visitor = CollectOptionsVisitor::default();
|
||||
set.metadata().record(&mut visitor);
|
||||
|
||||
let (mut fields, mut sets) = (visitor.fields, visitor.groups);
|
||||
|
||||
fields.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
sets.sort_unstable_by(|(name, _), (name2, _)| name.cmp(name2));
|
||||
|
||||
// Generate the fields.
|
||||
for (name, field) in &fields {
|
||||
emit_field(output, name, field, set.name());
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
// Generate all the sub-sets.
|
||||
for (set_name, sub_set) in &sets {
|
||||
generate_set(output, &Set::Named(set_name, *sub_set));
|
||||
}
|
||||
}
|
||||
|
||||
enum Set<'a> {
|
||||
Toplevel(OptionSet),
|
||||
Named(&'a str, OptionSet),
|
||||
}
|
||||
|
||||
impl<'a> Set<'a> {
|
||||
fn name(&self) -> Option<&'a str> {
|
||||
match self {
|
||||
Set::Toplevel(_) => None,
|
||||
Set::Named(name, _) => Some(name),
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&self) -> &'a str {
|
||||
match self {
|
||||
Set::Toplevel(_) => "Top-level",
|
||||
Set::Named(name, _) => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn metadata(&self) -> &OptionSet {
|
||||
match self {
|
||||
Set::Toplevel(set) => set,
|
||||
Set::Named(_, set) => set,
|
||||
}
|
||||
}
|
||||
}
|
||||
use ruff_workspace::options_base::{OptionEntry, OptionField};
|
||||
|
||||
fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name: Option<&str>) {
|
||||
// if there's a group name, we need to add it to the anchor
|
||||
@@ -102,18 +37,38 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, group_name:
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CollectOptionsVisitor {
|
||||
groups: Vec<(String, OptionSet)>,
|
||||
fields: Vec<(String, OptionField)>,
|
||||
}
|
||||
pub(crate) fn generate() -> String {
|
||||
let mut output: String = "### Top-level\n\n".into();
|
||||
|
||||
impl Visit for CollectOptionsVisitor {
|
||||
fn record_set(&mut self, name: &str, group: OptionSet) {
|
||||
self.groups.push((name.to_owned(), group));
|
||||
let sorted_options: Vec<_> = Options::metadata()
|
||||
.into_iter()
|
||||
.sorted_by_key(|(name, _)| *name)
|
||||
.collect();
|
||||
|
||||
// Generate all the top-level fields.
|
||||
for (name, entry) in &sorted_options {
|
||||
let OptionEntry::Field(field) = entry else {
|
||||
continue;
|
||||
};
|
||||
emit_field(&mut output, name, field, None);
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
|
||||
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||
self.fields.push((name.to_owned(), field));
|
||||
// Generate all the sub-groups.
|
||||
for (group_name, entry) in &sorted_options {
|
||||
let OptionEntry::Group(fields) = entry else {
|
||||
continue;
|
||||
};
|
||||
output.push_str(&format!("### {group_name}\n"));
|
||||
output.push('\n');
|
||||
for (name, entry) in fields.iter().sorted_by_key(|(name, _)| name) {
|
||||
let OptionEntry::Field(field) = entry else {
|
||||
continue;
|
||||
};
|
||||
emit_field(&mut output, name, field, Some(group_name));
|
||||
output.push_str("---\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use ruff_diagnostics::AutofixKind;
|
||||
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_linter::upstream_categories::UpstreamCategoryAndPrefix;
|
||||
use ruff_workspace::options::Options;
|
||||
use ruff_workspace::options_base::OptionsMetadata;
|
||||
|
||||
const FIX_SYMBOL: &str = "🛠️";
|
||||
const PREVIEW_SYMBOL: &str = "🧪";
|
||||
@@ -105,7 +104,10 @@ pub(crate) fn generate() -> String {
|
||||
table_out.push('\n');
|
||||
}
|
||||
|
||||
if Options::metadata().has(linter.name()) {
|
||||
if Options::metadata()
|
||||
.iter()
|
||||
.any(|(name, _)| name == &linter.name())
|
||||
{
|
||||
table_out.push_str(&format!(
|
||||
"For related settings, see [{}](settings.md#{}).",
|
||||
linter.name(),
|
||||
|
||||
@@ -55,11 +55,7 @@ use ruff_macros::CacheKey;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, CacheKey)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(rename_all = "kebab-case")
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[derive(Default)]
|
||||
pub enum IndentStyle {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{FormatOptions, IndentStyle, IndentWidth, LineWidth};
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
/// Options that affect how the [`crate::Printer`] prints the format tokens
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Default)]
|
||||
@@ -120,7 +121,7 @@ impl SourceMapGeneration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum LineEnding {
|
||||
/// Line Feed only (\n), common on Linux and macOS as well as inside git repos
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -104,12 +104,3 @@ def get_owner_id_from_mac_address():
|
||||
mac_address = get_primary_mac_address()
|
||||
except(IOError, OSError) as ex:
|
||||
msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7580
|
||||
import os
|
||||
|
||||
try:
|
||||
pass
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
@@ -695,7 +695,7 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
},
|
||||
) => {
|
||||
let module = module.as_deref();
|
||||
let level = *level;
|
||||
let level = level.map(|level| level.to_u32());
|
||||
if checker.enabled(Rule::ModuleImportNotAtTopOfFile) {
|
||||
pycodestyle::rules::module_import_not_at_top_of_file(checker, stmt);
|
||||
}
|
||||
|
||||
@@ -358,7 +358,7 @@ where
|
||||
range: _,
|
||||
}) => {
|
||||
let module = module.as_deref();
|
||||
let level = *level;
|
||||
let level = level.map(|level| level.to_u32());
|
||||
for alias in names {
|
||||
if let Some("__future__") = module {
|
||||
let name = alias.asname.as_ref().unwrap_or(&alias.name);
|
||||
|
||||
@@ -44,7 +44,7 @@ fn extract_import_map(path: &Path, package: Option<&Path>, blocks: &[&Block]) ->
|
||||
level,
|
||||
range: _,
|
||||
}) => {
|
||||
let level = level.unwrap_or_default() as usize;
|
||||
let level = level.map_or(0, |level| level.to_usize());
|
||||
let module = if let Some(module) = module {
|
||||
let module: &String = module.as_ref();
|
||||
if level == 0 {
|
||||
@@ -95,7 +95,6 @@ pub(crate) fn check_imports(
|
||||
tracker.visit_body(python_ast);
|
||||
tracker
|
||||
};
|
||||
|
||||
let blocks: Vec<&Block> = tracker.iter().collect();
|
||||
|
||||
// Enforce import rules.
|
||||
|
||||
@@ -308,7 +308,7 @@ impl<'a> Importer<'a> {
|
||||
range: _,
|
||||
}) = stmt
|
||||
{
|
||||
if level.map_or(true, |level| level == 0)
|
||||
if level.map_or(true, |level| level.to_u32() == 0)
|
||||
&& name.as_ref().is_some_and(|name| name == module)
|
||||
&& names.iter().all(|alias| alias.name.as_str() != "*")
|
||||
{
|
||||
|
||||
@@ -21,25 +21,14 @@ use crate::checkers::ast::Checker;
|
||||
/// `str.removesuffix` to remove an exact prefix or suffix from a string,
|
||||
/// respectively, which should be preferred when possible.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// As a heuristic, this rule only flags multi-character strings that contain
|
||||
/// duplicate characters. This allows usages like `.strip("xyz")`, which
|
||||
/// removes all occurrences of the characters `x`, `y`, and `z` from the
|
||||
/// leading and trailing ends of the string, but not `.strip("foo")`.
|
||||
///
|
||||
/// The use of unique, multi-character strings may be intentional and
|
||||
/// consistent with the intent of `.strip()`, `.lstrip()`, or `.rstrip()`,
|
||||
/// while the use of duplicate-character strings is very likely to be a
|
||||
/// mistake.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// "text.txt".strip(".txt") # "ex"
|
||||
/// "abcba".strip("ab") # "c"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// "text.txt".removesuffix(".txt") # "text"
|
||||
/// "abcba".removeprefix("ab").removesuffix("ba") # "c"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
@@ -50,7 +39,7 @@ pub struct StripWithMultiCharacters;
|
||||
impl Violation for StripWithMultiCharacters {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Using `.strip()` with multi-character strings is misleading")
|
||||
format!("Using `.strip()` with multi-character strings is misleading the reader")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +65,8 @@ pub(crate) fn strip_with_multi_characters(
|
||||
return;
|
||||
};
|
||||
|
||||
if value.chars().count() > 1 && !value.chars().all_unique() {
|
||||
let num_chars = value.chars().count();
|
||||
if num_chars > 1 && num_chars != value.chars().unique().count() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(StripWithMultiCharacters, expr.range()));
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_bugbear/mod.rs
|
||||
---
|
||||
B005.py:4:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:4:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
2 | s.strip(s) # no warning
|
||||
3 | s.strip("we") # no warning
|
||||
@@ -11,7 +11,7 @@ B005.py:4:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
6 | s.strip("\n\t ") # no warning
|
||||
|
|
||||
|
||||
B005.py:7:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:7:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
5 | s.strip("e") # no warning
|
||||
6 | s.strip("\n\t ") # no warning
|
||||
@@ -21,7 +21,7 @@ B005.py:7:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
9 | s.lstrip("we") # no warning
|
||||
|
|
||||
|
||||
B005.py:10:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:10:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
8 | s.lstrip(s) # no warning
|
||||
9 | s.lstrip("we") # no warning
|
||||
@@ -31,7 +31,7 @@ B005.py:10:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
12 | s.lstrip("\n\t ") # no warning
|
||||
|
|
||||
|
||||
B005.py:13:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:13:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
11 | s.lstrip("e") # no warning
|
||||
12 | s.lstrip("\n\t ") # no warning
|
||||
@@ -41,7 +41,7 @@ B005.py:13:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
15 | s.rstrip("we") # warning
|
||||
|
|
||||
|
||||
B005.py:16:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:16:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
14 | s.rstrip(s) # no warning
|
||||
15 | s.rstrip("we") # warning
|
||||
@@ -51,7 +51,7 @@ B005.py:16:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
18 | s.rstrip("\n\t ") # no warning
|
||||
|
|
||||
|
||||
B005.py:19:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:19:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
17 | s.rstrip("e") # no warning
|
||||
18 | s.rstrip("\n\t ") # no warning
|
||||
@@ -61,7 +61,7 @@ B005.py:19:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
21 | s.strip("あ") # no warning
|
||||
|
|
||||
|
||||
B005.py:22:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:22:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
20 | s.strip("a") # no warning
|
||||
21 | s.strip("あ") # no warning
|
||||
@@ -71,7 +71,7 @@ B005.py:22:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
24 | s.strip("\u0074\u0065\u0073\u0074") # warning
|
||||
|
|
||||
|
||||
B005.py:24:1: B005 Using `.strip()` with multi-character strings is misleading
|
||||
B005.py:24:1: B005 Using `.strip()` with multi-character strings is misleading the reader
|
||||
|
|
||||
22 | s.strip("ああ") # warning
|
||||
23 | s.strip("\ufeff") # no warning
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Identifier, Stmt};
|
||||
use ruff_python_ast::{self as ast, Identifier, Int, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
@@ -99,7 +99,7 @@ fn fix_banned_relative_import(
|
||||
TextRange::default(),
|
||||
)),
|
||||
names: names.clone(),
|
||||
level: Some(0),
|
||||
level: Some(Int::new(0)),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
let content = generator.stmt(&node.into());
|
||||
|
||||
@@ -118,7 +118,7 @@ pub(crate) fn annotate_imports<'a>(
|
||||
AnnotatedImport::ImportFrom {
|
||||
module: module.as_deref(),
|
||||
names: aliases,
|
||||
level: *level,
|
||||
level: level.map(|level| level.to_u32()),
|
||||
trailing_comma: if split_on_trailing_comma {
|
||||
trailing_comma(import, locator, source_type)
|
||||
} else {
|
||||
|
||||
@@ -75,7 +75,7 @@ fn includes_import(stmt: &Stmt, target: &AnyImport) -> bool {
|
||||
return false;
|
||||
};
|
||||
module.as_deref() == target.module
|
||||
&& *level == target.level
|
||||
&& level.map(|level| level.to_u32()) == target.level
|
||||
&& names.iter().any(|alias| {
|
||||
&alias.name == target.name.name
|
||||
&& alias.asname.as_deref() == target.name.as_name
|
||||
@@ -166,7 +166,7 @@ pub(crate) fn add_required_imports(
|
||||
name: name.name.as_str(),
|
||||
as_name: name.asname.as_deref(),
|
||||
},
|
||||
level: *level,
|
||||
level: level.map(|level| level.to_u32()),
|
||||
}),
|
||||
python_ast,
|
||||
locator,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_python_ast::{self as ast, Alias, Identifier, Stmt};
|
||||
use ruff_python_ast::{self as ast, Alias, Identifier, Int, Stmt};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use ruff_diagnostics::{AutofixKind, Diagnostic, Edit, Fix, Violation};
|
||||
@@ -80,7 +80,7 @@ pub(crate) fn manual_from_import(
|
||||
asname: None,
|
||||
range: TextRange::default(),
|
||||
}],
|
||||
level: Some(0),
|
||||
level: Some(Int::new(0)),
|
||||
range: TextRange::default(),
|
||||
};
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
|
||||
@@ -71,7 +71,7 @@ pub(crate) fn deprecated_c_element_tree(checker: &mut Checker, stmt: &Stmt) {
|
||||
level,
|
||||
range: _,
|
||||
}) => {
|
||||
if level.is_some_and(|level| level > 0) {
|
||||
if level.is_some_and(|level| level.to_u32() > 0) {
|
||||
// Ex) `import .xml.etree.cElementTree as ET`
|
||||
} else if let Some(module) = module {
|
||||
if module == "xml.etree.cElementTree" {
|
||||
|
||||
@@ -323,7 +323,7 @@ pub(crate) fn deprecated_mock_import(checker: &mut Checker, stmt: &Stmt) {
|
||||
level,
|
||||
..
|
||||
}) => {
|
||||
if level.is_some_and(|level| level > 0) {
|
||||
if level.is_some_and(|level| level.to_u32() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ fn is_alias(expr: &Expr, semantic: &SemanticModel) -> bool {
|
||||
matches!(
|
||||
call_path.as_slice(),
|
||||
["", "EnvironmentError" | "IOError" | "WindowsError"]
|
||||
| ["mmap" | "select" | "socket" | "os", "error"]
|
||||
| ["mmap" | "select" | "socket", "error"]
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -93,13 +93,16 @@ fn atom_diagnostic(checker: &mut Checker, target: &Expr) {
|
||||
}
|
||||
|
||||
/// Create a [`Diagnostic`] for a tuple of expressions.
|
||||
fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&Expr]) {
|
||||
let mut diagnostic = Diagnostic::new(OSErrorAlias { name: None }, tuple.range());
|
||||
fn tuple_diagnostic(checker: &mut Checker, target: &Expr, aliases: &[&Expr]) {
|
||||
let mut diagnostic = Diagnostic::new(OSErrorAlias { name: None }, target.range());
|
||||
if checker.patch(diagnostic.kind.rule()) {
|
||||
if checker.semantic().is_builtin("OSError") {
|
||||
let Expr::Tuple(ast::ExprTuple { elts, .. }) = target else {
|
||||
panic!("Expected Expr::Tuple");
|
||||
};
|
||||
|
||||
// Filter out any `OSErrors` aliases.
|
||||
let mut remaining: Vec<Expr> = tuple
|
||||
.elts
|
||||
let mut remaining: Vec<Expr> = elts
|
||||
.iter()
|
||||
.filter_map(|elt| {
|
||||
if aliases.contains(&elt) {
|
||||
@@ -111,11 +114,7 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
|
||||
.collect();
|
||||
|
||||
// If `OSError` itself isn't already in the tuple, add it.
|
||||
if tuple
|
||||
.elts
|
||||
.iter()
|
||||
.all(|elt| !is_os_error(elt, checker.semantic()))
|
||||
{
|
||||
if elts.iter().all(|elt| !is_os_error(elt, checker.semantic())) {
|
||||
let node = ast::ExprName {
|
||||
id: "OSError".into(),
|
||||
ctx: ExprContext::Load,
|
||||
@@ -136,8 +135,8 @@ fn tuple_diagnostic(checker: &mut Checker, tuple: &ast::ExprTuple, aliases: &[&E
|
||||
};
|
||||
|
||||
diagnostic.set_fix(Fix::automatic(Edit::range_replacement(
|
||||
pad(content, tuple.range(), checker.locator()),
|
||||
tuple.range(),
|
||||
pad(content, target.range(), checker.locator()),
|
||||
target.range(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
@@ -157,16 +156,16 @@ pub(crate) fn os_error_alias_handlers(checker: &mut Checker, handlers: &[ExceptH
|
||||
atom_diagnostic(checker, expr);
|
||||
}
|
||||
}
|
||||
Expr::Tuple(tuple) => {
|
||||
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||
// List of aliases to replace with `OSError`.
|
||||
let mut aliases: Vec<&Expr> = vec![];
|
||||
for elt in &tuple.elts {
|
||||
for elt in elts {
|
||||
if is_alias(elt, checker.semantic()) {
|
||||
aliases.push(elt);
|
||||
}
|
||||
}
|
||||
if !aliases.is_empty() {
|
||||
tuple_diagnostic(checker, tuple, &aliases);
|
||||
tuple_diagnostic(checker, expr, &aliases);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
||||
@@ -165,7 +165,7 @@ fn compare_version(target_version: &[u32], py_version: PythonVersion, or_equal:
|
||||
|
||||
let (py_major, py_minor) = py_version.as_tuple();
|
||||
|
||||
match if_major.cmp(&py_major.into()) {
|
||||
match if_major.cmp(&py_major) {
|
||||
Ordering::Less => true,
|
||||
Ordering::Greater => false,
|
||||
Ordering::Equal => {
|
||||
@@ -175,11 +175,11 @@ fn compare_version(target_version: &[u32], py_version: PythonVersion, or_equal:
|
||||
if or_equal {
|
||||
// Ex) `sys.version_info <= 3.8`. If Python 3.8 is the minimum supported version,
|
||||
// the condition won't always evaluate to `false`, so we want to return `false`.
|
||||
*if_minor < py_minor.into()
|
||||
*if_minor < py_minor
|
||||
} else {
|
||||
// Ex) `sys.version_info < 3.8`. If Python 3.8 is the minimum supported version,
|
||||
// the condition _will_ always evaluate to `false`, so we want to return `true`.
|
||||
*if_minor <= py_minor.into()
|
||||
*if_minor <= py_minor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,25 +281,5 @@ UP024_0.py:105:11: UP024 [*] Replace aliased errors with `OSError`
|
||||
105 |- except(IOError, OSError) as ex:
|
||||
105 |+ except OSError as ex:
|
||||
106 106 | msg = 'Unable to query URL to get Owner ID: {u}\n{e}'.format(u=owner_id_url, e=ex)
|
||||
107 107 |
|
||||
108 108 |
|
||||
|
||||
UP024_0.py:114:8: UP024 [*] Replace aliased errors with `OSError`
|
||||
|
|
||||
112 | try:
|
||||
113 | pass
|
||||
114 | except os.error:
|
||||
| ^^^^^^^^ UP024
|
||||
115 | pass
|
||||
|
|
||||
= help: Replace `os.error` with builtin `OSError`
|
||||
|
||||
ℹ Fix
|
||||
111 111 |
|
||||
112 112 | try:
|
||||
113 113 | pass
|
||||
114 |-except os.error:
|
||||
114 |+except OSError:
|
||||
115 115 | pass
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use ruff_source_file::Locator;
|
||||
///
|
||||
/// A known type is either a builtin type, any object from the standard library,
|
||||
/// or a type from the `typing_extensions` module.
|
||||
fn is_known_type(call_path: &CallPath, minor_version: u8) -> bool {
|
||||
fn is_known_type(call_path: &CallPath, minor_version: u32) -> bool {
|
||||
match call_path.as_slice() {
|
||||
["" | "typing_extensions", ..] => true,
|
||||
[module, ..] => is_known_standard_library(minor_version, module),
|
||||
@@ -72,7 +72,7 @@ impl<'a> TypingTarget<'a> {
|
||||
expr: &'a Expr,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
minor_version: u8,
|
||||
minor_version: u32,
|
||||
) -> Option<Self> {
|
||||
match expr {
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
@@ -141,7 +141,7 @@ impl<'a> TypingTarget<'a> {
|
||||
&self,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
minor_version: u8,
|
||||
minor_version: u32,
|
||||
) -> bool {
|
||||
match self {
|
||||
TypingTarget::None
|
||||
@@ -189,7 +189,12 @@ impl<'a> TypingTarget<'a> {
|
||||
}
|
||||
|
||||
/// Check if the [`TypingTarget`] explicitly allows `Any`.
|
||||
fn contains_any(&self, semantic: &SemanticModel, locator: &Locator, minor_version: u8) -> bool {
|
||||
fn contains_any(
|
||||
&self,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
minor_version: u32,
|
||||
) -> bool {
|
||||
match self {
|
||||
TypingTarget::Any => true,
|
||||
// `Literal` cannot contain `Any` as it's a dynamic value.
|
||||
@@ -237,7 +242,7 @@ pub(crate) fn type_hint_explicitly_allows_none<'a>(
|
||||
annotation: &'a Expr,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
minor_version: u8,
|
||||
minor_version: u32,
|
||||
) -> Option<&'a Expr> {
|
||||
match TypingTarget::try_from_expr(annotation, semantic, locator, minor_version) {
|
||||
None |
|
||||
@@ -267,7 +272,7 @@ pub(crate) fn type_hint_resolves_to_any(
|
||||
annotation: &Expr,
|
||||
semantic: &SemanticModel,
|
||||
locator: &Locator,
|
||||
minor_version: u8,
|
||||
minor_version: u32,
|
||||
) -> bool {
|
||||
match TypingTarget::try_from_expr(annotation, semantic, locator, minor_version) {
|
||||
None |
|
||||
|
||||
@@ -58,7 +58,7 @@ impl PythonVersion {
|
||||
Self::Py312
|
||||
}
|
||||
|
||||
pub const fn as_tuple(&self) -> (u8, u8) {
|
||||
pub const fn as_tuple(&self) -> (u32, u32) {
|
||||
match self {
|
||||
Self::Py37 => (3, 7),
|
||||
Self::Py38 => (3, 8),
|
||||
@@ -69,11 +69,11 @@ impl PythonVersion {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn major(&self) -> u8 {
|
||||
pub const fn major(&self) -> u32 {
|
||||
self.as_tuple().0
|
||||
}
|
||||
|
||||
pub const fn minor(&self) -> u8 {
|
||||
pub const fn minor(&self) -> u32 {
|
||||
self.as_tuple().1
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,7 @@ use syn::{
|
||||
};
|
||||
|
||||
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let DeriveInput {
|
||||
ident,
|
||||
data,
|
||||
attrs: struct_attributes,
|
||||
..
|
||||
} = input;
|
||||
let DeriveInput { ident, data, .. } = input;
|
||||
|
||||
match data {
|
||||
Data::Struct(DataStruct {
|
||||
@@ -55,39 +50,15 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
|
||||
};
|
||||
}
|
||||
|
||||
let docs: Vec<&Attribute> = struct_attributes
|
||||
.iter()
|
||||
.filter(|attr| attr.path().is_ident("doc"))
|
||||
.collect();
|
||||
|
||||
// Convert the list of `doc` attributes into a single string.
|
||||
let doc = dedent(
|
||||
&docs
|
||||
.into_iter()
|
||||
.map(parse_doc)
|
||||
.collect::<syn::Result<Vec<_>>>()?
|
||||
.join("\n"),
|
||||
)
|
||||
.trim_matches('\n')
|
||||
.to_string();
|
||||
|
||||
let documentation = if doc.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote!(
|
||||
fn documentation() -> Option<&'static str> {
|
||||
Some(&#doc)
|
||||
}
|
||||
))
|
||||
};
|
||||
let options_len = output.len();
|
||||
|
||||
Ok(quote! {
|
||||
impl crate::options_base::OptionsMetadata for #ident {
|
||||
fn record(visit: &mut dyn crate::options_base::Visit) {
|
||||
#(#output);*
|
||||
}
|
||||
|
||||
#documentation
|
||||
impl #ident {
|
||||
pub const fn metadata() -> crate::options_base::OptionGroup {
|
||||
const OPTIONS: [(&'static str, crate::options_base::OptionEntry); #options_len] = [#(#output),*];
|
||||
crate::options_base::OptionGroup::new(&OPTIONS)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -121,7 +92,7 @@ fn handle_option_group(field: &Field) -> syn::Result<proc_macro2::TokenStream> {
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => (visit.record_set(#kebab_name, crate::options_base::OptionSet::of::<#path>()))
|
||||
ident.span() => (#kebab_name, crate::options_base::OptionEntry::Group(#path::metadata()))
|
||||
))
|
||||
}
|
||||
_ => Err(syn::Error::new(
|
||||
@@ -179,14 +150,12 @@ fn handle_option(
|
||||
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
|
||||
|
||||
Ok(quote_spanned!(
|
||||
ident.span() => {
|
||||
visit.record_field(#kebab_name, crate::options_base::OptionField{
|
||||
doc: &#doc,
|
||||
default: &#default,
|
||||
value_type: &#value_type,
|
||||
example: &#example,
|
||||
})
|
||||
}
|
||||
ident.span() => (#kebab_name, crate::options_base::OptionEntry::Field(crate::options_base::OptionField {
|
||||
doc: &#doc,
|
||||
default: &#default,
|
||||
value_type: &#value_type,
|
||||
example: &#example,
|
||||
}))
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1161,7 +1161,7 @@ pub struct StmtImport<'a> {
|
||||
pub struct StmtImportFrom<'a> {
|
||||
module: Option<&'a str>,
|
||||
names: Vec<ComparableAlias<'a>>,
|
||||
level: Option<u32>,
|
||||
level: Option<ast::Int>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -466,7 +466,7 @@ pub struct StmtImportFrom {
|
||||
pub range: TextRange,
|
||||
pub module: Option<Identifier>,
|
||||
pub names: Vec<Alias>,
|
||||
pub level: Option<u32>,
|
||||
pub level: Option<Int>,
|
||||
}
|
||||
|
||||
impl From<StmtImportFrom> for Stmt {
|
||||
@@ -2578,6 +2578,35 @@ impl Ranged for Identifier {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Int(u32);
|
||||
|
||||
impl Int {
|
||||
pub fn new(i: u32) -> Self {
|
||||
Self(i)
|
||||
}
|
||||
pub fn to_u32(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
pub fn to_usize(&self) -> usize {
|
||||
self.0 as _
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<u32> for Int {
|
||||
#[inline]
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialEq<usize> for Int {
|
||||
#[inline]
|
||||
fn eq(&self, other: &usize) -> bool {
|
||||
self.0 as usize == *other
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, is_macro::Is)]
|
||||
pub enum Constant {
|
||||
None,
|
||||
|
||||
@@ -577,9 +577,7 @@ impl<'a> Generator<'a> {
|
||||
statement!({
|
||||
self.p("from ");
|
||||
if let Some(level) = level {
|
||||
for _ in 0..*level {
|
||||
self.p(".");
|
||||
}
|
||||
self.p(&".".repeat(level.to_usize()));
|
||||
}
|
||||
if let Some(module) = module {
|
||||
self.p_id(module);
|
||||
|
||||
@@ -30,7 +30,6 @@ memchr = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, optional = true }
|
||||
smallvec = { workspace = true }
|
||||
static_assertions = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
@@ -53,5 +52,4 @@ required-features = ["serde"]
|
||||
|
||||
[features]
|
||||
serde = ["dep:serde", "ruff_formatter/serde", "ruff_source_file/serde", "ruff_python_ast/serde"]
|
||||
schemars = ["dep:schemars", "ruff_formatter/schemars"]
|
||||
default = []
|
||||
default = ["serde"]
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
[
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 2
|
||||
},
|
||||
{
|
||||
"indent_style": "tab",
|
||||
"indent_style": "Tab",
|
||||
"indent_width": 8
|
||||
},
|
||||
{
|
||||
"indent_style": "tab",
|
||||
"indent_style": "Tab",
|
||||
"indent_width": 4
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 2
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 1
|
||||
},
|
||||
{
|
||||
"indent_style": "tab"
|
||||
"indent_style": "Tab"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 4
|
||||
},
|
||||
{
|
||||
"indent_style": "space",
|
||||
"indent_style": "Space",
|
||||
"indent_width": 2
|
||||
},
|
||||
{
|
||||
"indent_style": "tab"
|
||||
"indent_style": "Tab"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
###
|
||||
# Blank lines around functions
|
||||
###
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
# comment
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
###
|
||||
# Blank lines around imports.
|
||||
###
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x # comment
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f(): pass # comment
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
@@ -376,11 +376,3 @@ def f( # first
|
||||
def this_is_unusual() -> (please := no): ...
|
||||
|
||||
def this_is_unusual(x) -> (please := no): ...
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
try:
|
||||
def test():
|
||||
pass
|
||||
#comment
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@@ -104,110 +104,6 @@ if True: print("a") # 1
|
||||
elif True: print("b") # 2
|
||||
else: print("c") # 3
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass # comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5337
|
||||
if parent_body:
|
||||
if current_body:
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::borrow::Cow;
|
||||
use ruff_formatter::{format_args, write, FormatError, FormatOptions, SourceCode};
|
||||
use ruff_python_ast::node::{AnyNodeRef, AstNode};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_trivia::{lines_after, lines_before};
|
||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
|
||||
use crate::comments::{CommentLinePosition, SourceComment};
|
||||
@@ -86,26 +86,17 @@ impl Format<PyFormatContext<'_>> for FormatLeadingAlternateBranchComments<'_> {
|
||||
if let Some(first_leading) = self.comments.first() {
|
||||
// Leading comments only preserves the lines after the comment but not before.
|
||||
// Insert the necessary lines.
|
||||
write!(
|
||||
f,
|
||||
[empty_lines(lines_before(
|
||||
first_leading.start(),
|
||||
f.context().source()
|
||||
))]
|
||||
)?;
|
||||
if lines_before(first_leading.start(), f.context().source()) > 1 {
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
|
||||
write!(f, [leading_comments(self.comments)])?;
|
||||
} else if let Some(last_preceding) = self.last_node {
|
||||
// The leading comments formatting ensures that it preserves the right amount of lines after
|
||||
// We need to take care of this ourselves, if there's no leading `else` comment.
|
||||
let end = if let Some(last_trailing) =
|
||||
f.context().comments().trailing(last_preceding).last()
|
||||
{
|
||||
last_trailing.end()
|
||||
} else {
|
||||
last_preceding.end()
|
||||
};
|
||||
write!(f, [empty_lines(lines_after(end, f.context().source()))])?;
|
||||
if lines_after_ignoring_trivia(last_preceding.end(), f.context().source()) > 1 {
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -308,14 +299,7 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLines {
|
||||
NodeLevel::TopLevel => match self.lines {
|
||||
0 | 1 => write!(f, [hard_line_break()]),
|
||||
2 => write!(f, [empty_line()]),
|
||||
_ => match f.options().source_type() {
|
||||
PySourceType::Stub => {
|
||||
write!(f, [empty_line()])
|
||||
}
|
||||
PySourceType::Python | PySourceType::Ipynb => {
|
||||
write!(f, [empty_line(), empty_line()])
|
||||
}
|
||||
},
|
||||
_ => write!(f, [empty_line(), empty_line()]),
|
||||
},
|
||||
|
||||
NodeLevel::CompoundStatement => match self.lines {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use ruff_formatter::FormatRuleWithOptions;
|
||||
use ruff_formatter::{FormatContext, FormatRuleWithOptions};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{Constant, ExprConstant};
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange};
|
||||
@@ -78,6 +78,8 @@ impl NeedsParentheses for ExprConstant {
|
||||
OptionalParentheses::Multiline
|
||||
} else if is_multiline_string(self, context.source()) {
|
||||
OptionalParentheses::Never
|
||||
} else if context.options().preview().is_enabled() {
|
||||
OptionalParentheses::Multiline
|
||||
} else {
|
||||
OptionalParentheses::BestFit
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use ruff_formatter::{format_args, write, FormatError};
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
@@ -139,18 +140,131 @@ impl<'a> FormatString<'a> {
|
||||
impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let locator = f.context().locator();
|
||||
let quote_style = f.options().quote_style();
|
||||
match self.layout {
|
||||
StringLayout::Default => {
|
||||
if self.string.is_implicit_concatenated() {
|
||||
in_parentheses_only_group(&FormatStringContinuation::new(self.string)).fmt(f)
|
||||
let continuation = StringContinuation::from_string(self.string, &locator)?;
|
||||
if f.options().preview().is_enabled() {
|
||||
if let Some((first, rest)) = continuation.parts.split_first() {
|
||||
let first_normalized =
|
||||
first.normalize(Quoting::CanChange, &locator, quote_style);
|
||||
|
||||
let quotes = first_normalized.quotes;
|
||||
let mut normalized = SmallVec::with_capacity(continuation.parts.len());
|
||||
normalized.push(first_normalized);
|
||||
|
||||
for part in rest {
|
||||
normalized.push(part.normalize_with_quotes(&locator, quotes));
|
||||
}
|
||||
|
||||
let continuation = NormalizedStringContinuation {
|
||||
string: self.string,
|
||||
parts: normalized,
|
||||
};
|
||||
|
||||
let format_flat = format_with(|f| {
|
||||
quotes.fmt(f)?;
|
||||
|
||||
// TODO comments
|
||||
for part in &continuation.parts {
|
||||
match &part.text {
|
||||
Cow::Borrowed(_) => {
|
||||
source_text_slice(part.range()).fmt(f)?
|
||||
}
|
||||
Cow::Owned(content) => {
|
||||
text(&content, Some(part.start())).fmt(f)?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quotes.fmt(f)
|
||||
});
|
||||
|
||||
let format_multiline = format_with(|f| {
|
||||
// TODO won't format comments again
|
||||
group(&continuation).should_expand(true).fmt(f)
|
||||
});
|
||||
|
||||
let format_joined = format_with(|f| {
|
||||
quotes.fmt(f)?;
|
||||
|
||||
let mut fill = f.fill();
|
||||
|
||||
let separator = format_with(|f| {
|
||||
group(&format_args![
|
||||
if_group_breaks("es),
|
||||
soft_line_break_or_space(),
|
||||
if_group_breaks(&format_args![quotes, space()])
|
||||
])
|
||||
.fmt(f)
|
||||
});
|
||||
|
||||
for part in &continuation.parts {
|
||||
let mut words = part.text.split(' ').peekable();
|
||||
|
||||
while let Some(word) = words.next() {
|
||||
let is_last = words.peek().is_none();
|
||||
let format_word =
|
||||
format_with(|f| write!(f, [text(word, None)]));
|
||||
|
||||
fill.entry(&separator, &format_word);
|
||||
}
|
||||
}
|
||||
|
||||
fill.finish()?;
|
||||
|
||||
quotes.fmt(f)
|
||||
});
|
||||
best_fitting![format_flat, format_multiline, format_joined]
|
||||
.with_mode(BestFittingMode::AllLines)
|
||||
.fmt(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
in_parentheses_only_group(&continuation.normalize(quote_style, &locator))
|
||||
.fmt(f)
|
||||
}
|
||||
} else {
|
||||
StringPart::from_source(self.string.range(), &locator)
|
||||
.normalize(
|
||||
self.string.quoting(&locator),
|
||||
&locator,
|
||||
f.options().quote_style(),
|
||||
)
|
||||
.fmt(f)
|
||||
// Joining/ splitting does not apply to triple quoted strings or expression statement strings
|
||||
// Splitting only applies in parenthesized contexts
|
||||
// Joins strings in non parenthesized contexts
|
||||
// Does not join/split if string has a prefix other than `u` or `f`.
|
||||
if f.options().preview().is_enabled() {
|
||||
let normalized = StringPart::from_source(self.string.range(), &locator)
|
||||
.normalize(self.string.quoting(&locator), &locator, quote_style);
|
||||
|
||||
// TODO how to optimize to avoid allocating a string for every word?
|
||||
write!(f, [normalized.prefix, normalized.quotes])?;
|
||||
|
||||
// TODO split by words longer with a length of at least 6 characters.
|
||||
let mut words = normalized.text.split(' ');
|
||||
let mut fill = f.fill();
|
||||
|
||||
let separator = format_with(|f| {
|
||||
group(&format_args![
|
||||
if_group_breaks(&normalized.quotes),
|
||||
soft_line_break_or_space(),
|
||||
if_group_breaks(&format_args![normalized.quotes, space()])
|
||||
])
|
||||
.fmt(f)
|
||||
});
|
||||
|
||||
while let Some(word) = words.next() {
|
||||
let format_word = format_with(|f| write!(f, [text(word, None)]));
|
||||
|
||||
fill.entry(&separator, &format_word);
|
||||
}
|
||||
|
||||
fill.finish()?;
|
||||
|
||||
normalized.quotes.fmt(f)
|
||||
} else {
|
||||
StringPart::from_source(self.string.range(), &locator)
|
||||
.normalize(self.string.quoting(&locator), &locator, quote_style)
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
StringLayout::DocString => {
|
||||
@@ -160,33 +274,24 @@ impl<'a> Format<PyFormatContext<'_>> for FormatString<'a> {
|
||||
format_docstring(&normalized, f)
|
||||
}
|
||||
StringLayout::ImplicitConcatenatedStringInBinaryLike => {
|
||||
FormatStringContinuation::new(self.string).fmt(f)
|
||||
StringContinuation::from_string(self.string, &locator)?
|
||||
.normalize(f.options().quote_style(), &locator)
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatStringContinuation<'a> {
|
||||
struct StringContinuation<'a> {
|
||||
parts: SmallVec<[StringPart; 4]>,
|
||||
string: &'a AnyString<'a>,
|
||||
}
|
||||
|
||||
impl<'a> FormatStringContinuation<'a> {
|
||||
fn new(string: &'a AnyString<'a>) -> Self {
|
||||
if let AnyString::Constant(constant) = string {
|
||||
debug_assert!(constant.value.is_str() || constant.value.is_bytes());
|
||||
}
|
||||
Self { string }
|
||||
}
|
||||
}
|
||||
impl<'a> StringContinuation<'a> {
|
||||
fn from_string(string: &'a AnyString<'a>, locator: &Locator) -> FormatResult<Self> {
|
||||
debug_assert!(string.is_implicit_concatenated());
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let comments = f.context().comments().clone();
|
||||
let locator = f.context().locator();
|
||||
let quote_style = f.options().quote_style();
|
||||
let mut dangling_comments = comments.dangling(self.string);
|
||||
|
||||
let string_range = self.string.range();
|
||||
let string_range = string.range();
|
||||
let string_content = locator.slice(string_range);
|
||||
|
||||
// The AST parses implicit concatenation as a single string.
|
||||
@@ -195,7 +300,7 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||
// because this is a black preview style.
|
||||
let lexer = lex_starts_at(string_content, Mode::Expression, string_range.start());
|
||||
|
||||
let mut joiner = f.join_with(in_parentheses_only_soft_line_break_or_space());
|
||||
let mut parts = SmallVec::new();
|
||||
|
||||
for token in lexer {
|
||||
let (token, token_range) = match token {
|
||||
@@ -228,46 +333,7 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||
|
||||
match token {
|
||||
Tok::String { .. } => {
|
||||
// ```python
|
||||
// (
|
||||
// "a"
|
||||
// # leading
|
||||
// "the comment above"
|
||||
// )
|
||||
// ```
|
||||
let leading_comments_end = dangling_comments
|
||||
.partition_point(|comment| comment.start() <= token_range.start());
|
||||
|
||||
let (leading_part_comments, rest) =
|
||||
dangling_comments.split_at(leading_comments_end);
|
||||
|
||||
// ```python
|
||||
// (
|
||||
// "a" # trailing comment
|
||||
// "the comment above"
|
||||
// )
|
||||
// ```
|
||||
let trailing_comments_end = rest.partition_point(|comment| {
|
||||
comment.line_position().is_end_of_line()
|
||||
&& !locator.contains_line_break(TextRange::new(
|
||||
token_range.end(),
|
||||
comment.start(),
|
||||
))
|
||||
});
|
||||
|
||||
let (trailing_part_comments, rest) = rest.split_at(trailing_comments_end);
|
||||
let part = StringPart::from_source(token_range, &locator);
|
||||
let normalized =
|
||||
part.normalize(self.string.quoting(&locator), &locator, quote_style);
|
||||
|
||||
joiner.entry(&format_args![
|
||||
line_suffix_boundary(),
|
||||
leading_comments(leading_part_comments),
|
||||
normalized,
|
||||
trailing_comments(trailing_part_comments)
|
||||
]);
|
||||
|
||||
dangling_comments = rest;
|
||||
parts.push(StringPart::from_source(token_range, locator));
|
||||
}
|
||||
Tok::Comment(_)
|
||||
| Tok::NonLogicalNewline
|
||||
@@ -278,6 +344,79 @@ impl Format<PyFormatContext<'_>> for FormatStringContinuation<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { parts, string })
|
||||
}
|
||||
|
||||
fn normalize(
|
||||
self,
|
||||
quote_style: QuoteStyle,
|
||||
locator: &Locator<'a>,
|
||||
) -> NormalizedStringContinuation<'a> {
|
||||
let quoting = self.string.quoting(locator);
|
||||
let normalized = self
|
||||
.parts
|
||||
.into_iter()
|
||||
.map(|part| part.normalize(quoting, &locator, quote_style))
|
||||
.collect();
|
||||
|
||||
NormalizedStringContinuation {
|
||||
parts: normalized,
|
||||
string: self.string,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NormalizedStringContinuation<'a> {
|
||||
parts: SmallVec<[NormalizedString<'a>; 4]>,
|
||||
string: &'a AnyString<'a>,
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for NormalizedStringContinuation<'_> {
|
||||
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
let comments = f.context().comments().clone();
|
||||
let locator = f.context().locator();
|
||||
let quote_style = f.options().quote_style();
|
||||
let quoting = self.string.quoting(&locator);
|
||||
|
||||
let mut dangling_comments = comments.dangling(self.string);
|
||||
let mut joiner = f.join_with(in_parentheses_only_soft_line_break_or_space());
|
||||
|
||||
for part in &self.parts {
|
||||
// ```python
|
||||
// (
|
||||
// "a"
|
||||
// # leading
|
||||
// "the comment above"
|
||||
// )
|
||||
// ```
|
||||
let leading_comments_end =
|
||||
dangling_comments.partition_point(|comment| comment.start() <= part.start());
|
||||
|
||||
let (leading_part_comments, rest) = dangling_comments.split_at(leading_comments_end);
|
||||
|
||||
// ```python
|
||||
// (
|
||||
// "a" # trailing comment
|
||||
// "the comment above"
|
||||
// )
|
||||
// ```
|
||||
let trailing_comments_end = rest.partition_point(|comment| {
|
||||
comment.line_position().is_end_of_line()
|
||||
&& !locator.contains_line_break(TextRange::new(part.end(), comment.start()))
|
||||
});
|
||||
|
||||
let (trailing_part_comments, rest) = rest.split_at(trailing_comments_end);
|
||||
|
||||
joiner.entry(&format_args![
|
||||
line_suffix_boundary(),
|
||||
leading_comments(leading_part_comments),
|
||||
part,
|
||||
trailing_comments(trailing_part_comments)
|
||||
]);
|
||||
|
||||
dangling_comments = rest;
|
||||
}
|
||||
|
||||
debug_assert!(dangling_comments.is_empty());
|
||||
|
||||
joiner.finish()
|
||||
@@ -320,10 +459,10 @@ impl StringPart {
|
||||
|
||||
/// Computes the strings preferred quotes and normalizes its content.
|
||||
fn normalize<'a>(
|
||||
self,
|
||||
&self,
|
||||
quoting: Quoting,
|
||||
locator: &'a Locator,
|
||||
quote_style: QuoteStyle,
|
||||
locator: &Locator<'a>,
|
||||
configured_quote_style: QuoteStyle,
|
||||
) -> NormalizedString<'a> {
|
||||
let raw_content = locator.slice(self.content_range);
|
||||
|
||||
@@ -331,28 +470,40 @@ impl StringPart {
|
||||
Quoting::Preserve => self.quotes,
|
||||
Quoting::CanChange => {
|
||||
if self.prefix.is_raw_string() {
|
||||
preferred_quotes_raw(raw_content, self.quotes, quote_style)
|
||||
preferred_quotes_raw(raw_content, self.quotes, configured_quote_style)
|
||||
} else {
|
||||
preferred_quotes(raw_content, self.quotes, quote_style)
|
||||
preferred_quotes(raw_content, self.quotes, configured_quote_style)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let normalized = normalize_string(
|
||||
locator.slice(self.content_range),
|
||||
preferred_quotes,
|
||||
self.prefix.is_raw_string(),
|
||||
);
|
||||
self.normalize_with_quotes(locator, preferred_quotes)
|
||||
}
|
||||
|
||||
fn normalize_with_quotes<'a>(
|
||||
&self,
|
||||
locator: &Locator<'a>,
|
||||
quotes: StringQuotes,
|
||||
) -> NormalizedString<'a> {
|
||||
let raw_content = locator.slice(self.content_range);
|
||||
|
||||
let normalized = normalize_string(raw_content, quotes, self.prefix.is_raw_string());
|
||||
|
||||
NormalizedString {
|
||||
prefix: self.prefix,
|
||||
content_range: self.content_range,
|
||||
text: normalized,
|
||||
quotes: preferred_quotes,
|
||||
quotes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for StringPart {
|
||||
fn range(&self) -> TextRange {
|
||||
self.content_range
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct NormalizedString<'a> {
|
||||
prefix: StringPrefix,
|
||||
|
||||
@@ -17,6 +17,7 @@ use crate::comments::{
|
||||
pub use crate::context::PyFormatContext;
|
||||
pub use crate::options::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
|
||||
use crate::verbatim::suppressed_node;
|
||||
pub use settings::FormatterSettings;
|
||||
|
||||
pub(crate) mod builders;
|
||||
pub mod cli;
|
||||
@@ -29,6 +30,7 @@ mod options;
|
||||
pub(crate) mod other;
|
||||
pub(crate) mod pattern;
|
||||
mod prelude;
|
||||
mod settings;
|
||||
pub(crate) mod statement;
|
||||
pub(crate) mod type_param;
|
||||
mod verbatim;
|
||||
|
||||
@@ -5,8 +5,8 @@ use ruff_python_ast::PySourceType;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Resolved options for formatting one individual file. The difference to `FormatterSettings`
|
||||
/// is that `FormatterSettings` stores the settings for multiple files (the entire project, a subdirectory, ..)
|
||||
/// Resolved options for formatting one individual file. This is different from [`crate::FormatterSettings`] which
|
||||
/// represents the formatting settings for multiple files (the whole project, a subdirectory, ...)
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
@@ -185,7 +185,6 @@ impl FormatOptions for PyFormatOptions {
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(rename_all = "kebab-case")
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum QuoteStyle {
|
||||
Single,
|
||||
#[default]
|
||||
@@ -269,9 +268,9 @@ impl FromStr for MagicTrailingComma {
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
|
||||
pub enum PreviewMode {
|
||||
#[default]
|
||||
Disabled,
|
||||
|
||||
#[default]
|
||||
Enabled,
|
||||
}
|
||||
|
||||
|
||||
49
crates/ruff_python_formatter/src/settings.rs
Normal file
49
crates/ruff_python_formatter/src/settings.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use ruff_formatter::{FormatOptions, IndentStyle, LineWidth};
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
|
||||
use crate::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
|
||||
|
||||
#[derive(CacheKey, Clone, Debug)]
|
||||
pub struct FormatterSettings {
|
||||
/// The files that are excluded from formatting (but may be linted).
|
||||
pub exclude: Vec<PathBuf>,
|
||||
|
||||
pub preview: PreviewMode,
|
||||
|
||||
pub line_width: LineWidth,
|
||||
|
||||
pub indent_style: IndentStyle,
|
||||
|
||||
pub quote_style: QuoteStyle,
|
||||
|
||||
pub magic_trailing_comma: MagicTrailingComma,
|
||||
}
|
||||
|
||||
impl FormatterSettings {
|
||||
pub fn to_format_options(&self, source_type: PySourceType) -> PyFormatOptions {
|
||||
PyFormatOptions::from_source_type(source_type)
|
||||
.with_indent_style(self.indent_style)
|
||||
.with_quote_style(self.quote_style)
|
||||
.with_magic_trailing_comma(self.magic_trailing_comma)
|
||||
.with_preview(self.preview)
|
||||
.with_line_width(self.line_width)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FormatterSettings {
|
||||
fn default() -> Self {
|
||||
let default_options = PyFormatOptions::default();
|
||||
|
||||
Self {
|
||||
exclude: Vec::default(),
|
||||
preview: PreviewMode::Disabled,
|
||||
line_width: default_options.line_width(),
|
||||
indent_style: default_options.indent_style(),
|
||||
quote_style: default_options.quote_style(),
|
||||
magic_trailing_comma: default_options.magic_trailing_comma(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ impl FormatNodeRule<StmtImportFrom> for FormatStmtImportFrom {
|
||||
} = item;
|
||||
|
||||
let level_str = level
|
||||
.map(|level| ".".repeat(level as usize))
|
||||
.map(|level| ".".repeat(level.to_usize()))
|
||||
.unwrap_or(String::default());
|
||||
|
||||
write!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_formatter::{write, FormatOwnedWithRule, FormatRefWithRule, FormatRuleWithOptions};
|
||||
use ruff_python_ast::helpers::is_compound_statement;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ExprConstant, PySourceType, Stmt, Suite};
|
||||
use ruff_python_ast::{self as ast, Constant, Expr, ExprConstant, Stmt, Suite};
|
||||
use ruff_python_trivia::{lines_after, lines_after_ignoring_trivia, lines_before};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
@@ -192,14 +192,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
SuiteKind::TopLevel => {
|
||||
match lines_after_ignoring_trivia(preceding.end(), source) {
|
||||
0..=2 => empty_line().fmt(f)?,
|
||||
_ => match source_type {
|
||||
PySourceType::Stub => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
PySourceType::Python | PySourceType::Ipynb => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
},
|
||||
_ => write!(f, [empty_line(), empty_line()])?,
|
||||
}
|
||||
}
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
@@ -232,15 +225,8 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
match lines_before(start, source) {
|
||||
0 | 1 => hard_line_break().fmt(f)?,
|
||||
2 => empty_line().fmt(f)?,
|
||||
_ => match self.kind {
|
||||
SuiteKind::TopLevel => match source_type {
|
||||
PySourceType::Stub => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
PySourceType::Python | PySourceType::Ipynb => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
},
|
||||
3.. => match self.kind {
|
||||
SuiteKind::TopLevel => write!(f, [empty_line(), empty_line()])?,
|
||||
SuiteKind::Function | SuiteKind::Class | SuiteKind::Other => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
@@ -294,14 +280,7 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
|
||||
NodeLevel::TopLevel => match count_lines(end) {
|
||||
0 | 1 => hard_line_break().fmt(f)?,
|
||||
2 => empty_line().fmt(f)?,
|
||||
_ => match source_type {
|
||||
PySourceType::Stub => {
|
||||
empty_line().fmt(f)?;
|
||||
}
|
||||
PySourceType::Python | PySourceType::Ipynb => {
|
||||
write!(f, [empty_line(), empty_line()])?;
|
||||
}
|
||||
},
|
||||
_ => write!(f, [empty_line(), empty_line()])?,
|
||||
},
|
||||
NodeLevel::CompoundStatement => match count_lines(end) {
|
||||
0 | 1 => hard_line_break().fmt(f)?,
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/newlines.pyi
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
###
|
||||
# Blank lines around functions
|
||||
###
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
# comment
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
###
|
||||
# Blank lines around imports.
|
||||
###
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f():
|
||||
import x # comment
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
|
||||
def f(): pass # comment
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
###
|
||||
# Blank lines around functions
|
||||
###
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
if True:
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
# comment
|
||||
def f():
|
||||
pass
|
||||
|
||||
x = 1
|
||||
|
||||
# comment
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
###
|
||||
# Blank lines around imports.
|
||||
###
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x
|
||||
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
def f():
|
||||
import x # comment
|
||||
# comment
|
||||
|
||||
import y
|
||||
|
||||
def f():
|
||||
pass # comment
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
|
||||
def f():
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
x = 1
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -382,14 +382,6 @@ def f( # first
|
||||
def this_is_unusual() -> (please := no): ...
|
||||
|
||||
def this_is_unusual(x) -> (please := no): ...
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
try:
|
||||
def test():
|
||||
pass
|
||||
#comment
|
||||
except ImportError:
|
||||
pass
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -929,17 +921,6 @@ def this_is_unusual() -> (please := no):
|
||||
|
||||
def this_is_unusual(x) -> (please := no):
|
||||
...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
try:
|
||||
|
||||
def test():
|
||||
pass
|
||||
|
||||
# comment
|
||||
except ImportError:
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -110,110 +110,6 @@ if True: print("a") # 1
|
||||
elif True: print("b") # 2
|
||||
else: print("c") # 3
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass # comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5337
|
||||
if parent_body:
|
||||
if current_body:
|
||||
@@ -267,7 +163,6 @@ elif aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
|
||||
]:
|
||||
...
|
||||
|
||||
|
||||
else:
|
||||
...
|
||||
|
||||
@@ -342,108 +237,6 @@ elif True:
|
||||
else:
|
||||
print("c") # 3
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass # comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
|
||||
# comment
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5337
|
||||
if parent_body:
|
||||
if current_body:
|
||||
|
||||
@@ -253,18 +253,18 @@ ImportStatement: ast::Stmt = {
|
||||
},
|
||||
};
|
||||
|
||||
ImportFromLocation: (Option<u32>, Option<ast::Identifier>) = {
|
||||
ImportFromLocation: (Option<ast::Int>, Option<ast::Identifier>) = {
|
||||
<dots: ImportDots*> <name:DottedName> => {
|
||||
(Some(dots.iter().sum()), Some(name))
|
||||
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), Some(name))
|
||||
},
|
||||
<dots: ImportDots+> => {
|
||||
(Some(dots.iter().sum()), None)
|
||||
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), None)
|
||||
},
|
||||
};
|
||||
|
||||
ImportDots: u32 = {
|
||||
"..." => 3,
|
||||
"." => 1,
|
||||
ImportDots: ast::Int = {
|
||||
"..." => ast::Int::new(3),
|
||||
"." => ast::Int::new(1),
|
||||
};
|
||||
|
||||
ImportAsNames: Vec<ast::Alias> = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// auto-generated: "lalrpop 0.20.0"
|
||||
// sha3: eb535c9ae34baad8c940ef61dbbea0a7fec7baf3cd62af40837b2616f656f927
|
||||
// sha3: e8f3229288c1a13387ea6041355e2d8fe9ab788fbc7229032d2de92beb675944
|
||||
use num_bigint::BigInt;
|
||||
use ruff_text_size::{Ranged, TextSize};
|
||||
use ruff_python_ast::{self as ast, IpyEscapeKind};
|
||||
@@ -117,9 +117,9 @@ mod __parse__Top {
|
||||
Variant69(core::option::Option<(Option<(TextSize, TextSize, Option<ast::Identifier>)>, ast::Expr)>),
|
||||
Variant70(ast::Alias),
|
||||
Variant71(Vec<ast::Alias>),
|
||||
Variant72(u32),
|
||||
Variant73(alloc::vec::Vec<u32>),
|
||||
Variant74((Option<u32>, Option<ast::Identifier>)),
|
||||
Variant72(ast::Int),
|
||||
Variant73(alloc::vec::Vec<ast::Int>),
|
||||
Variant74((Option<ast::Int>, Option<ast::Identifier>)),
|
||||
Variant75(ast::MatchCase),
|
||||
Variant76(alloc::vec::Vec<ast::MatchCase>),
|
||||
Variant77(ast::PatternKeyword),
|
||||
@@ -17596,7 +17596,7 @@ mod __parse__Top {
|
||||
fn __pop_Variant74<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
) -> (TextSize, (Option<u32>, Option<ast::Identifier>), TextSize)
|
||||
) -> (TextSize, (Option<ast::Int>, Option<ast::Identifier>), TextSize)
|
||||
{
|
||||
match __symbols.pop() {
|
||||
Some((__l, __Symbol::Variant74(__v), __r)) => (__l, __v, __r),
|
||||
@@ -17973,6 +17973,16 @@ mod __parse__Top {
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant73<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
) -> (TextSize, alloc::vec::Vec<ast::Int>, TextSize)
|
||||
{
|
||||
match __symbols.pop() {
|
||||
Some((__l, __Symbol::Variant73(__v), __r)) => (__l, __v, __r),
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant76<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
@@ -18043,16 +18053,6 @@ mod __parse__Top {
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant73<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
) -> (TextSize, alloc::vec::Vec<u32>, TextSize)
|
||||
{
|
||||
match __symbols.pop() {
|
||||
Some((__l, __Symbol::Variant73(__v), __r)) => (__l, __v, __r),
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant70<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
@@ -18143,6 +18143,16 @@ mod __parse__Top {
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant72<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
) -> (TextSize, ast::Int, TextSize)
|
||||
{
|
||||
match __symbols.pop() {
|
||||
Some((__l, __Symbol::Variant72(__v), __r)) => (__l, __v, __r),
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant75<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
@@ -18513,16 +18523,6 @@ mod __parse__Top {
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
fn __pop_Variant72<
|
||||
>(
|
||||
__symbols: &mut alloc::vec::Vec<(TextSize,__Symbol<>,TextSize)>
|
||||
) -> (TextSize, u32, TextSize)
|
||||
{
|
||||
match __symbols.pop() {
|
||||
Some((__l, __Symbol::Variant72(__v), __r)) => (__l, __v, __r),
|
||||
_ => __symbol_type_mismatch()
|
||||
}
|
||||
}
|
||||
pub(crate) fn __reduce0<
|
||||
>(
|
||||
mode: Mode,
|
||||
@@ -31464,7 +31464,7 @@ fn __action61<
|
||||
mode: Mode,
|
||||
(_, location, _): (TextSize, TextSize, TextSize),
|
||||
(_, _, _): (TextSize, token::Tok, TextSize),
|
||||
(_, source, _): (TextSize, (Option<u32>, Option<ast::Identifier>), TextSize),
|
||||
(_, source, _): (TextSize, (Option<ast::Int>, Option<ast::Identifier>), TextSize),
|
||||
(_, _, _): (TextSize, token::Tok, TextSize),
|
||||
(_, names, _): (TextSize, Vec<ast::Alias>, TextSize),
|
||||
(_, end_location, _): (TextSize, TextSize, TextSize),
|
||||
@@ -31488,12 +31488,12 @@ fn __action61<
|
||||
fn __action62<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, dots, _): (TextSize, alloc::vec::Vec<u32>, TextSize),
|
||||
(_, dots, _): (TextSize, alloc::vec::Vec<ast::Int>, TextSize),
|
||||
(_, name, _): (TextSize, ast::Identifier, TextSize),
|
||||
) -> (Option<u32>, Option<ast::Identifier>)
|
||||
) -> (Option<ast::Int>, Option<ast::Identifier>)
|
||||
{
|
||||
{
|
||||
(Some(dots.iter().sum()), Some(name))
|
||||
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), Some(name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31502,11 +31502,11 @@ fn __action62<
|
||||
fn __action63<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, dots, _): (TextSize, alloc::vec::Vec<u32>, TextSize),
|
||||
) -> (Option<u32>, Option<ast::Identifier>)
|
||||
(_, dots, _): (TextSize, alloc::vec::Vec<ast::Int>, TextSize),
|
||||
) -> (Option<ast::Int>, Option<ast::Identifier>)
|
||||
{
|
||||
{
|
||||
(Some(dots.iter().sum()), None)
|
||||
(Some(ast::Int::new(dots.iter().map(ast::Int::to_u32).sum())), None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31516,9 +31516,9 @@ fn __action64<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, __0, _): (TextSize, token::Tok, TextSize),
|
||||
) -> u32
|
||||
) -> ast::Int
|
||||
{
|
||||
3
|
||||
ast::Int::new(3)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
@@ -31527,9 +31527,9 @@ fn __action65<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, __0, _): (TextSize, token::Tok, TextSize),
|
||||
) -> u32
|
||||
) -> ast::Int
|
||||
{
|
||||
1
|
||||
ast::Int::new(1)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
@@ -36264,8 +36264,8 @@ fn __action364<
|
||||
fn __action365<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, __0, _): (TextSize, u32, TextSize),
|
||||
) -> alloc::vec::Vec<u32>
|
||||
(_, __0, _): (TextSize, ast::Int, TextSize),
|
||||
) -> alloc::vec::Vec<ast::Int>
|
||||
{
|
||||
alloc::vec![__0]
|
||||
}
|
||||
@@ -36275,9 +36275,9 @@ fn __action365<
|
||||
fn __action366<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, v, _): (TextSize, alloc::vec::Vec<u32>, TextSize),
|
||||
(_, e, _): (TextSize, u32, TextSize),
|
||||
) -> alloc::vec::Vec<u32>
|
||||
(_, v, _): (TextSize, alloc::vec::Vec<ast::Int>, TextSize),
|
||||
(_, e, _): (TextSize, ast::Int, TextSize),
|
||||
) -> alloc::vec::Vec<ast::Int>
|
||||
{
|
||||
{ let mut v = v; v.push(e); v }
|
||||
}
|
||||
@@ -36289,7 +36289,7 @@ fn __action367<
|
||||
mode: Mode,
|
||||
__lookbehind: &TextSize,
|
||||
__lookahead: &TextSize,
|
||||
) -> alloc::vec::Vec<u32>
|
||||
) -> alloc::vec::Vec<ast::Int>
|
||||
{
|
||||
alloc::vec![]
|
||||
}
|
||||
@@ -36299,8 +36299,8 @@ fn __action367<
|
||||
fn __action368<
|
||||
>(
|
||||
mode: Mode,
|
||||
(_, v, _): (TextSize, alloc::vec::Vec<u32>, TextSize),
|
||||
) -> alloc::vec::Vec<u32>
|
||||
(_, v, _): (TextSize, alloc::vec::Vec<ast::Int>, TextSize),
|
||||
) -> alloc::vec::Vec<ast::Int>
|
||||
{
|
||||
v
|
||||
}
|
||||
@@ -45772,7 +45772,7 @@ fn __action806<
|
||||
>(
|
||||
mode: Mode,
|
||||
__0: (TextSize, token::Tok, TextSize),
|
||||
__1: (TextSize, (Option<u32>, Option<ast::Identifier>), TextSize),
|
||||
__1: (TextSize, (Option<ast::Int>, Option<ast::Identifier>), TextSize),
|
||||
__2: (TextSize, token::Tok, TextSize),
|
||||
__3: (TextSize, Vec<ast::Alias>, TextSize),
|
||||
__4: (TextSize, TextSize, TextSize),
|
||||
@@ -59644,7 +59644,7 @@ fn __action1299<
|
||||
>(
|
||||
mode: Mode,
|
||||
__0: (TextSize, token::Tok, TextSize),
|
||||
__1: (TextSize, (Option<u32>, Option<ast::Identifier>), TextSize),
|
||||
__1: (TextSize, (Option<ast::Int>, Option<ast::Identifier>), TextSize),
|
||||
__2: (TextSize, token::Tok, TextSize),
|
||||
__3: (TextSize, Vec<ast::Alias>, TextSize),
|
||||
) -> ast::Stmt
|
||||
@@ -66376,7 +66376,7 @@ fn __action1542<
|
||||
>(
|
||||
mode: Mode,
|
||||
__0: (TextSize, ast::Identifier, TextSize),
|
||||
) -> (Option<u32>, Option<ast::Identifier>)
|
||||
) -> (Option<ast::Int>, Option<ast::Identifier>)
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.0;
|
||||
@@ -66398,9 +66398,9 @@ fn __action1542<
|
||||
fn __action1543<
|
||||
>(
|
||||
mode: Mode,
|
||||
__0: (TextSize, alloc::vec::Vec<u32>, TextSize),
|
||||
__0: (TextSize, alloc::vec::Vec<ast::Int>, TextSize),
|
||||
__1: (TextSize, ast::Identifier, TextSize),
|
||||
) -> (Option<u32>, Option<ast::Identifier>)
|
||||
) -> (Option<ast::Int>, Option<ast::Identifier>)
|
||||
{
|
||||
let __start0 = __0.0;
|
||||
let __end0 = __0.2;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! This file is generated by `scripts/generate_known_standard_library.py`
|
||||
|
||||
pub fn is_known_standard_library(minor_version: u8, module: &str) -> bool {
|
||||
pub fn is_known_standard_library(minor_version: u32, module: &str) -> bool {
|
||||
matches!(
|
||||
(minor_version, module),
|
||||
(
|
||||
|
||||
@@ -303,7 +303,7 @@ impl<'a> ParsedModule<'a> {
|
||||
// TODO(konstin): Add an options for py/pyi to the UI (2/2)
|
||||
let options = settings
|
||||
.formatter
|
||||
.to_format_options(PySourceType::default(), self.source_code);
|
||||
.to_format_options(PySourceType::default());
|
||||
|
||||
format_node(
|
||||
&self.module,
|
||||
|
||||
@@ -15,9 +15,7 @@ license = { workspace = true }
|
||||
[dependencies]
|
||||
ruff_linter = { path = "../ruff_linter" }
|
||||
ruff_formatter = { path = "../ruff_formatter" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter", features = ["serde"] }
|
||||
ruff_python_ast = { path = "../ruff_python_ast" }
|
||||
ruff_source_file = { path = "../ruff_source_file" }
|
||||
ruff_python_formatter = { path = "../ruff_python_formatter" }
|
||||
ruff_cache = { path = "../ruff_cache" }
|
||||
ruff_macros = { path = "../ruff_macros" }
|
||||
|
||||
@@ -45,6 +43,4 @@ tempfile = "3.6.0"
|
||||
|
||||
|
||||
[features]
|
||||
schemars = [ "dep:schemars", "ruff_formatter/schemars", "ruff_python_formatter/schemars" ]
|
||||
|
||||
default = []
|
||||
schemars = [ "dep:schemars" ]
|
||||
|
||||
@@ -32,20 +32,17 @@ use ruff_linter::settings::{
|
||||
use ruff_linter::{
|
||||
fs, warn_user, warn_user_once, warn_user_once_by_id, RuleSelector, RUFF_PKG_VERSION,
|
||||
};
|
||||
use ruff_python_formatter::{MagicTrailingComma, QuoteStyle};
|
||||
use ruff_python_formatter::{FormatterSettings, MagicTrailingComma, QuoteStyle};
|
||||
|
||||
use crate::options::{
|
||||
Flake8AnnotationsOptions, Flake8BanditOptions, Flake8BugbearOptions, Flake8BuiltinsOptions,
|
||||
Flake8ComprehensionsOptions, Flake8CopyrightOptions, Flake8ErrMsgOptions, Flake8GetTextOptions,
|
||||
Flake8ImplicitStrConcatOptions, Flake8ImportConventionsOptions, Flake8PytestStyleOptions,
|
||||
Flake8QuotesOptions, Flake8SelfOptions, Flake8TidyImportsOptions, Flake8TypeCheckingOptions,
|
||||
Flake8UnusedArgumentsOptions, FormatOptions, FormatOrOutputFormat, IsortOptions, McCabeOptions,
|
||||
Options, Pep8NamingOptions, PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions,
|
||||
PyflakesOptions, PylintOptions,
|
||||
};
|
||||
use crate::settings::{
|
||||
FileResolverSettings, FormatterSettings, LineEnding, Settings, EXCLUDE, INCLUDE,
|
||||
Flake8UnusedArgumentsOptions, IsortOptions, McCabeOptions, Options, Pep8NamingOptions,
|
||||
PyUpgradeOptions, PycodestyleOptions, PydocstyleOptions, PyflakesOptions, PylintOptions,
|
||||
};
|
||||
use crate::settings::{FileResolverSettings, Settings, EXCLUDE, INCLUDE};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RuleSelection {
|
||||
@@ -116,8 +113,6 @@ pub struct Configuration {
|
||||
pub pyflakes: Option<PyflakesOptions>,
|
||||
pub pylint: Option<PylintOptions>,
|
||||
pub pyupgrade: Option<PyUpgradeOptions>,
|
||||
|
||||
pub format: FormatConfiguration,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -134,28 +129,6 @@ impl Configuration {
|
||||
|
||||
let target_version = self.target_version.unwrap_or_default();
|
||||
let rules = self.as_rule_table();
|
||||
let preview = self.preview.unwrap_or_default();
|
||||
|
||||
let format = self.format;
|
||||
let format_defaults = FormatterSettings::default();
|
||||
// TODO(micha): Support changing the tab-width but disallow changing the number of spaces
|
||||
let formatter = FormatterSettings {
|
||||
preview: match format.preview.unwrap_or(preview) {
|
||||
PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled,
|
||||
PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled,
|
||||
},
|
||||
line_width: self
|
||||
.line_length
|
||||
.map_or(format_defaults.line_width, |length| {
|
||||
LineWidth::from(NonZeroU16::from(length))
|
||||
}),
|
||||
line_ending: format.line_ending.unwrap_or(format_defaults.line_ending),
|
||||
indent_style: format.indent_style.unwrap_or(format_defaults.indent_style),
|
||||
quote_style: format.quote_style.unwrap_or(format_defaults.quote_style),
|
||||
magic_trailing_comma: format
|
||||
.magic_trailing_comma
|
||||
.unwrap_or(format_defaults.magic_trailing_comma),
|
||||
};
|
||||
|
||||
Ok(Settings {
|
||||
cache_dir: self
|
||||
@@ -212,7 +185,7 @@ impl Configuration {
|
||||
.task_tags
|
||||
.unwrap_or_else(|| TASK_TAGS.iter().map(ToString::to_string).collect()),
|
||||
logger_objects: self.logger_objects.unwrap_or_default(),
|
||||
preview,
|
||||
preview: self.preview.unwrap_or_default(),
|
||||
typing_modules: self.typing_modules.unwrap_or_default(),
|
||||
// Plugins
|
||||
flake8_annotations: self
|
||||
@@ -317,7 +290,20 @@ impl Configuration {
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
|
||||
formatter,
|
||||
formatter: FormatterSettings {
|
||||
exclude: vec![],
|
||||
preview: self
|
||||
.preview
|
||||
.map(|preview| match preview {
|
||||
PreviewMode::Disabled => ruff_python_formatter::PreviewMode::Disabled,
|
||||
PreviewMode::Enabled => ruff_python_formatter::PreviewMode::Enabled,
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
line_width: LineWidth::from(NonZeroU16::from(self.line_length.unwrap_or_default())),
|
||||
indent_style: IndentStyle::default(),
|
||||
quote_style: QuoteStyle::default(),
|
||||
magic_trailing_comma: MagicTrailingComma::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,12 +397,7 @@ impl Configuration {
|
||||
external: options.external,
|
||||
fix: options.fix,
|
||||
fix_only: options.fix_only,
|
||||
output_format: options.output_format.or_else(|| {
|
||||
options
|
||||
.format
|
||||
.as_ref()
|
||||
.and_then(FormatOrOutputFormat::as_output_format)
|
||||
}),
|
||||
output_format: options.output_format.or(options.format),
|
||||
force_exclude: options.force_exclude,
|
||||
ignore_init_module_imports: options.ignore_init_module_imports,
|
||||
include: options.include.map(|paths| {
|
||||
@@ -480,12 +461,6 @@ impl Configuration {
|
||||
pyflakes: options.pyflakes,
|
||||
pylint: options.pylint,
|
||||
pyupgrade: options.pyupgrade,
|
||||
|
||||
format: if let Some(FormatOrOutputFormat::Format(format)) = options.format {
|
||||
FormatConfiguration::from_options(format)?
|
||||
} else {
|
||||
FormatConfiguration::default()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -809,52 +784,6 @@ impl Configuration {
|
||||
pyflakes: self.pyflakes.combine(config.pyflakes),
|
||||
pylint: self.pylint.combine(config.pylint),
|
||||
pyupgrade: self.pyupgrade.combine(config.pyupgrade),
|
||||
|
||||
format: self.format.combine(config.format),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FormatConfiguration {
|
||||
pub preview: Option<PreviewMode>,
|
||||
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
|
||||
pub quote_style: Option<QuoteStyle>,
|
||||
|
||||
pub magic_trailing_comma: Option<MagicTrailingComma>,
|
||||
|
||||
pub line_ending: Option<LineEnding>,
|
||||
}
|
||||
|
||||
impl FormatConfiguration {
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn from_options(options: FormatOptions) -> Result<Self> {
|
||||
Ok(Self {
|
||||
preview: options.preview.map(PreviewMode::from),
|
||||
indent_style: options.indent_style,
|
||||
quote_style: options.quote_style,
|
||||
magic_trailing_comma: options.skip_magic_trailing_comma.map(|skip| {
|
||||
if skip {
|
||||
MagicTrailingComma::Ignore
|
||||
} else {
|
||||
MagicTrailingComma::Respect
|
||||
}
|
||||
}),
|
||||
line_ending: options.line_ending,
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
pub fn combine(self, other: Self) -> Self {
|
||||
Self {
|
||||
preview: self.preview.or(other.preview),
|
||||
indent_style: self.indent_style.or(other.indent_style),
|
||||
quote_style: self.quote_style.or(other.quote_style),
|
||||
magic_trailing_comma: self.magic_trailing_comma.or(other.magic_trailing_comma),
|
||||
line_ending: self.line_ending.or(other.line_ending),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ pub mod resolver;
|
||||
pub mod options_base;
|
||||
mod settings;
|
||||
|
||||
pub use settings::{FileResolverSettings, FormatterSettings, Settings};
|
||||
pub use settings::Settings;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
use regex::Regex;
|
||||
use ruff_formatter::IndentStyle;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::options_base::{OptionsMetadata, Visit};
|
||||
use ruff_linter::line_width::{LineLength, TabSize};
|
||||
use ruff_linter::rules::flake8_pytest_style::settings::SettingsError;
|
||||
use ruff_linter::rules::flake8_pytest_style::types;
|
||||
@@ -28,9 +19,11 @@ use ruff_linter::settings::types::{
|
||||
};
|
||||
use ruff_linter::{warn_user_once, RuleSelector};
|
||||
use ruff_macros::{CombineOptions, ConfigurationOptions};
|
||||
use ruff_python_formatter::QuoteStyle;
|
||||
|
||||
use crate::settings::LineEnding;
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeSet;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default, ConfigurationOptions, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
@@ -259,6 +252,17 @@ pub struct Options {
|
||||
)]
|
||||
pub fixable: Option<Vec<RuleSelector>>,
|
||||
|
||||
/// 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), `"gitlab"` (GitLab CI code quality report),
|
||||
/// `"pylint"` (Pylint text format) or `"azure"` (Azure Pipeline logging commands).
|
||||
///
|
||||
/// This option has been **deprecated** in favor of `output-format`
|
||||
/// to avoid ambiguity with Ruff's upcoming formatter.
|
||||
#[cfg_attr(feature = "schemars", schemars(skip))]
|
||||
pub format: Option<SerializationFormat>,
|
||||
|
||||
/// 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
|
||||
@@ -677,20 +681,6 @@ pub struct Options {
|
||||
#[option_group]
|
||||
pub pyupgrade: Option<PyUpgradeOptions>,
|
||||
|
||||
/// Options to configure the code formatting.
|
||||
///
|
||||
/// Previously:
|
||||
/// 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), `"gitlab"` (GitLab CI code quality report),
|
||||
/// `"pylint"` (Pylint text format) or `"azure"` (Azure Pipeline logging commands).
|
||||
///
|
||||
/// This option has been **deprecated** in favor of `output-format`
|
||||
/// to avoid ambiguity with Ruff's upcoming formatter.
|
||||
#[option_group]
|
||||
pub format: Option<FormatOrOutputFormat>,
|
||||
|
||||
// Tables are required to go last.
|
||||
/// A list of mappings from file pattern to rule codes or prefixes to
|
||||
/// exclude, when considering any matching files.
|
||||
@@ -2391,138 +2381,10 @@ impl PyUpgradeOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum FormatOrOutputFormat {
|
||||
Format(FormatOptions),
|
||||
OutputFormat(SerializationFormat),
|
||||
}
|
||||
|
||||
impl FormatOrOutputFormat {
|
||||
pub const fn as_output_format(&self) -> Option<SerializationFormat> {
|
||||
match self {
|
||||
FormatOrOutputFormat::Format(_) => None,
|
||||
FormatOrOutputFormat::OutputFormat(format) => Some(*format),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionsMetadata for FormatOrOutputFormat {
|
||||
fn record(visit: &mut dyn Visit) {
|
||||
FormatOptions::record(visit);
|
||||
}
|
||||
|
||||
fn documentation() -> Option<&'static str> {
|
||||
FormatOptions::documentation()
|
||||
}
|
||||
}
|
||||
|
||||
/// Experimental: Configures how `ruff format` formats your code.
|
||||
///
|
||||
/// Please provide feedback in [this discussion](https://github.com/astral-sh/ruff/discussions/7310).
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, Default, Serialize, Deserialize, ConfigurationOptions, CombineOptions,
|
||||
)]
|
||||
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct FormatOptions {
|
||||
/// Whether to enable the unstable preview style formatting.
|
||||
#[option(
|
||||
default = "false",
|
||||
value_type = "bool",
|
||||
example = r#"
|
||||
# Enable preview style formatting
|
||||
preview = true
|
||||
"#
|
||||
)]
|
||||
pub preview: Option<bool>,
|
||||
|
||||
/// Whether to use 4 spaces or hard tabs for indenting code.
|
||||
///
|
||||
/// Defaults to 4 spaces. We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them.
|
||||
#[option(
|
||||
default = "space",
|
||||
value_type = r#""space" | "tab""#,
|
||||
example = r#"
|
||||
# Use tabs instead of 4 space indentation
|
||||
indent-style = "tab"
|
||||
"#
|
||||
)]
|
||||
pub indent_style: Option<IndentStyle>,
|
||||
|
||||
/// Whether to prefer single `'` or double `"` quotes for strings and docstrings.
|
||||
///
|
||||
/// Ruff may deviate from this option if using the configured quotes would require more escaped quotes:
|
||||
///
|
||||
/// ```python
|
||||
/// a = "It's monday morning"
|
||||
/// b = "a string without any quotes"
|
||||
/// ```
|
||||
///
|
||||
/// Ruff leaves `a` unchanged when using `quote-style = "single"` because it is otherwise
|
||||
/// necessary to escape the `'` which leads to less readable code: `'It\'s monday morning'`.
|
||||
/// Ruff changes the quotes of `b` to use single quotes.
|
||||
#[option(
|
||||
default = r#"double"#,
|
||||
value_type = r#""double" | "single""#,
|
||||
example = r#"
|
||||
# Prefer single quotes over double quotes
|
||||
quote-style = "single"
|
||||
"#
|
||||
)]
|
||||
pub quote_style: Option<QuoteStyle>,
|
||||
|
||||
/// Ruff uses existing trailing commas as an indication that short lines should be left separate.
|
||||
/// If this option is set to `true`, the magic trailing comma is ignored.
|
||||
///
|
||||
/// For example, Ruff leaves the arguments separate even though
|
||||
/// collapsing the arguments to a single line doesn't exceed the line width if `skip-magic-trailing-comma = false`:
|
||||
///
|
||||
/// ```python
|
||||
/// # The arguments remain on separate lines because of the trailing comma after `b`
|
||||
/// def test(
|
||||
/// a,
|
||||
/// b,
|
||||
/// ): pass
|
||||
/// ```
|
||||
///
|
||||
/// Setting `skip-magic-trailing-comma = true` changes the formatting to:
|
||||
///
|
||||
/// ```python
|
||||
/// # The arguments remain on separate lines because of the trailing comma after `b`
|
||||
/// def test(a, b):
|
||||
/// pass
|
||||
/// ```
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
value_type = r#"bool"#,
|
||||
example = "skip-magic-trailing-comma = true"
|
||||
)]
|
||||
pub skip_magic_trailing_comma: Option<bool>,
|
||||
|
||||
/// The character Ruff uses at the end of a line.
|
||||
///
|
||||
/// * `lf`: Line endings will be converted to `\n`. The default line ending on Unix.
|
||||
/// * `cr-lf`: Line endings will be converted to `\r\n`. The default line ending on Windows.
|
||||
/// * `auto`: The newline style is detected automatically on a file per file basis. Files with mixed line endings will be converted to the first detected line ending. Defaults to `\n` for files that contain no line endings.
|
||||
/// * `native`: Line endings will be converted to `\n` on Unix and `\r\n` on Windows.
|
||||
#[option(
|
||||
default = r#"lf"#,
|
||||
value_type = r#""lf" | "crlf" | "auto" | "native""#,
|
||||
example = r#"
|
||||
# Automatically detect the line ending on a file per file basis.
|
||||
quote-style = "auto"
|
||||
"#
|
||||
)]
|
||||
pub line_ending: Option<LineEnding>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_linter::rules::flake8_self;
|
||||
|
||||
use crate::options::Flake8SelfOptions;
|
||||
use ruff_linter::rules::flake8_self;
|
||||
|
||||
#[test]
|
||||
fn flake8_self_options() {
|
||||
|
||||
@@ -1,308 +1,167 @@
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Visits [`OptionsMetadata`].
|
||||
///
|
||||
/// An instance of [`Visit`] represents the logic for inspecting an object's options metadata.
|
||||
pub trait Visit {
|
||||
/// Visits an [`OptionField`] value named `name`.
|
||||
fn record_field(&mut self, name: &str, field: OptionField);
|
||||
|
||||
/// Visits an [`OptionSet`] value named `name`.
|
||||
fn record_set(&mut self, name: &str, group: OptionSet);
|
||||
}
|
||||
|
||||
/// Returns metadata for its options.
|
||||
pub trait OptionsMetadata {
|
||||
/// Visits the options metadata of this object by calling `visit` for each option.
|
||||
fn record(visit: &mut dyn Visit);
|
||||
|
||||
fn documentation() -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the extracted metadata.
|
||||
fn metadata() -> OptionSet
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
OptionSet::of::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata of an option that can either be a [`OptionField`] or [`OptionSet`].
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum OptionEntry {
|
||||
/// A single option.
|
||||
Field(OptionField),
|
||||
|
||||
/// A set of options
|
||||
Set(OptionSet),
|
||||
Group(OptionGroup),
|
||||
}
|
||||
|
||||
impl Display for OptionEntry {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
OptionEntry::Set(set) => std::fmt::Display::fmt(set, f),
|
||||
OptionEntry::Field(field) => std::fmt::Display::fmt(&field, f),
|
||||
OptionEntry::Field(field) => field.fmt(f),
|
||||
OptionEntry::Group(group) => group.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of options.
|
||||
///
|
||||
/// It extracts the options by calling the [`OptionsMetadata::record`] of a type implementing
|
||||
/// [`OptionsMetadata`].
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct OptionSet {
|
||||
record: fn(&mut dyn Visit),
|
||||
doc: fn() -> Option<&'static str>,
|
||||
}
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct OptionGroup(&'static [(&'static str, OptionEntry)]);
|
||||
|
||||
impl OptionSet {
|
||||
pub fn of<T>() -> Self
|
||||
where
|
||||
T: OptionsMetadata + 'static,
|
||||
{
|
||||
Self {
|
||||
record: T::record,
|
||||
doc: T::documentation,
|
||||
}
|
||||
impl OptionGroup {
|
||||
pub const fn new(options: &'static [(&'static str, OptionEntry)]) -> Self {
|
||||
Self(options)
|
||||
}
|
||||
|
||||
/// Visits the options in this set by calling `visit` for each option.
|
||||
pub fn record(&self, visit: &mut dyn Visit) {
|
||||
let record = self.record;
|
||||
record(visit);
|
||||
pub fn iter(&self) -> std::slice::Iter<(&str, OptionEntry)> {
|
||||
self.into_iter()
|
||||
}
|
||||
|
||||
pub fn documentation(&self) -> Option<&'static str> {
|
||||
let documentation = self.doc;
|
||||
documentation()
|
||||
}
|
||||
|
||||
/// Returns `true` if this set has an option that resolves to `name`.
|
||||
///
|
||||
/// The name can be separated by `.` to find a nested option.
|
||||
/// Get an option entry by its fully-qualified name
|
||||
/// (e.g. `foo.bar` refers to the `bar` option in the `foo` group).
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ### Test for the existence of a child option
|
||||
/// ### Find a direct child
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
|
||||
/// # use ruff_workspace::options_base::{OptionGroup, OptionEntry, OptionField};
|
||||
///
|
||||
/// struct WithOptions;
|
||||
/// const options: [(&'static str, OptionEntry); 2] = [
|
||||
/// ("ignore_names", OptionEntry::Field(OptionField {
|
||||
/// doc: "ignore_doc",
|
||||
/// default: "ignore_default",
|
||||
/// value_type: "value_type",
|
||||
/// example: "ignore code"
|
||||
/// })),
|
||||
///
|
||||
/// impl OptionsMetadata for WithOptions {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||
/// doc: "Whether Ruff should respect the gitignore file",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// });
|
||||
/// ("global_names", OptionEntry::Field(OptionField {
|
||||
/// doc: "global_doc",
|
||||
/// default: "global_default",
|
||||
/// value_type: "value_type",
|
||||
/// example: "global code"
|
||||
/// }))
|
||||
/// ];
|
||||
///
|
||||
/// let group = OptionGroup::new(&options);
|
||||
///
|
||||
/// let ignore_names = group.get("ignore_names");
|
||||
///
|
||||
/// match ignore_names {
|
||||
/// None => panic!("Expect option 'ignore_names' to be Some"),
|
||||
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
||||
/// Some(OptionEntry::Field(field)) => {
|
||||
/// assert_eq!("ignore_doc", field.doc);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert!(WithOptions::metadata().has("ignore-git-ignore"));
|
||||
/// assert!(!WithOptions::metadata().has("does-not-exist"));
|
||||
/// assert_eq!(None, group.get("not_existing_option"));
|
||||
/// ```
|
||||
/// ### Test for the existence of a nested option
|
||||
///
|
||||
/// ### Find a nested options
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
|
||||
/// # use ruff_workspace::options_base::{OptionGroup, OptionEntry, OptionField};
|
||||
///
|
||||
/// struct Root;
|
||||
/// const ignore_options: [(&'static str, OptionEntry); 2] = [
|
||||
/// ("names", OptionEntry::Field(OptionField {
|
||||
/// doc: "ignore_name_doc",
|
||||
/// default: "ignore_name_default",
|
||||
/// value_type: "value_type",
|
||||
/// example: "ignore name code"
|
||||
/// })),
|
||||
///
|
||||
/// impl OptionsMetadata for Root {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||
/// doc: "Whether Ruff should respect the gitignore file",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// });
|
||||
/// ("extensions", OptionEntry::Field(OptionField {
|
||||
/// doc: "ignore_extensions_doc",
|
||||
/// default: "ignore_extensions_default",
|
||||
/// value_type: "value_type",
|
||||
/// example: "ignore extensions code"
|
||||
/// }))
|
||||
/// ];
|
||||
///
|
||||
/// visit.record_set("format", Nested::metadata());
|
||||
/// const options: [(&'static str, OptionEntry); 2] = [
|
||||
/// ("ignore", OptionEntry::Group(OptionGroup::new(&ignore_options))),
|
||||
///
|
||||
/// ("global_names", OptionEntry::Field(OptionField {
|
||||
/// doc: "global_doc",
|
||||
/// default: "global_default",
|
||||
/// value_type: "value_type",
|
||||
/// example: "global code"
|
||||
/// }))
|
||||
/// ];
|
||||
///
|
||||
/// let group = OptionGroup::new(&options);
|
||||
///
|
||||
/// let ignore_names = group.get("ignore.names");
|
||||
///
|
||||
/// match ignore_names {
|
||||
/// None => panic!("Expect option 'ignore.names' to be Some"),
|
||||
/// Some(OptionEntry::Group(group)) => panic!("Expected 'ignore_names' to be a field but found group {}", group),
|
||||
/// Some(OptionEntry::Field(field)) => {
|
||||
/// assert_eq!("ignore_name_doc", field.doc);
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct Nested;
|
||||
///
|
||||
/// impl OptionsMetadata for Nested {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("hard-tabs", OptionField {
|
||||
/// doc: "Use hard tabs for indentation and spaces for alignment.",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert!(Root::metadata().has("format.hard-tabs"));
|
||||
/// assert!(!Root::metadata().has("format.spaces"));
|
||||
/// assert!(!Root::metadata().has("lint.hard-tabs"));
|
||||
/// ```
|
||||
pub fn has(&self, name: &str) -> bool {
|
||||
self.find(name).is_some()
|
||||
}
|
||||
pub fn get(&self, name: &str) -> Option<&OptionEntry> {
|
||||
let mut parts = name.split('.').peekable();
|
||||
|
||||
/// Returns `Some` if this set has an option that resolves to `name` and `None` otherwise.
|
||||
///
|
||||
/// The name can be separated by `.` to find a nested option.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ### Find a child option
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
|
||||
///
|
||||
/// struct WithOptions;
|
||||
///
|
||||
/// static IGNORE_GIT_IGNORE: OptionField = OptionField {
|
||||
/// doc: "Whether Ruff should respect the gitignore file",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// };
|
||||
///
|
||||
/// impl OptionsMetadata for WithOptions {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("ignore-git-ignore", IGNORE_GIT_IGNORE.clone());
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(WithOptions::metadata().find("ignore-git-ignore"), Some(OptionEntry::Field(IGNORE_GIT_IGNORE.clone())));
|
||||
/// assert_eq!(WithOptions::metadata().find("does-not-exist"), None);
|
||||
/// ```
|
||||
/// ### Find a nested option
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
|
||||
///
|
||||
/// static HARD_TABS: OptionField = OptionField {
|
||||
/// doc: "Use hard tabs for indentation and spaces for alignment.",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// };
|
||||
///
|
||||
/// struct Root;
|
||||
///
|
||||
/// impl OptionsMetadata for Root {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("ignore-git-ignore", OptionField {
|
||||
/// doc: "Whether Ruff should respect the gitignore file",
|
||||
/// default: "false",
|
||||
/// value_type: "bool",
|
||||
/// example: "",
|
||||
/// });
|
||||
///
|
||||
/// visit.record_set("format", Nested::metadata());
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct Nested;
|
||||
///
|
||||
/// impl OptionsMetadata for Nested {
|
||||
/// fn record(visit: &mut dyn Visit) {
|
||||
/// visit.record_field("hard-tabs", HARD_TABS.clone());
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(Root::metadata().find("format.hard-tabs"), Some(OptionEntry::Field(HARD_TABS.clone())));
|
||||
/// assert_eq!(Root::metadata().find("format"), Some(OptionEntry::Set(Nested::metadata())));
|
||||
/// assert_eq!(Root::metadata().find("format.spaces"), None);
|
||||
/// assert_eq!(Root::metadata().find("lint.hard-tabs"), None);
|
||||
/// ```
|
||||
pub fn find(&self, name: &str) -> Option<OptionEntry> {
|
||||
struct FindOptionVisitor<'a> {
|
||||
option: Option<OptionEntry>,
|
||||
parts: std::str::Split<'a, char>,
|
||||
needle: &'a str,
|
||||
}
|
||||
let mut options = self.iter();
|
||||
|
||||
impl Visit for FindOptionVisitor<'_> {
|
||||
fn record_set(&mut self, name: &str, set: OptionSet) {
|
||||
if self.option.is_none() && name == self.needle {
|
||||
if let Some(next) = self.parts.next() {
|
||||
self.needle = next;
|
||||
set.record(self);
|
||||
} else {
|
||||
self.option = Some(OptionEntry::Set(set));
|
||||
}
|
||||
}
|
||||
}
|
||||
loop {
|
||||
let part = parts.next()?;
|
||||
|
||||
fn record_field(&mut self, name: &str, field: OptionField) {
|
||||
if self.option.is_none() && name == self.needle {
|
||||
if self.parts.next().is_none() {
|
||||
self.option = Some(OptionEntry::Field(field));
|
||||
}
|
||||
let (_, field) = options.find(|(name, _)| *name == part)?;
|
||||
|
||||
match (parts.peek(), field) {
|
||||
(None, field) => return Some(field),
|
||||
(Some(..), OptionEntry::Field(..)) => return None,
|
||||
(Some(..), OptionEntry::Group(group)) => {
|
||||
options = group.iter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut parts = name.split('.');
|
||||
impl<'a> IntoIterator for &'a OptionGroup {
|
||||
type IntoIter = std::slice::Iter<'a, (&'a str, OptionEntry)>;
|
||||
type Item = &'a (&'a str, OptionEntry);
|
||||
|
||||
if let Some(first) = parts.next() {
|
||||
let mut visitor = FindOptionVisitor {
|
||||
parts,
|
||||
needle: first,
|
||||
option: None,
|
||||
};
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
self.record(&mut visitor);
|
||||
visitor.option
|
||||
} else {
|
||||
None
|
||||
impl IntoIterator for OptionGroup {
|
||||
type IntoIter = std::slice::Iter<'static, (&'static str, OptionEntry)>;
|
||||
type Item = &'static (&'static str, OptionEntry);
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OptionGroup {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for (name, _) in self {
|
||||
writeln!(f, "{name}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Visitor that writes out the names of all fields and sets.
|
||||
struct DisplayVisitor<'fmt, 'buf> {
|
||||
f: &'fmt mut Formatter<'buf>,
|
||||
result: std::fmt::Result,
|
||||
}
|
||||
|
||||
impl<'fmt, 'buf> DisplayVisitor<'fmt, 'buf> {
|
||||
fn new(f: &'fmt mut Formatter<'buf>) -> Self {
|
||||
Self { f, result: Ok(()) }
|
||||
}
|
||||
|
||||
fn finish(self) -> std::fmt::Result {
|
||||
self.result
|
||||
}
|
||||
}
|
||||
|
||||
impl Visit for DisplayVisitor<'_, '_> {
|
||||
fn record_set(&mut self, name: &str, _: OptionSet) {
|
||||
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
|
||||
}
|
||||
|
||||
fn record_field(&mut self, name: &str, _: OptionField) {
|
||||
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for OptionSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut visitor = DisplayVisitor::new(f);
|
||||
self.record(&mut visitor);
|
||||
visitor.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for OptionSet {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct OptionField {
|
||||
pub doc: &'static str,
|
||||
pub default: &'static str,
|
||||
|
||||
@@ -17,7 +17,6 @@ use ruff_linter::packaging::is_package;
|
||||
use ruff_linter::{fs, warn_user_once};
|
||||
|
||||
use crate::configuration::Configuration;
|
||||
use crate::options::FormatOrOutputFormat;
|
||||
use crate::pyproject;
|
||||
use crate::pyproject::settings_toml;
|
||||
use crate::settings::Settings;
|
||||
@@ -221,8 +220,8 @@ fn resolve_configuration(
|
||||
let options = pyproject::load_options(&path)
|
||||
.map_err(|err| anyhow!("Failed to parse `{}`: {}", path.display(), err))?;
|
||||
|
||||
if matches!(options.format, Some(FormatOrOutputFormat::OutputFormat(_))) {
|
||||
warn_user_once!("The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `output-format` instead.");
|
||||
if options.format.is_some() {
|
||||
warn_user_once!("The option `format` has been deprecated to avoid ambiguity with Ruff's upcoming formatter. Use `format-output` instead.");
|
||||
}
|
||||
|
||||
let project_root = relativity.resolve(&path);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use path_absolutize::path_dedot;
|
||||
use ruff_cache::cache_dir;
|
||||
use ruff_formatter::{FormatOptions, IndentStyle, LineWidth};
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet, SerializationFormat};
|
||||
use ruff_linter::settings::LinterSettings;
|
||||
use ruff_macros::CacheKey;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_formatter::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
|
||||
use ruff_source_file::find_newline;
|
||||
use ruff_python_formatter::FormatterSettings;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, CacheKey)]
|
||||
@@ -105,88 +102,3 @@ impl FileResolverSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(CacheKey, Clone, Debug)]
|
||||
pub struct FormatterSettings {
|
||||
pub preview: PreviewMode,
|
||||
|
||||
pub line_width: LineWidth,
|
||||
|
||||
pub indent_style: IndentStyle,
|
||||
|
||||
pub quote_style: QuoteStyle,
|
||||
|
||||
pub magic_trailing_comma: MagicTrailingComma,
|
||||
|
||||
pub line_ending: LineEnding,
|
||||
}
|
||||
|
||||
impl FormatterSettings {
|
||||
pub fn to_format_options(&self, source_type: PySourceType, source: &str) -> PyFormatOptions {
|
||||
let line_ending = match self.line_ending {
|
||||
LineEnding::Lf => ruff_formatter::printer::LineEnding::LineFeed,
|
||||
LineEnding::CrLf => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
||||
#[cfg(target_os = "windows")]
|
||||
LineEnding::Native => ruff_formatter::printer::LineEnding::CarriageReturnLineFeed,
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
LineEnding::Native => ruff_formatter::printer::LineEnding::LineFeed,
|
||||
LineEnding::Auto => match find_newline(source) {
|
||||
Some((_, ruff_source_file::LineEnding::Lf)) => {
|
||||
ruff_formatter::printer::LineEnding::LineFeed
|
||||
}
|
||||
Some((_, ruff_source_file::LineEnding::CrLf)) => {
|
||||
ruff_formatter::printer::LineEnding::CarriageReturnLineFeed
|
||||
}
|
||||
Some((_, ruff_source_file::LineEnding::Cr)) => {
|
||||
ruff_formatter::printer::LineEnding::CarriageReturn
|
||||
}
|
||||
None => ruff_formatter::printer::LineEnding::LineFeed,
|
||||
},
|
||||
};
|
||||
|
||||
PyFormatOptions::from_source_type(source_type)
|
||||
.with_indent_style(self.indent_style)
|
||||
.with_quote_style(self.quote_style)
|
||||
.with_magic_trailing_comma(self.magic_trailing_comma)
|
||||
.with_preview(self.preview)
|
||||
.with_line_ending(line_ending)
|
||||
.with_line_width(self.line_width)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FormatterSettings {
|
||||
fn default() -> Self {
|
||||
let default_options = PyFormatOptions::default();
|
||||
|
||||
Self {
|
||||
preview: ruff_python_formatter::PreviewMode::Disabled,
|
||||
line_width: default_options.line_width(),
|
||||
line_ending: LineEnding::Lf,
|
||||
indent_style: default_options.indent_style(),
|
||||
quote_style: default_options.quote_style(),
|
||||
magic_trailing_comma: default_options.magic_trailing_comma(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Debug, Eq, PartialEq, Default, CacheKey, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum LineEnding {
|
||||
/// Line endings will be converted to `\n` as is common on Unix.
|
||||
#[default]
|
||||
Lf,
|
||||
|
||||
/// Line endings will be converted to `\r\n` as is common on Windows.
|
||||
CrLf,
|
||||
|
||||
/// The newline style is detected automatically on a file per file basis.
|
||||
/// Files with mixed line endings will be converted to the first detected line ending.
|
||||
/// Defaults to [`LineEnding::Lf`] for a files that contain no line endings.
|
||||
Auto,
|
||||
|
||||
/// Line endings will be converted to `\n` on Unix and `\r\n` on Windows.
|
||||
Native,
|
||||
}
|
||||
|
||||
14
docs/.gitignore
vendored
14
docs/.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
contributing.md
|
||||
index.md
|
||||
rules.md
|
||||
rules/
|
||||
settings.md
|
||||
*
|
||||
!assets
|
||||
!configuration.md
|
||||
!editor-integrations.md
|
||||
!faq.md
|
||||
!installation.md
|
||||
!requirements.txt
|
||||
!tutorial.md
|
||||
!usage.md
|
||||
|
||||
@@ -105,7 +105,7 @@ src = ["src"]
|
||||
|
||||
### Rule Selection
|
||||
|
||||
Ruff supports [over 700 lint rules](rules.md) split across over 50 built-in plugins, but
|
||||
Ruff supports [over 600 lint rules](rules.md) split across over 40 built-in plugins, but
|
||||
determining the right set of rules will depend on your project's needs: some rules may be too
|
||||
strict, some are framework-specific, and so on.
|
||||
|
||||
@@ -247,7 +247,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -23,7 +23,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -33,7 +33,7 @@ Or, to enable autofix:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [ --fix, --exit-non-zero-on-fix ]
|
||||
@@ -44,7 +44,7 @@ Or, to run the hook on Jupyter Notebooks too:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.0.290
|
||||
hooks:
|
||||
- id: ruff
|
||||
types_or: [python, pyi, jupyter]
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
|
||||
132
ruff.schema.json
generated
132
ruff.schema.json
generated
@@ -326,17 +326,6 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"format": {
|
||||
"description": "Options to configure the code formatting.\n\nPreviously: 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), `\"gitlab\"` (GitLab CI code quality report), `\"pylint\"` (Pylint text format) or `\"azure\"` (Azure Pipeline logging commands).\n\nThis option has been **deprecated** in favor of `output-format` to avoid ambiguity with Ruff's upcoming formatter.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FormatOrOutputFormat"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ignore": {
|
||||
"description": "A list of rule codes or prefixes to ignore. Prefixes can specify exact rules (like `F841`), entire categories (like `F`), or anything in between.\n\nWhen breaking ties between enabled and disabled rules (via `select` and `ignore`, respectively), more specific prefixes override less specific prefixes.",
|
||||
"type": [
|
||||
@@ -1162,70 +1151,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FormatOptions": {
|
||||
"description": "Experimental: Configures how `ruff format` formats your code.\n\nPlease provide feedback in [this discussion](https://github.com/astral-sh/ruff/discussions/7310).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"indent-style": {
|
||||
"description": "Whether to use 4 spaces or hard tabs for indenting code.\n\nDefaults to 4 spaces. We care about accessibility; if you do not need tabs for accessibility, we do not recommend you use them.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/IndentStyle"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"line-ending": {
|
||||
"description": "The character Ruff uses at the end of a line.\n\n* `lf`: Line endings will be converted to `\\n`. The default line ending on Unix. * `cr-lf`: Line endings will be converted to `\\r\\n`. The default line ending on Windows. * `auto`: The newline style is detected automatically on a file per file basis. Files with mixed line endings will be converted to the first detected line ending. Defaults to `\\n` for files that contain no line endings. * `native`: Line endings will be converted to `\\n` on Unix and `\\r\\n` on Windows.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LineEnding"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"description": "Whether to enable the unstable preview style formatting.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"quote-style": {
|
||||
"description": "Whether to prefer single `'` or double `\"` quotes for strings and docstrings.\n\nRuff may deviate from this option if using the configured quotes would require more escaped quotes:\n\n```python a = \"It's monday morning\" b = \"a string without any quotes\" ```\n\nRuff leaves `a` unchanged when using `quote-style = \"single\"` because it is otherwise necessary to escape the `'` which leads to less readable code: `'It\\'s monday morning'`. Ruff changes the quotes of `b` to use single quotes.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/QuoteStyle"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"skip-magic-trailing-comma": {
|
||||
"description": "Ruff uses existing trailing commas as an indication that short lines should be left separate. If this option is set to `true`, the magic trailing comma is ignored.\n\nFor example, Ruff leaves the arguments separate even though collapsing the arguments to a single line doesn't exceed the line width if `skip-magic-trailing-comma = false`:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test( a, b, ): pass ```\n\nSetting `skip-magic-trailing-comma = true` changes the formatting to:\n\n```python # The arguments remain on separate lines because of the trailing comma after `b` def test(a, b): pass ```",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"FormatOrOutputFormat": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/FormatOptions"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/SerializationFormat"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ImportSection": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -1246,24 +1171,6 @@
|
||||
"local-folder"
|
||||
]
|
||||
},
|
||||
"IndentStyle": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Use tabs to indent code.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"tab"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Use [`IndentWidth`] spaces to indent code.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"space"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"IsortOptions": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1497,38 +1404,6 @@
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"LineEnding": {
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Line endings will be converted to `\\n` as is common on Unix.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"lf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Line endings will be converted to `\\r\\n` as is common on Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cr-lf"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "The newline style is detected automatically on a file per file basis. Files with mixed line endings will be converted to the first detected line ending. Defaults to [`LineEnding::Lf`] for a files that contain no line endings.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Line endings will be converted to `\\n` on Unix and `\\r\\n` on Windows.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"native"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"LineLength": {
|
||||
"description": "The length of a line of text that is considered too long.\n\nThe allowed range of values is 1..=320",
|
||||
"type": "integer",
|
||||
@@ -1798,13 +1673,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"QuoteStyle": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"double"
|
||||
]
|
||||
},
|
||||
"RelativeImportsOrder": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user