Compare commits
6 Commits
v0.0.291
...
range-form
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de239ace74 | ||
|
|
53b5121f30 | ||
|
|
be93983e8e | ||
|
|
a1239b8f2d | ||
|
|
d1b12acb3c | ||
|
|
1aabf59f77 |
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
|
||||
|
||||
14
Cargo.lock
generated
14
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",
|
||||
@@ -2051,7 +2051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2187,7 +2187,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2322,6 +2322,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"clap",
|
||||
"countme",
|
||||
"indoc",
|
||||
"insta",
|
||||
"itertools 0.11.0",
|
||||
"memchr",
|
||||
@@ -2336,7 +2337,6 @@ dependencies = [
|
||||
"ruff_source_file",
|
||||
"ruff_text_size",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar",
|
||||
@@ -2524,9 +2524,7 @@ dependencies = [
|
||||
"ruff_formatter",
|
||||
"ruff_linter",
|
||||
"ruff_macros",
|
||||
"ruff_python_ast",
|
||||
"ruff_python_formatter",
|
||||
"ruff_source_file",
|
||||
"rustc-hash",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -4,7 +4,7 @@ use ruff_benchmark::criterion::{
|
||||
criterion_group, criterion_main, BenchmarkId, Criterion, Throughput,
|
||||
};
|
||||
use ruff_benchmark::{TestCase, TestFile, TestFileDownloadError};
|
||||
use ruff_python_formatter::{format_node, PyFormatOptions};
|
||||
use ruff_python_formatter::{format_module_ast, PyFormatOptions};
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
@@ -65,13 +65,14 @@ fn benchmark_formatter(criterion: &mut Criterion) {
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
|
||||
// Parse the AST.
|
||||
let python_ast = parse_tokens(tokens, Mode::Module, "<filename>")
|
||||
let module = parse_tokens(tokens, Mode::Module, "<filename>")
|
||||
.expect("Input to be a valid python program");
|
||||
|
||||
b.iter(|| {
|
||||
let options = PyFormatOptions::from_extension(Path::new(case.name()));
|
||||
let formatted = format_node(&python_ast, &comment_ranges, case.code(), options)
|
||||
.expect("Formatting to succeed");
|
||||
let formatted =
|
||||
format_module_ast(&module, &comment_ranges, case.code(), options)
|
||||
.expect("Formatting to succeed");
|
||||
|
||||
formatted.print().expect("Printing to succeed")
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.291"
|
||||
version = "0.0.290"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { 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"])
|
||||
@@ -11,6 +11,7 @@ use ruff_linter::settings::types::{
|
||||
FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat,
|
||||
};
|
||||
use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser};
|
||||
use ruff_python_formatter::LspRowColumn;
|
||||
use ruff_workspace::configuration::{Configuration, RuleSelection};
|
||||
use ruff_workspace::resolver::ConfigurationTransformer;
|
||||
|
||||
@@ -395,6 +396,14 @@ pub struct FormatCommand {
|
||||
preview: bool,
|
||||
#[clap(long, overrides_with("preview"), hide = true)]
|
||||
no_preview: bool,
|
||||
/// Range formatting start: Zero-indexed row and zero-indexed char-based column separated by
|
||||
/// colon, e.g. `1:2`
|
||||
#[clap(long)]
|
||||
pub start: Option<LspRowColumn>,
|
||||
/// Range formatting end: Zero-indexed row and zero-indexed char-based column separated by
|
||||
/// colon, e.g. `3:4`
|
||||
#[clap(long)]
|
||||
pub end: Option<LspRowColumn>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
|
||||
@@ -516,6 +525,8 @@ impl FormatCommand {
|
||||
files: self.files,
|
||||
isolated: self.isolated,
|
||||
stdin_filename: self.stdin_filename,
|
||||
start: self.start,
|
||||
end: self.end,
|
||||
},
|
||||
CliOverrides {
|
||||
line_length: self.line_length,
|
||||
@@ -572,6 +583,8 @@ pub struct FormatArguments {
|
||||
pub files: Vec<PathBuf>,
|
||||
pub isolated: bool,
|
||||
pub stdin_filename: Option<PathBuf>,
|
||||
pub start: Option<LspRowColumn>,
|
||||
pub end: Option<LspRowColumn>,
|
||||
}
|
||||
|
||||
/// CLI settings that function as configuration overrides.
|
||||
|
||||
@@ -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_source, 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,17 +139,21 @@ 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 formatted = format_module(&unformatted, options)
|
||||
let options = options.with_line_ending(line_ending);
|
||||
|
||||
let formatted = format_module_source(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(Some(path.to_path_buf()), err))?;
|
||||
|
||||
let formatted = formatted.as_code();
|
||||
|
||||
@@ -5,9 +5,10 @@ use anyhow::Result;
|
||||
use log::warn;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_formatter::format_module;
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, format_module_source_range, LspRowColumn, 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 +39,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, cli.start, cli.end) {
|
||||
Ok(result) => match mode {
|
||||
FormatMode::Write => Ok(ExitStatus::Success),
|
||||
FormatMode::Check => {
|
||||
@@ -59,30 +65,32 @@ 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,
|
||||
start: Option<LspRowColumn>,
|
||||
end: Option<LspRowColumn>,
|
||||
) -> 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))?;
|
||||
}
|
||||
let formatted = if start.is_some() || end.is_some() {
|
||||
let formatted = format_module_source_range(&unformatted, options, start, end)
|
||||
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
|
||||
formatted
|
||||
} else {
|
||||
let formatted = format_module_source(&unformatted, options)
|
||||
.map_err(|err| FormatCommandError::FormatModule(path.map(Path::to_path_buf), err))?;
|
||||
let formatted = formatted.as_code();
|
||||
formatted.to_string()
|
||||
};
|
||||
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,7 +28,7 @@ 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 }
|
||||
|
||||
@@ -34,7 +34,7 @@ use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
use ruff_python_formatter::{
|
||||
format_module, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, Resolver};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -799,7 +800,7 @@ fn format_dev_file(
|
||||
let content = fs::read_to_string(input_path)?;
|
||||
#[cfg(not(debug_assertions))]
|
||||
let start = Instant::now();
|
||||
let printed = match format_module(&content, options.clone()) {
|
||||
let printed = match format_module_source(&content, options.clone()) {
|
||||
Ok(printed) => printed,
|
||||
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
||||
return Err(CheckFileError::SyntaxErrorInInput(err));
|
||||
@@ -826,7 +827,7 @@ fn format_dev_file(
|
||||
}
|
||||
|
||||
if stability_check {
|
||||
let reformatted = match format_module(formatted, options) {
|
||||
let reformatted = match format_module_source(formatted, options) {
|
||||
Ok(reformatted) => reformatted,
|
||||
Err(err @ (FormatModuleError::LexError(_) | FormatModuleError::ParseError(_))) => {
|
||||
return Err(CheckFileError::SyntaxErrorInOutput {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use crate::prelude::TagKind;
|
||||
use crate::GroupId;
|
||||
use ruff_text_size::TextRange;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
@@ -12,7 +11,7 @@ pub enum FormatError {
|
||||
SyntaxError { message: &'static str },
|
||||
/// In case range formatting failed because the provided range was larger
|
||||
/// than the formatted syntax tree
|
||||
RangeError { input: TextRange, tree: TextRange },
|
||||
RangeError { row: usize, col: usize },
|
||||
|
||||
/// In case printing the document failed because it has an invalid structure.
|
||||
InvalidDocument(InvalidDocumentError),
|
||||
@@ -32,9 +31,9 @@ impl std::fmt::Display for FormatError {
|
||||
FormatError::SyntaxError {message} => {
|
||||
std::write!(fmt, "syntax error: {message}")
|
||||
},
|
||||
FormatError::RangeError { input, tree } => std::write!(
|
||||
FormatError::RangeError { row, col } => std::write!(
|
||||
fmt,
|
||||
"formatting range {input:?} is larger than syntax tree {tree:?}"
|
||||
"formatting range {row}:{col} is not a valid index"
|
||||
),
|
||||
FormatError::InvalidDocument(error) => std::write!(fmt, "Invalid document: {error}\n\n This is an internal Rome error. Please report if necessary."),
|
||||
FormatError::PoorLayout => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -334,7 +334,7 @@ macro_rules! best_fitting {
|
||||
$crate::BestFitting::from_arguments_unchecked($crate::format_args!($least_expanded, $($tail),+))
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -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 }
|
||||
@@ -44,6 +43,7 @@ insta = { workspace = true, features = ["glob"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
similar = { workspace = true }
|
||||
indoc = "2.0.4"
|
||||
|
||||
[[test]]
|
||||
name = "ruff_python_formatter_fixtures"
|
||||
@@ -53,5 +53,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:
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{format_err, Context, Result};
|
||||
use clap::{command, Parser, ValueEnum};
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
use ruff_text_size::Ranged;
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::comments::collect_comments;
|
||||
use crate::{format_node, PyFormatOptions};
|
||||
use crate::{format_module_ast, format_module_range, PyFormatOptions};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
pub enum Emit {
|
||||
@@ -37,38 +37,46 @@ pub struct Cli {
|
||||
pub print_ir: bool,
|
||||
#[clap(long)]
|
||||
pub print_comments: bool,
|
||||
/// byte offset for range formatting
|
||||
#[clap(long)]
|
||||
pub start: Option<u32>,
|
||||
/// byte offset for range formatting
|
||||
#[clap(long)]
|
||||
pub end: Option<u32>,
|
||||
}
|
||||
|
||||
pub fn format_and_debug_print(input: &str, cli: &Cli, source_type: &Path) -> Result<String> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
pub fn format_and_debug_print(source: &str, cli: &Cli, source_type: &Path) -> Result<String> {
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source)
|
||||
.map_err(|err| format_err!("Source contains syntax errors {err:?}"))?;
|
||||
let module =
|
||||
parse_ok_tokens(tokens, Mode::Module, "<filename>").context("Syntax error in input")?;
|
||||
let options = PyFormatOptions::from_extension(source_type);
|
||||
let source_code = SourceCode::new(source);
|
||||
let locator = Locator::new(source);
|
||||
|
||||
for result in lex(input, Mode::Module) {
|
||||
let (token, range) = match result {
|
||||
Ok((token, range)) => (token, range),
|
||||
Err(err) => bail!("Source contains syntax errors {err:?}"),
|
||||
};
|
||||
|
||||
comment_ranges.visit_token(&token, range);
|
||||
tokens.push(Ok((token, range)));
|
||||
if cli.start.is_some() || cli.end.is_some() {
|
||||
let range = TextRange::new(
|
||||
cli.start.map(TextSize::new).unwrap_or_default(),
|
||||
cli.end.map(TextSize::new).unwrap_or(source.text_len()),
|
||||
);
|
||||
return Ok(format_module_range(
|
||||
&module,
|
||||
&comment_ranges,
|
||||
source,
|
||||
options,
|
||||
&locator,
|
||||
range,
|
||||
)?);
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
|
||||
// Parse the AST.
|
||||
let python_ast =
|
||||
parse_tokens(tokens, Mode::Module, "<filename>").context("Syntax error in input")?;
|
||||
|
||||
let options = PyFormatOptions::from_extension(source_type);
|
||||
let formatted = format_node(&python_ast, &comment_ranges, input, options)
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options)
|
||||
.context("Failed to format node")?;
|
||||
if cli.print_ir {
|
||||
println!("{}", formatted.document().display(SourceCode::new(input)));
|
||||
println!("{}", formatted.document().display(source_code));
|
||||
}
|
||||
if cli.print_comments {
|
||||
// Print preceding, following and enclosing nodes
|
||||
let source_code = SourceCode::new(input);
|
||||
let decorated_comments = collect_comments(&python_ast, source_code, &comment_ranges);
|
||||
let decorated_comments = collect_comments(&module, source_code, &comment_ranges);
|
||||
if !decorated_comments.is_empty() {
|
||||
println!("# Comment decoration: Range, Preceding, Following, Enclosing, Comment");
|
||||
}
|
||||
@@ -86,13 +94,10 @@ pub fn format_and_debug_print(input: &str, cli: &Cli, source_type: &Path) -> Res
|
||||
comment.enclosing_node().kind(),
|
||||
comment.enclosing_node().range()
|
||||
),
|
||||
comment.slice().text(SourceCode::new(input)),
|
||||
comment.slice().text(source_code),
|
||||
);
|
||||
}
|
||||
println!(
|
||||
"{:#?}",
|
||||
formatted.context().comments().debug(SourceCode::new(input))
|
||||
);
|
||||
println!("{:#?}", formatted.context().comments().debug(source_code));
|
||||
}
|
||||
Ok(formatted
|
||||
.print()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -549,9 +549,9 @@ mod tests {
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::Mod;
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
|
||||
use crate::comments::Comments;
|
||||
@@ -563,19 +563,11 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<'a> CommentsTestCase<'a> {
|
||||
fn from_code(code: &'a str) -> Self {
|
||||
let source_code = SourceCode::new(code);
|
||||
let tokens: Vec<_> = lex(code, Mode::Module).collect();
|
||||
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for (token, range) in tokens.iter().flatten() {
|
||||
comment_ranges.visit_token(token, *range);
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
|
||||
let parsed = parse_tokens(tokens, Mode::Module, "test.py")
|
||||
fn from_code(source: &'a str) -> Self {
|
||||
let source_code = SourceCode::new(source);
|
||||
let (tokens, comment_ranges) =
|
||||
tokens_and_ranges(source).expect("Expect source to be valid Python");
|
||||
let parsed = parse_ok_tokens(tokens, Mode::Module, "test.py")
|
||||
.expect("Expect source to be valid Python");
|
||||
|
||||
CommentsTestCase {
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
use std::iter;
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use tracing::Level;
|
||||
use tracing::{warn, Level};
|
||||
|
||||
use ruff_formatter::prelude::*;
|
||||
use ruff_formatter::{format, FormatError, Formatted, PrintError, Printed, SourceCode};
|
||||
use ruff_python_ast::node::AstNode;
|
||||
use ruff_python_ast::Mod;
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::{lex, LexicalError};
|
||||
use ruff_python_parser::{parse_tokens, Mode, ParseError};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_python_ast::{
|
||||
Mod, Stmt, StmtClassDef, StmtFor, StmtFunctionDef, StmtIf, StmtWhile, StmtWith,
|
||||
};
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
use ruff_python_parser::lexer::LexicalError;
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode, ParseError};
|
||||
use ruff_python_trivia::{is_python_whitespace, CommentRanges};
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::comments::{
|
||||
dangling_comments, leading_comments, trailing_comments, Comments, SourceComment,
|
||||
};
|
||||
pub use crate::context::PyFormatContext;
|
||||
pub use crate::options::{MagicTrailingComma, PreviewMode, PyFormatOptions, QuoteStyle};
|
||||
use crate::statement::suite::SuiteKind;
|
||||
use crate::verbatim::suppressed_node;
|
||||
pub use settings::FormatterSettings;
|
||||
|
||||
pub(crate) mod builders;
|
||||
pub mod cli;
|
||||
@@ -29,6 +36,8 @@ mod options;
|
||||
pub(crate) mod other;
|
||||
pub(crate) mod pattern;
|
||||
mod prelude;
|
||||
mod range_formatting;
|
||||
mod settings;
|
||||
pub(crate) mod statement;
|
||||
pub(crate) mod type_param;
|
||||
mod verbatim;
|
||||
@@ -121,61 +130,260 @@ impl From<ParseError> for FormatModuleError {
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = Level::TRACE, skip_all)]
|
||||
pub fn format_module(
|
||||
contents: &str,
|
||||
#[tracing::instrument(name = "format", level = Level::TRACE, skip_all)]
|
||||
pub fn format_module_source(
|
||||
source: &str,
|
||||
options: PyFormatOptions,
|
||||
) -> Result<Printed, FormatModuleError> {
|
||||
// Tokenize once
|
||||
let mut tokens = Vec::new();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for result in lex(contents, Mode::Module) {
|
||||
let (token, range) = result?;
|
||||
|
||||
comment_ranges.visit_token(&token, range);
|
||||
tokens.push(Ok((token, range)));
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
|
||||
// Parse the AST.
|
||||
let python_ast = parse_tokens(tokens, Mode::Module, "<filename>")?;
|
||||
|
||||
let formatted = format_node(&python_ast, &comment_ranges, contents, options)?;
|
||||
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source)?;
|
||||
let module = parse_ok_tokens(tokens, Mode::Module, "<filename>")?;
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options)?;
|
||||
Ok(formatted.print()?)
|
||||
}
|
||||
|
||||
pub fn format_node<'a>(
|
||||
root: &'a Mod,
|
||||
/// Range formatting coordinate: Zero-indexed row and zero-indexed char-based column separated by
|
||||
/// colon, e.g. `1:2`.
|
||||
///
|
||||
/// See [`Locator::convert_row_and_column`] for details on the semantics.
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct LspRowColumn {
|
||||
row: usize,
|
||||
col: usize,
|
||||
}
|
||||
|
||||
impl FromStr for LspRowColumn {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let Some((row, col)) = s.split_once(':') else {
|
||||
return Err("Coordinate is missing a colon, the format is `<row>:<column>`");
|
||||
};
|
||||
|
||||
Ok(LspRowColumn {
|
||||
row: row.parse().map_err(|_| "row must be a number")?,
|
||||
col: col.parse().map_err(|_| "col must be a number")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[tracing::instrument(name = "format", level = Level::TRACE, skip_all)]
|
||||
pub fn format_module_source_range(
|
||||
source: &str,
|
||||
options: PyFormatOptions,
|
||||
start: Option<LspRowColumn>,
|
||||
end: Option<LspRowColumn>,
|
||||
) -> Result<String, FormatModuleError> {
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source)?;
|
||||
let module = parse_ok_tokens(tokens, Mode::Module, "<filename>")?;
|
||||
let locator = Locator::new(source);
|
||||
|
||||
let start = if let Some(start) = start {
|
||||
locator
|
||||
.convert_row_and_column(start.row, start.col)
|
||||
.ok_or(FormatError::RangeError {
|
||||
row: start.row,
|
||||
col: start.col,
|
||||
})?
|
||||
} else {
|
||||
TextSize::default()
|
||||
};
|
||||
let end = if let Some(end) = end {
|
||||
locator
|
||||
.convert_row_and_column(end.row, end.col)
|
||||
.ok_or(FormatError::RangeError {
|
||||
row: end.row,
|
||||
col: end.col,
|
||||
})?
|
||||
} else {
|
||||
source.text_len()
|
||||
};
|
||||
|
||||
let formatted = format_module_range(
|
||||
&module,
|
||||
&comment_ranges,
|
||||
source,
|
||||
options,
|
||||
&locator,
|
||||
TextRange::new(start, end),
|
||||
)?;
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
pub fn format_module_ast<'a>(
|
||||
module: &'a Mod,
|
||||
comment_ranges: &'a CommentRanges,
|
||||
source: &'a str,
|
||||
options: PyFormatOptions,
|
||||
) -> FormatResult<Formatted<PyFormatContext<'a>>> {
|
||||
let comments = Comments::from_ast(root, SourceCode::new(source), comment_ranges);
|
||||
|
||||
let source_code = SourceCode::new(source);
|
||||
let comments = Comments::from_ast(module, source_code, comment_ranges);
|
||||
let locator = Locator::new(source);
|
||||
|
||||
let formatted = format!(
|
||||
PyFormatContext::new(options, locator.contents(), comments),
|
||||
[root.format()]
|
||||
[module.format()]
|
||||
)?;
|
||||
formatted
|
||||
.context()
|
||||
.comments()
|
||||
.assert_all_formatted(SourceCode::new(source));
|
||||
.assert_all_formatted(source_code);
|
||||
Ok(formatted)
|
||||
}
|
||||
|
||||
/// Public function for generating a printable string of the debug comments.
|
||||
pub fn pretty_comments(root: &Mod, comment_ranges: &CommentRanges, source: &str) -> String {
|
||||
let comments = Comments::from_ast(root, SourceCode::new(source), comment_ranges);
|
||||
/// Is range inside the body of a node, if we consider the whitespace surrounding the suite as part
|
||||
/// of the body?
|
||||
///
|
||||
/// TODO: Handle leading comments on the first statement
|
||||
fn range_in_body(suite: &[Stmt], range: TextRange, source: &str) -> bool {
|
||||
let suite_start = suite.first().unwrap().start();
|
||||
let suite_end = suite.last().unwrap().end();
|
||||
|
||||
std::format!(
|
||||
"{comments:#?}",
|
||||
comments = comments.debug(SourceCode::new(source))
|
||||
)
|
||||
if range.start() < suite_start
|
||||
// Extend the range include all whitespace prior to the first statement
|
||||
&& !source[TextRange::new(range.start(), suite_start)]
|
||||
.chars()
|
||||
.all(|c| is_python_whitespace(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if range.end() > suite_end
|
||||
// Extend the range include all whitespace after to the last statement
|
||||
&& !source[TextRange::new(suite_end,range.end())]
|
||||
.chars()
|
||||
.all(|c| is_python_whitespace(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn format_module_range<'a>(
|
||||
module: &'a Mod,
|
||||
comment_ranges: &'a CommentRanges,
|
||||
source: &'a str,
|
||||
options: PyFormatOptions,
|
||||
locator: &Locator<'a>,
|
||||
range: TextRange,
|
||||
) -> FormatResult<String> {
|
||||
let comments = Comments::from_ast(&module, SourceCode::new(source), &comment_ranges);
|
||||
|
||||
let Mod::Module(module_inner) = &module else {
|
||||
panic!("That's not a module");
|
||||
};
|
||||
|
||||
// TODO: Move this to LspRowColumn? we first count chars to then discard that anyway
|
||||
// Consider someone wanted to format `print(i); print(j)`. This wouldn't work indent-wise, so
|
||||
// we always do whole lines instead which means we can count indentation normally
|
||||
// ```python
|
||||
// if True:
|
||||
// for i in range(10): j=i+1; print(i); print(j)
|
||||
// ```
|
||||
let range = TextRange::new(
|
||||
locator.line_start(range.start()),
|
||||
locator.line_end(range.end()),
|
||||
);
|
||||
|
||||
// ```
|
||||
// a = 1; b = 2; c = 3; d = 4; e = 5
|
||||
// ^ b end ^ d start
|
||||
// ^^^^^^^^^^^^^^^ range
|
||||
// ^ range start ^ range end
|
||||
// ```
|
||||
// TODO: If it goes beyond the end of the last stmt or before start, do we need to format
|
||||
// the parent?
|
||||
let mut parent_body: &[Stmt] = module_inner.body.as_slice();
|
||||
let mut in_range;
|
||||
|
||||
// TODO: Allow partial inclusions, e.g.
|
||||
// ```python
|
||||
// not_formatted = 0
|
||||
// start = 1
|
||||
// if cond_formatted:
|
||||
// last_formatted = 2
|
||||
// not_formatted_anymore = 3
|
||||
// ```
|
||||
// prob a slice and an optional trailing arg
|
||||
let in_range = loop {
|
||||
let start = parent_body.partition_point(|child| child.end() < range.start());
|
||||
let end = parent_body.partition_point(|child| child.start() < range.end());
|
||||
in_range = &parent_body[start..end];
|
||||
|
||||
let [single_stmt] = in_range else {
|
||||
break in_range;
|
||||
};
|
||||
|
||||
match single_stmt {
|
||||
Stmt::For(StmtFor { body, .. })
|
||||
| Stmt::While(StmtWhile { body, .. })
|
||||
| Stmt::With(StmtWith { body, .. })
|
||||
| Stmt::FunctionDef(StmtFunctionDef { body, .. })
|
||||
| Stmt::ClassDef(StmtClassDef { body, .. }) => {
|
||||
// We need to format the header or a trailing comment
|
||||
// TODO: ignore trivia
|
||||
if range_in_body(body, range, source) {
|
||||
break in_range;
|
||||
} else {
|
||||
parent_body = &body;
|
||||
}
|
||||
}
|
||||
Stmt::If(StmtIf {
|
||||
body,
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
let if_all_end = TextRange::new(
|
||||
range.start(),
|
||||
elif_else_clauses
|
||||
.last()
|
||||
.map(|clause| clause.body.last().unwrap().end())
|
||||
.unwrap_or(body.last().unwrap().end()),
|
||||
);
|
||||
if !range_in_body(body, if_all_end, source) {
|
||||
break in_range;
|
||||
} else if let Some(body) = iter::once(body)
|
||||
.chain(elif_else_clauses.iter().map(|clause| &clause.body))
|
||||
.find(|body| range_in_body(body, range, source))
|
||||
{
|
||||
parent_body = &body;
|
||||
} else {
|
||||
break in_range;
|
||||
}
|
||||
}
|
||||
// | Stmt::StmtTry(ast::StmtTry { body, .. })
|
||||
// | Stmt::ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler { body, .. })
|
||||
// | Stmt::ElifElseClause(ast::ElifElseClause { body, .. }) => &body,
|
||||
// match
|
||||
_ => break in_range,
|
||||
}
|
||||
};
|
||||
|
||||
let (Some(first), Some(last)) = (in_range.first(), in_range.last()) else {
|
||||
// TODO: Use tracing again https://github.com/tokio-rs/tracing/issues/2721
|
||||
// TODO: Forward this to something proper
|
||||
eprintln!("The formatting range contains no statements");
|
||||
return Ok(source.to_string());
|
||||
};
|
||||
|
||||
let mut buffer = source[TextRange::up_to(first.start())].to_string();
|
||||
|
||||
let formatted: Formatted<PyFormatContext> = format!(
|
||||
PyFormatContext::new(options.clone(), locator.contents(), comments),
|
||||
// TODO: Make suite formatting accept slices
|
||||
[in_range.to_vec().format().with_options(SuiteKind::TopLevel)]
|
||||
)?;
|
||||
//println!("{}", formatted.document().display(SourceCode::new(source)));
|
||||
// TODO: Make the printer use the buffer instead
|
||||
buffer += formatted.print_with_indent(1)?.as_code();
|
||||
buffer += &source[TextRange::new(last.end(), source.text_len())];
|
||||
return Ok(buffer.to_string());
|
||||
}
|
||||
|
||||
/// Public function for generating a printable string of the debug comments.
|
||||
pub fn pretty_comments(module: &Mod, comment_ranges: &CommentRanges, source: &str) -> String {
|
||||
let source_code = SourceCode::new(source);
|
||||
let comments = Comments::from_ast(module, source_code, comment_ranges);
|
||||
|
||||
std::format!("{comments:#?}", comments = comments.debug(source_code))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -185,11 +393,11 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use ruff_python_index::CommentRangesBuilder;
|
||||
use ruff_python_parser::lexer::lex;
|
||||
use ruff_python_parser::{parse_tokens, Mode};
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
|
||||
use crate::{format_module, format_node, PyFormatOptions};
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
|
||||
use crate::{format_module_ast, format_module_source, PyFormatOptions};
|
||||
|
||||
/// Very basic test intentionally kept very similar to the CLI
|
||||
#[test]
|
||||
@@ -205,7 +413,7 @@ if True:
|
||||
pass
|
||||
# trailing
|
||||
"#;
|
||||
let actual = format_module(input, PyFormatOptions::default())?
|
||||
let actual = format_module_source(input, PyFormatOptions::default())?
|
||||
.as_code()
|
||||
.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
@@ -216,7 +424,7 @@ if True:
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn quick_test() {
|
||||
let src = r#"
|
||||
let source = r#"
|
||||
def main() -> None:
|
||||
if True:
|
||||
some_very_long_variable_name_abcdefghijk = Foo()
|
||||
@@ -226,23 +434,13 @@ def main() -> None:
|
||||
]
|
||||
|
||||
"#;
|
||||
// Tokenize once
|
||||
let mut tokens = Vec::new();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for result in lex(src, Mode::Module) {
|
||||
let (token, range) = result.unwrap();
|
||||
comment_ranges.visit_token(&token, range);
|
||||
tokens.push(Ok((token, range)));
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source).unwrap();
|
||||
|
||||
// Parse the AST.
|
||||
let source_path = "code_inline.py";
|
||||
let python_ast = parse_tokens(tokens, Mode::Module, source_path).unwrap();
|
||||
let module = parse_ok_tokens(tokens, Mode::Module, source_path).unwrap();
|
||||
let options = PyFormatOptions::from_extension(Path::new(source_path));
|
||||
let formatted = format_node(&python_ast, &comment_ranges, src, options).unwrap();
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options).unwrap();
|
||||
|
||||
// Uncomment the `dbg` to print the IR.
|
||||
// Use `dbg_write!(f, []) instead of `write!(f, [])` in your formatting code to print some IR
|
||||
|
||||
@@ -25,11 +25,11 @@ fn main() -> Result<()> {
|
||||
cli.emit
|
||||
);
|
||||
}
|
||||
let input = read_from_stdin()?;
|
||||
let source = read_from_stdin()?;
|
||||
// It seems reasonable to give this a dummy name
|
||||
let formatted = format_and_debug_print(&input, &cli, Path::new("stdin.py"))?;
|
||||
let formatted = format_and_debug_print(&source, &cli, Path::new("stdin.py"))?;
|
||||
if cli.check {
|
||||
if formatted == input {
|
||||
if formatted == source {
|
||||
return Ok(());
|
||||
}
|
||||
bail!("Content not correctly formatted")
|
||||
@@ -37,9 +37,9 @@ fn main() -> Result<()> {
|
||||
stdout().lock().write_all(formatted.as_bytes())?;
|
||||
} else {
|
||||
for file in &cli.files {
|
||||
let input = fs::read_to_string(file)
|
||||
let source = fs::read_to_string(file)
|
||||
.with_context(|| format!("Could not read {}: ", file.display()))?;
|
||||
let formatted = format_and_debug_print(&input, &cli, file)?;
|
||||
let formatted = format_and_debug_print(&source, &cli, file)?;
|
||||
match cli.emit {
|
||||
Some(Emit::Stdout) => stdout().lock().write_all(formatted.as_bytes())?,
|
||||
None | Some(Emit::Files) => {
|
||||
|
||||
@@ -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]
|
||||
|
||||
226
crates/ruff_python_formatter/src/range_formatting.rs
Normal file
226
crates/ruff_python_formatter/src/range_formatting.rs
Normal file
@@ -0,0 +1,226 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{format_module_source_range, LspRowColumn, PyFormatOptions};
|
||||
use indoc::indoc;
|
||||
use insta::assert_snapshot;
|
||||
|
||||
fn format(source: &str, start: (usize, usize), end: (usize, usize)) -> String {
|
||||
format_module_source_range(
|
||||
source,
|
||||
PyFormatOptions::default(),
|
||||
Some(LspRowColumn {
|
||||
row: start.0,
|
||||
col: start.1,
|
||||
}),
|
||||
Some(LspRowColumn {
|
||||
row: end.0,
|
||||
col: end.1,
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_level() {
|
||||
assert_snapshot!(format(indoc! {r#"
|
||||
a = [1,]
|
||||
b = [1,]
|
||||
c = [1,]
|
||||
d = [1,]
|
||||
"#}, (1, 3), (2, 5)), @r###"
|
||||
a = [1,]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [1,]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_easy_nested() {
|
||||
assert_snapshot!(format(indoc! {r#"
|
||||
a = [1,]
|
||||
for i in range( 1 ):
|
||||
b = [1,]
|
||||
c = [1,]
|
||||
d = [1,]
|
||||
e = [1,]
|
||||
"#}, (3, 3), (3, 5)), @r###"
|
||||
a = [1,]
|
||||
for i in range(1):
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [
|
||||
1,
|
||||
]
|
||||
|
||||
e = [1,]
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_if() {
|
||||
let source = indoc! {r#"
|
||||
import random
|
||||
if random.random() < 0.5:
|
||||
a = [1,]
|
||||
b = [1,]
|
||||
elif random.random() < 0.75:
|
||||
c = [1,]
|
||||
d = [1,]
|
||||
else:
|
||||
e = [1,]
|
||||
f = [1,]
|
||||
g = [1,]
|
||||
"#};
|
||||
|
||||
assert_snapshot!(format(source, (3, 0), (3, 10)), @r###"
|
||||
import random
|
||||
if random.random() < 0.5:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
elif random.random() < 0.75:
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [
|
||||
1,
|
||||
]
|
||||
else:
|
||||
e = [
|
||||
1,
|
||||
]
|
||||
f = [
|
||||
1,
|
||||
]
|
||||
|
||||
g = [1,]
|
||||
"###);
|
||||
assert_snapshot!(format(source, (6, 0), (6, 10)), @r###"
|
||||
import random
|
||||
if random.random() < 0.5:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
elif random.random() < 0.75:
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [
|
||||
1,
|
||||
]
|
||||
else:
|
||||
e = [
|
||||
1,
|
||||
]
|
||||
f = [
|
||||
1,
|
||||
]
|
||||
|
||||
g = [1,]
|
||||
"###);
|
||||
assert_snapshot!(format(source, (9, 0), (9, 10)), @r###"
|
||||
import random
|
||||
if random.random() < 0.5:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
elif random.random() < 0.75:
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [
|
||||
1,
|
||||
]
|
||||
else:
|
||||
e = [
|
||||
1,
|
||||
]
|
||||
f = [
|
||||
1,
|
||||
]
|
||||
|
||||
g = [1,]
|
||||
"###);
|
||||
assert_snapshot!(format(source, (3, 0), (6, 10)), @r###"
|
||||
import random
|
||||
if random.random() < 0.5:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
elif random.random() < 0.75:
|
||||
c = [
|
||||
1,
|
||||
]
|
||||
d = [
|
||||
1,
|
||||
]
|
||||
else:
|
||||
e = [
|
||||
1,
|
||||
]
|
||||
f = [
|
||||
1,
|
||||
]
|
||||
|
||||
g = [1,]
|
||||
"###);
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[test]
|
||||
fn test_trailing_comment() {
|
||||
assert_snapshot!(format(indoc! {r#"
|
||||
if True:
|
||||
a = [1,]
|
||||
# trailing comment
|
||||
"#}, (1, 3), (2, 5)), @r###"
|
||||
if True:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
|
||||
# trailing comment
|
||||
"###);
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[test]
|
||||
fn test_alternative_indent() {
|
||||
assert_snapshot!(format(indoc! {r#"
|
||||
if True:
|
||||
a = [1,]
|
||||
b = [1,]
|
||||
c = [1,]
|
||||
"#}, (1, 3), (2, 5)), @r###"
|
||||
if True:
|
||||
a = [
|
||||
1,
|
||||
]
|
||||
b = [
|
||||
1,
|
||||
]
|
||||
c = [1,]
|
||||
"###);
|
||||
}
|
||||
}
|
||||
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,5 +1,5 @@
|
||||
use ruff_formatter::FormatOptions;
|
||||
use ruff_python_formatter::{format_module, PyFormatOptions};
|
||||
use ruff_python_formatter::{format_module_source, PyFormatOptions};
|
||||
use similar::TextDiff;
|
||||
use std::fmt::{Formatter, Write};
|
||||
use std::io::BufReader;
|
||||
@@ -20,7 +20,7 @@ fn black_compatibility() {
|
||||
PyFormatOptions::from_extension(input_path)
|
||||
};
|
||||
|
||||
let printed = format_module(&content, options.clone()).unwrap_or_else(|err| {
|
||||
let printed = format_module_source(&content, options.clone()).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"Formatting of {} to succeed but encountered error {err}",
|
||||
input_path.display()
|
||||
@@ -107,7 +107,8 @@ fn format() {
|
||||
let content = fs::read_to_string(input_path).unwrap();
|
||||
|
||||
let options = PyFormatOptions::from_extension(input_path);
|
||||
let printed = format_module(&content, options.clone()).expect("Formatting to succeed");
|
||||
let printed =
|
||||
format_module_source(&content, options.clone()).expect("Formatting to succeed");
|
||||
let formatted_code = printed.as_code();
|
||||
|
||||
ensure_stability_when_formatting_twice(formatted_code, options.clone(), input_path);
|
||||
@@ -124,7 +125,7 @@ fn format() {
|
||||
|
||||
for (i, options) in options.into_iter().enumerate() {
|
||||
let printed =
|
||||
format_module(&content, options.clone()).expect("Formatting to succeed");
|
||||
format_module_source(&content, options.clone()).expect("Formatting to succeed");
|
||||
let formatted_code = printed.as_code();
|
||||
|
||||
ensure_stability_when_formatting_twice(formatted_code, options.clone(), input_path);
|
||||
@@ -139,7 +140,8 @@ fn format() {
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
let printed = format_module(&content, options.clone()).expect("Formatting to succeed");
|
||||
let printed =
|
||||
format_module_source(&content, options.clone()).expect("Formatting to succeed");
|
||||
let formatted_code = printed.as_code();
|
||||
|
||||
ensure_stability_when_formatting_twice(formatted_code, options, input_path);
|
||||
@@ -174,7 +176,7 @@ fn ensure_stability_when_formatting_twice(
|
||||
options: PyFormatOptions,
|
||||
input_path: &Path,
|
||||
) {
|
||||
let reformatted = match format_module(formatted_code, options) {
|
||||
let reformatted = match format_module_source(formatted_code, options) {
|
||||
Ok(reformatted) => reformatted,
|
||||
Err(err) => {
|
||||
panic!(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_python_parser::lexer::{lex, LexicalError};
|
||||
use ruff_python_parser::{Mode, Tok};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -20,3 +21,21 @@ impl CommentRangesBuilder {
|
||||
CommentRanges::new(self.ranges)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to lex and extract comment ranges
|
||||
pub fn tokens_and_ranges(
|
||||
source: &str,
|
||||
) -> Result<(Vec<(Tok, TextRange)>, CommentRanges), LexicalError> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for result in lex(source, Mode::Module) {
|
||||
let (token, range) = result?;
|
||||
|
||||
comment_ranges.visit_token(&token, range);
|
||||
tokens.push((token, range));
|
||||
}
|
||||
|
||||
let comment_ranges = comment_ranges.finish();
|
||||
Ok((tokens, comment_ranges))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
mod comment_ranges;
|
||||
mod indexer;
|
||||
|
||||
pub use comment_ranges::CommentRangesBuilder;
|
||||
pub use comment_ranges::{tokens_and_ranges, CommentRangesBuilder};
|
||||
pub use indexer::Indexer;
|
||||
|
||||
@@ -110,8 +110,8 @@
|
||||
//! [lexer]: crate::lexer
|
||||
|
||||
pub use parser::{
|
||||
parse, parse_expression, parse_expression_starts_at, parse_program, parse_starts_at,
|
||||
parse_suite, parse_tokens, ParseError, ParseErrorType,
|
||||
parse, parse_expression, parse_expression_starts_at, parse_ok_tokens, parse_program,
|
||||
parse_starts_at, parse_suite, parse_tokens, ParseError, ParseErrorType,
|
||||
};
|
||||
use ruff_python_ast::{CmpOp, Expr, Mod, PySourceType, Suite};
|
||||
use ruff_text_size::{Ranged, TextRange, TextSize};
|
||||
|
||||
@@ -18,7 +18,7 @@ use itertools::Itertools;
|
||||
pub(super) use lalrpop_util::ParseError as LalrpopError;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::lexer::{lex, lex_starts_at};
|
||||
use crate::lexer::{lex, lex_starts_at, Spanned};
|
||||
use crate::{
|
||||
lexer::{self, LexResult, LexicalError, LexicalErrorType},
|
||||
python,
|
||||
@@ -159,7 +159,7 @@ pub fn parse_expression_starts_at(
|
||||
/// let program = parse(source, Mode::Ipython, "<embedded>");
|
||||
/// assert!(program.is_ok());
|
||||
/// ```
|
||||
pub fn parse(source: &str, mode: Mode, source_path: &str) -> Result<ast::Mod, ParseError> {
|
||||
pub fn parse(source: &str, mode: Mode, source_path: &str) -> Result<Mod, ParseError> {
|
||||
parse_starts_at(source, mode, source_path, TextSize::default())
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ pub fn parse_starts_at(
|
||||
mode: Mode,
|
||||
source_path: &str,
|
||||
offset: TextSize,
|
||||
) -> Result<ast::Mod, ParseError> {
|
||||
) -> Result<Mod, ParseError> {
|
||||
let lxr = lexer::lex_starts_at(source, mode, offset);
|
||||
parse_tokens(lxr, mode, source_path)
|
||||
}
|
||||
@@ -215,7 +215,7 @@ pub fn parse_tokens(
|
||||
lxr: impl IntoIterator<Item = LexResult>,
|
||||
mode: Mode,
|
||||
source_path: &str,
|
||||
) -> Result<ast::Mod, ParseError> {
|
||||
) -> Result<Mod, ParseError> {
|
||||
let lxr = lxr.into_iter();
|
||||
|
||||
parse_filtered_tokens(
|
||||
@@ -225,19 +225,35 @@ pub fn parse_tokens(
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse tokens into an AST like [`parse_tokens`], but we already know all tokens are valid.
|
||||
pub fn parse_ok_tokens(
|
||||
lxr: impl IntoIterator<Item = Spanned>,
|
||||
mode: Mode,
|
||||
source_path: &str,
|
||||
) -> Result<Mod, ParseError> {
|
||||
let lxr = lxr
|
||||
.into_iter()
|
||||
.filter(|(tok, _)| !matches!(tok, Tok::Comment { .. } | Tok::NonLogicalNewline));
|
||||
let marker_token = (Tok::start_marker(mode), TextRange::default());
|
||||
let lexer = iter::once(marker_token)
|
||||
.chain(lxr)
|
||||
.map(|(t, range)| (range.start(), t, range.end()));
|
||||
python::TopParser::new()
|
||||
.parse(mode, lexer)
|
||||
.map_err(|e| parse_error_from_lalrpop(e, source_path))
|
||||
}
|
||||
|
||||
fn parse_filtered_tokens(
|
||||
lxr: impl IntoIterator<Item = LexResult>,
|
||||
mode: Mode,
|
||||
source_path: &str,
|
||||
) -> Result<ast::Mod, ParseError> {
|
||||
) -> Result<Mod, ParseError> {
|
||||
let marker_token = (Tok::start_marker(mode), TextRange::default());
|
||||
let lexer = iter::once(Ok(marker_token)).chain(lxr);
|
||||
python::TopParser::new()
|
||||
.parse(
|
||||
mode,
|
||||
lexer
|
||||
.into_iter()
|
||||
.map_ok(|(t, range)| (range.start(), t, range.end())),
|
||||
lexer.map_ok(|(t, range)| (range.start(), t, range.end())),
|
||||
)
|
||||
.map_err(|e| parse_error_from_lalrpop(e, source_path))
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
(
|
||||
|
||||
@@ -441,6 +441,75 @@ impl<'a> Locator<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the byte offset from zero-indexed row and column indices.
|
||||
///
|
||||
/// We get row and column from the LSP. E.g.
|
||||
/// ```text
|
||||
/// a=(1,2,)
|
||||
/// b=(3,4,)
|
||||
/// ^
|
||||
/// c=(5,6,)
|
||||
/// ```
|
||||
/// has coordinates `1:2`. Note that indices are computed in chars, e.g.
|
||||
/// ```text
|
||||
/// a=(1,2,)
|
||||
/// "안녕"
|
||||
/// ^
|
||||
/// ```
|
||||
/// where the first syllable is a single character (two bytes), we get `1:2`, while for
|
||||
/// ```text
|
||||
/// a=(1,2,)
|
||||
/// "감기"
|
||||
/// ^
|
||||
/// ```
|
||||
/// where the first syllable is three characters (three times two bytes), we get `1:4`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
/// # use ruff_source_file::Locator;
|
||||
///
|
||||
/// let source = "a=(1,2,)\nb=(3,4,)";
|
||||
/// let locator = Locator::new(source);
|
||||
/// let offset = locator.convert_row_and_column(1, 2).unwrap();
|
||||
/// assert_eq!(&source[TextRange::new(offset, source.text_len())], "(3,4,)");
|
||||
///
|
||||
/// let source = "a=(1,2,)\n'안녕'";
|
||||
/// let locator = Locator::new(source);
|
||||
/// let offset = locator.convert_row_and_column(1, 2).unwrap();
|
||||
/// assert_eq!(&source[TextRange::new(offset, source.text_len())], "녕'");
|
||||
///
|
||||
/// let source = "a=(1,2,)\n'감기'";
|
||||
/// let locator = Locator::new(source);
|
||||
/// let offset = locator.convert_row_and_column(1, 4).unwrap();
|
||||
/// assert_eq!(&source[TextRange::new(offset, source.text_len())], "기'");
|
||||
/// ```
|
||||
pub fn convert_row_and_column(&self, row: usize, column: usize) -> Option<TextSize> {
|
||||
let line_start = *self.to_index().line_starts().get(row)?;
|
||||
let next_line_start = self
|
||||
.to_index()
|
||||
.line_starts()
|
||||
.get(row + 1)
|
||||
.copied()
|
||||
.unwrap_or(self.contents.text_len());
|
||||
let line_contents = &self.contents[TextRange::from(line_start..next_line_start)];
|
||||
debug_assert!(
|
||||
line_contents
|
||||
.chars()
|
||||
// Since the range goes to the next line start, `line_contents` contains the line
|
||||
// break
|
||||
.take_while(|c| *c != '\n' && *c != '\r')
|
||||
.count()
|
||||
>= column,
|
||||
"The column is not in the line"
|
||||
);
|
||||
let len_in_line: TextSize = line_contents
|
||||
.chars()
|
||||
.take(column)
|
||||
.map(TextLen::text_len)
|
||||
.sum();
|
||||
Some(line_start + len_in_line)
|
||||
}
|
||||
|
||||
/// Take the source code between the given [`TextRange`].
|
||||
#[inline]
|
||||
pub fn slice<T: Ranged>(&self, ranged: T) -> &'a str {
|
||||
|
||||
@@ -14,7 +14,7 @@ use ruff_linter::settings::{flags, DUMMY_VARIABLE_RGX, PREFIXES};
|
||||
use ruff_linter::source_kind::SourceKind;
|
||||
use ruff_python_ast::{Mod, PySourceType};
|
||||
use ruff_python_codegen::Stylist;
|
||||
use ruff_python_formatter::{format_node, pretty_comments, PyFormatContext};
|
||||
use ruff_python_formatter::{format_module_ast, pretty_comments, PyFormatContext};
|
||||
use ruff_python_index::{CommentRangesBuilder, Indexer};
|
||||
use ruff_python_parser::lexer::LexResult;
|
||||
use ruff_python_parser::{parse_tokens, AsMode, Mode};
|
||||
@@ -303,9 +303,9 @@ 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(
|
||||
format_module_ast(
|
||||
&self.module,
|
||||
&self.comment_ranges,
|
||||
self.source_code,
|
||||
|
||||
@@ -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