Compare commits

..

2 Commits

Author SHA1 Message Date
Micha Reiser
6ca7407868 Options: Support #[serde(alias = name)]
Signed-off-by: Micha Reiser <micha@reiser.io>
2023-10-18 15:39:39 +09:00
Micha Reiser
f1b00cafd4 Respect tab-size setting in formatter 2023-10-18 15:29:43 +09:00
95 changed files with 969 additions and 2691 deletions

View File

@@ -13,8 +13,3 @@ MD041: false
# MD013/line-length
MD013: false
# MD024/no-duplicate-heading
MD024:
# Allow when nested under different parents e.g. CHANGELOG.md
allow_different_nesting: true

View File

@@ -1,60 +1,11 @@
# Changelog
## 0.1.1
### Rule changes
- Add unsafe fix for `escape-sequence-in-docstring` (`D301`) (#7970)
### Configuration
- Respect `#(deprecated)` attribute in configuration options (#8035)
- Add `[format|lint].exclude` options (#8000)
- Respect `tab-size` setting in formatter (#8006)
- Add `lint.preview` (#8002)
## Preview features
- \[`pylint`\] Implement `literal-membership` (`PLR6201`) (#7973)
- \[`pylint`\] Implement `too-many-boolean-expressions` (`PLR0916`) (#7975)
- \[`pylint`\] Implement `misplaced-bare-raise` (`E0704`) (#7961)
- \[`pylint`\] Implement `global-at-module-level` (`W0604`) (#8058)
- \[`pylint`\] Implement `unspecified-encoding` (`PLW1514`) (#7939)
- Add fix for `triple-single-quotes` (`D300`) (#7967)
### Formatter
- New code style badge for `ruff format` (#7878)
- Fix comments outside expression parentheses (#7873)
- Add `--target-version` to `ruff format` (#8055)
- Skip over parentheses when detecting `in` keyword (#8054)
- Add `--diff` option to `ruff format` (#7937)
- Insert newline after nested function or class statements (#7946)
- Use `pass` over ellipsis in non-function/class contexts (#8049)
### Bug fixes
- Lazily evaluate all PEP 695 type alias values (#8033)
- Avoid failed assertion when showing fixes from stdin (#8029)
- Avoid flagging HTTP and HTTPS literals in urllib-open (#8046)
- Avoid flagging `bad-dunder-method-name` for `_` (#8015)
- Remove Python 2-only methods from `URLOpen` audit (#8047)
- Use set bracket replacement for `iteration-over-set` to preserve whitespace and comments (#8001)
### Documentation
- Update tutorial to match revised Ruff defaults (#8066)
- Update rule `B005` docs (#8028)
- Update GitHub actions example in docs to use `--output-format` (#8014)
- Document `lint.preview` and `format.preview` (#8032)
- Clarify that new rules should be added to `RuleGroup::Preview`. (#7989)
## 0.1.0
This is the first release which uses the `CHANGELOG` file. See [GitHub Releases](https://github.com/astral-sh/ruff/releases) for prior changelog entries.
Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
## 0.1.0
### Breaking changes
- Unsafe fixes are no longer displayed or applied without opt-in ([#7769](https://github.com/astral-sh/ruff/pull/7769))

30
Cargo.lock generated
View File

@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
@@ -2051,7 +2051,7 @@ dependencies = [
[[package]]
name = "ruff_cli"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -2188,7 +2188,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.1",
@@ -2259,6 +2259,7 @@ dependencies = [
"proc-macro2",
"quote",
"ruff_python_trivia",
"serde_derive_internals 0.29.0",
"syn 2.0.38",
]
@@ -2438,7 +2439,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.1.1"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
@@ -2624,7 +2625,7 @@ checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"serde_derive_internals 0.26.0",
"syn 1.0.109",
]
@@ -2664,9 +2665,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090"
[[package]]
name = "serde"
version = "1.0.188"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
dependencies = [
"serde_derive",
]
@@ -2684,9 +2685,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.188"
version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
"proc-macro2",
"quote",
@@ -2704,6 +2705,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "serde_derive_internals"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "serde_json"
version = "1.0.107"

View File

@@ -34,7 +34,7 @@ quote = { version = "1.0.23" }
regex = { version = "1.10.2" }
rustc-hash = { version = "1.1.0" }
schemars = { version = "0.8.15" }
serde = { version = "1.0.152", features = ["derive"] }
serde = { version = "1.0.189", features = ["derive"] }
serde_json = { version = "1.0.107" }
shellexpand = { version = "3.0.0" }
similar = { version = "2.3.0", features = ["inline"] }

View File

@@ -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.1.1
rev: v0.1.0
hooks:
- id: ruff
```
@@ -237,8 +237,9 @@ linting command.
isort, pyupgrade, and others. Regardless of the rule's origin, Ruff re-implements every rule in
Rust as a first-party feature.
By default, Ruff enables Flake8's `F` rules, along with a subset of the `E` rules, omitting any
stylistic rules that overlap with the use of a formatter, like
By default, Ruff enables Flake8's `E` and `F` rules. Ruff supports all rules from the `F` category,
and a [subset](https://docs.astral.sh/ruff/rules/#error-e) of the `E` category, omitting those
stylistic rules made obsolete by the use of a formatter, like
[Black](https://github.com/psf/black).
If you're just getting started with Ruff, **the default rule set is a great place to start**: it

View File

@@ -1,8 +0,0 @@
{
"label": "code style",
"message": "Ruff",
"logoSvg": "<svg width=\"510\" height=\"622\" viewBox=\"0 0 510 622\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M206.701 0C200.964 0 196.314 4.64131 196.314 10.3667V41.4667C196.314 47.192 191.663 51.8333 185.927 51.8333H156.843C151.107 51.8333 146.456 56.4746 146.456 62.2V145.133C146.456 150.859 141.806 155.5 136.069 155.5H106.986C101.249 155.5 96.5988 160.141 96.5988 165.867V222.883C96.5988 228.609 91.9484 233.25 86.2118 233.25H57.1283C51.3917 233.25 46.7413 237.891 46.7413 243.617V300.633C46.7413 306.359 42.0909 311 36.3544 311H10.387C4.6504 311 0 315.641 0 321.367V352.467C0 358.192 4.6504 362.833 10.387 362.833H145.418C151.154 362.833 155.804 367.475 155.804 373.2V430.217C155.804 435.942 151.154 440.583 145.418 440.583H116.334C110.597 440.583 105.947 445.225 105.947 450.95V507.967C105.947 513.692 101.297 518.333 95.5601 518.333H66.4766C60.74 518.333 56.0896 522.975 56.0896 528.7V611.633C56.0896 617.359 60.74 622 66.4766 622H149.572C155.309 622 159.959 617.359 159.959 611.633V570.167H201.507C207.244 570.167 211.894 565.525 211.894 559.8V528.7C211.894 522.975 216.544 518.333 222.281 518.333H251.365C257.101 518.333 261.752 513.692 261.752 507.967V476.867C261.752 471.141 266.402 466.5 272.138 466.5H301.222C306.959 466.5 311.609 461.859 311.609 456.133V425.033C311.609 419.308 316.259 414.667 321.996 414.667H351.079C356.816 414.667 361.466 410.025 361.466 404.3V373.2C361.466 367.475 366.117 362.833 371.853 362.833H400.937C406.673 362.833 411.324 358.192 411.324 352.467V321.367C411.324 315.641 415.974 311 421.711 311H450.794C456.531 311 461.181 306.359 461.181 300.633V217.7C461.181 211.975 456.531 207.333 450.794 207.333H420.672C414.936 207.333 410.285 202.692 410.285 196.967V165.867C410.285 160.141 414.936 155.5 420.672 155.5H449.756C455.492 155.5 460.143 150.859 460.143 145.133V114.033C460.143 108.308 464.793 103.667 470.53 103.667H499.613C505.35 103.667 510 99.0253 510 93.3V10.3667C510 4.64132 505.35 0 499.613 0H206.701ZM168.269 440.583C162.532 440.583 157.882 445.225 157.882 450.95V507.967C157.882 513.692 153.231 518.333 147.495 518.333H118.411C112.675 518.333 108.024 522.975 108.024 528.7V559.8C108.024 565.525 112.675 570.167 118.411 570.167H159.959V528.7C159.959 522.975 164.61 518.333 170.346 518.333H199.43C205.166 518.333 209.817 513.692 209.817 507.967V476.867C209.817 471.141 214.467 466.5 220.204 466.5H249.287C255.024 466.5 259.674 461.859 259.674 456.133V425.033C259.674 419.308 264.325 414.667 270.061 414.667H299.145C304.881 414.667 309.532 410.025 309.532 404.3V373.2C309.532 367.475 314.182 362.833 319.919 362.833H349.002C354.739 362.833 359.389 358.192 359.389 352.467V321.367C359.389 315.641 364.039 311 369.776 311H398.859C404.596 311 409.246 306.359 409.246 300.633V269.533C409.246 263.808 404.596 259.167 398.859 259.167H318.88C313.143 259.167 308.493 254.525 308.493 248.8V217.7C308.493 211.975 313.143 207.333 318.88 207.333H347.963C353.7 207.333 358.35 202.692 358.35 196.967V165.867C358.35 160.141 363.001 155.5 368.737 155.5H397.821C403.557 155.5 408.208 150.859 408.208 145.133V114.033C408.208 108.308 412.858 103.667 418.595 103.667H447.678C453.415 103.667 458.065 99.0253 458.065 93.3V62.2C458.065 56.4746 453.415 51.8333 447.678 51.8333H208.778C203.041 51.8333 198.391 56.4746 198.391 62.2V145.133C198.391 150.859 193.741 155.5 188.004 155.5H158.921C153.184 155.5 148.534 160.141 148.534 165.867V222.883C148.534 228.609 143.883 233.25 138.147 233.25H109.063C103.327 233.25 98.6762 237.891 98.6762 243.617V300.633C98.6762 306.359 103.327 311 109.063 311H197.352C203.089 311 207.739 315.641 207.739 321.367V430.217C207.739 435.942 203.089 440.583 197.352 440.583H168.269Z\" fill=\"#D7FF64\"/></svg>",
"logoWidth": 10,
"labelColor": "grey",
"color": "#261230"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.1.1"
version = "0.1.0"
description = """
Convert Flake8 configuration files to Ruff configuration files.
"""

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_cli"
version = "0.1.1"
version = "0.1.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1 +0,0 @@
print("All formatted!")

View File

@@ -1,37 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "98e1dd71-14a2-454d-9be0-061dde560b07",
"metadata": {},
"outputs": [],
"source": [
"import numpy\n",
"maths = (numpy.arange(100)**2).sum()\n",
"stats= numpy.asarray([1,2,3,4]).median()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -1,3 +0,0 @@
x = 1
y=2
z = 3

View File

@@ -353,10 +353,6 @@ pub struct FormatCommand {
/// files would have been modified, and zero otherwise.
#[arg(long)]
pub check: bool,
/// Avoid writing any formatted files back; instead, exit with a non-zero status code and the
/// difference between the current file and how the formatted file would look like.
#[arg(long)]
pub diff: bool,
/// Path to the `pyproject.toml` or `ruff.toml` file to use for configuration.
#[arg(long, conflicts_with = "isolated")]
pub config: Option<PathBuf>,
@@ -398,9 +394,7 @@ pub struct FormatCommand {
/// The name of the file when passing it through stdin.
#[arg(long, help_heading = "Miscellaneous")]
pub stdin_filename: Option<PathBuf>,
/// The minimum Python version that should be supported.
#[arg(long, value_enum)]
pub target_version: Option<PythonVersion>,
/// Enable preview mode; enables unstable formatting.
/// Use `--no-preview` to disable.
#[arg(long, overrides_with("no_preview"))]
@@ -526,7 +520,6 @@ impl FormatCommand {
(
FormatArguments {
check: self.check,
diff: self.diff,
config: self.config,
files: self.files,
isolated: self.isolated,
@@ -541,7 +534,6 @@ impl FormatCommand {
exclude: self.exclude,
preview: resolve_bool_arg(self.preview, self.no_preview).map(PreviewMode::from),
force_exclude: resolve_bool_arg(self.force_exclude, self.no_force_exclude),
target_version: self.target_version,
// Unsupported on the formatter CLI, but required on `Overrides`.
..CliOverrides::default()
},
@@ -585,7 +577,6 @@ pub struct CheckArguments {
#[allow(clippy::struct_excessive_bools)]
pub struct FormatArguments {
pub check: bool,
pub diff: bool,
pub config: Option<PathBuf>,
pub files: Vec<PathBuf>,
pub isolated: bool,

View File

@@ -1,7 +1,5 @@
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::io;
use std::io::{stderr, stdout, Write};
use std::path::{Path, PathBuf};
use std::time::Instant;
@@ -11,7 +9,6 @@ use itertools::Itertools;
use log::error;
use rayon::iter::Either::{Left, Right};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use similar::TextDiff;
use thiserror::Error;
use tracing::debug;
@@ -37,20 +34,6 @@ pub(crate) enum FormatMode {
Write,
/// Check if the file is formatted, but do not write the formatted contents back.
Check,
/// Check if the file is formatted, show a diff if not.
Diff,
}
impl FormatMode {
pub(crate) fn from_cli(cli: &FormatArguments) -> Self {
if cli.diff {
FormatMode::Diff
} else if cli.check {
FormatMode::Check
} else {
FormatMode::Write
}
}
}
/// Format a set of files, and return the exit status.
@@ -65,7 +48,11 @@ pub(crate) fn format(
overrides,
cli.stdin_filename.as_deref(),
)?;
let mode = FormatMode::from_cli(cli);
let mode = if cli.check {
FormatMode::Check
} else {
FormatMode::Write
};
let (paths, resolver) = python_files_in_path(&cli.files, &pyproject_config, overrides)?;
if paths.is_empty() {
@@ -74,7 +61,7 @@ pub(crate) fn format(
}
let start = Instant::now();
let (mut results, mut errors): (Vec<_>, Vec<_>) = paths
let (mut results, errors): (Vec<_>, Vec<_>) = paths
.into_par_iter()
.filter_map(|entry| {
match entry {
@@ -98,31 +85,12 @@ pub(crate) fn format(
return None;
}
// Extract the sources from the file.
let unformatted = match SourceKind::from_path(path, source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => return None,
Err(err) => {
return Some(Err(FormatCommandError::Read(
Some(path.to_path_buf()),
err,
)));
}
};
Some(
match catch_unwind(|| {
format_path(
path,
&resolved_settings.formatter,
&unformatted,
source_type,
mode,
)
format_path(path, &resolved_settings.formatter, source_type, mode)
}) {
Ok(inner) => inner.map(|result| FormatPathResult {
path: resolved_file.into_path(),
unformatted,
result,
}),
Err(error) => Err(FormatCommandError::Panic(
@@ -141,27 +109,6 @@ pub(crate) fn format(
});
let duration = start.elapsed();
// Make output deterministic, at least as long as we have a path
results.sort_unstable_by(|x, y| x.path.cmp(&y.path));
errors.sort_by(|x, y| {
fn get_key(error: &FormatCommandError) -> Option<&PathBuf> {
match &error {
FormatCommandError::Ignore(ignore) => {
if let ignore::Error::WithPath { path, .. } = ignore {
Some(path)
} else {
None
}
}
FormatCommandError::Panic(path, _)
| FormatCommandError::Read(path, _)
| FormatCommandError::Format(path, _)
| FormatCommandError::Write(path, _) => path.as_ref(),
}
}
get_key(x).cmp(&get_key(y))
});
debug!(
"Formatted {} files in {:.2?}",
results.len() + errors.len(),
@@ -174,19 +121,14 @@ pub(crate) fn format(
}
results.sort_unstable_by(|a, b| a.path.cmp(&b.path));
let results = FormatResults::new(results.as_slice(), mode);
if mode.is_diff() {
results.write_diff(&mut stdout().lock())?;
}
let summary = FormatSummary::new(results.as_slice(), mode);
// Report on the formatting changes.
if log_level >= LogLevel::Default {
if mode.is_diff() {
// Allow piping the diff to e.g. a file by writing the summary to stderr
results.write_summary(&mut stderr().lock())?;
} else {
results.write_summary(&mut stdout().lock())?;
#[allow(clippy::print_stdout)]
{
println!("{summary}");
}
}
@@ -198,9 +140,9 @@ pub(crate) fn format(
Ok(ExitStatus::Error)
}
}
FormatMode::Check | FormatMode::Diff => {
FormatMode::Check => {
if errors.is_empty() {
if results.any_formatted() {
if summary.any_formatted() {
Ok(ExitStatus::Failure)
} else {
Ok(ExitStatus::Success)
@@ -217,43 +159,57 @@ pub(crate) fn format(
fn format_path(
path: &Path,
settings: &FormatterSettings,
unformatted: &SourceKind,
source_type: PySourceType,
mode: FormatMode,
) -> Result<FormatResult, FormatCommandError> {
// Extract the sources from the file.
let source_kind = match SourceKind::from_path(path, source_type) {
Ok(Some(source_kind)) => source_kind,
Ok(None) => return Ok(FormatResult::Unchanged),
Err(err) => {
return Err(FormatCommandError::Read(Some(path.to_path_buf()), err));
}
};
// Format the source.
let format_result = match format_source(unformatted, source_type, Some(path), settings)? {
FormattedSource::Formatted(formatted) => match mode {
FormatMode::Write => {
match format_source(source_kind, source_type, Some(path), settings)? {
FormattedSource::Formatted(formatted) => {
if mode.is_write() {
let mut writer = File::create(path).map_err(|err| {
FormatCommandError::Write(Some(path.to_path_buf()), err.into())
})?;
formatted
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(Some(path.to_path_buf()), err))?;
FormatResult::Formatted
}
FormatMode::Check => FormatResult::Formatted,
FormatMode::Diff => FormatResult::Diff(formatted),
},
FormattedSource::Unchanged => FormatResult::Unchanged,
};
Ok(format_result)
Ok(FormatResult::Formatted)
}
FormattedSource::Unchanged(_) => Ok(FormatResult::Unchanged),
}
}
#[derive(Debug)]
pub(crate) enum FormattedSource {
/// The source was formatted, and the [`SourceKind`] contains the transformed source code.
Formatted(SourceKind),
/// The source was unchanged.
Unchanged,
/// The source was unchanged, and the [`SourceKind`] contains the original source code.
Unchanged(SourceKind),
}
impl From<FormattedSource> for FormatResult {
fn from(value: FormattedSource) -> Self {
match value {
FormattedSource::Formatted(_) => FormatResult::Formatted,
FormattedSource::Unchanged => FormatResult::Unchanged,
FormattedSource::Unchanged(_) => FormatResult::Unchanged,
}
}
}
impl FormattedSource {
pub(crate) fn source_kind(&self) -> &SourceKind {
match self {
FormattedSource::Formatted(source_kind) => source_kind,
FormattedSource::Unchanged(source_kind) => source_kind,
}
}
}
@@ -261,28 +217,30 @@ impl From<FormattedSource> for FormatResult {
/// Format a [`SourceKind`], returning the transformed [`SourceKind`], or `None` if the source was
/// unchanged.
pub(crate) fn format_source(
source_kind: &SourceKind,
source_kind: SourceKind,
source_type: PySourceType,
path: Option<&Path>,
settings: &FormatterSettings,
) -> Result<FormattedSource, FormatCommandError> {
match source_kind {
SourceKind::Python(unformatted) => {
let options = settings.to_format_options(source_type, unformatted);
let options = settings.to_format_options(source_type, &unformatted);
let formatted = format_module_source(unformatted, options)
let formatted = format_module_source(&unformatted, options)
.map_err(|err| FormatCommandError::Format(path.map(Path::to_path_buf), err))?;
let formatted = formatted.into_code();
if formatted.len() == unformatted.len() && formatted == *unformatted {
Ok(FormattedSource::Unchanged)
Ok(FormattedSource::Unchanged(SourceKind::Python(unformatted)))
} else {
Ok(FormattedSource::Formatted(SourceKind::Python(formatted)))
}
}
SourceKind::IpyNotebook(notebook) => {
if !notebook.is_python_notebook() {
return Ok(FormattedSource::Unchanged);
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
notebook,
)));
}
let options = settings.to_format_options(source_type, notebook.source_code());
@@ -330,7 +288,9 @@ pub(crate) fn format_source(
// If the file was unchanged, return `None`.
let (Some(mut output), Some(last)) = (output, last) else {
return Ok(FormattedSource::Unchanged);
return Ok(FormattedSource::Unchanged(SourceKind::IpyNotebook(
notebook,
)));
};
// Add the remaining content.
@@ -338,23 +298,21 @@ pub(crate) fn format_source(
output.push_str(slice);
// Update the notebook.
let mut formatted = notebook.clone();
formatted.update(&source_map, output);
let mut notebook = notebook.clone();
notebook.update(&source_map, output);
Ok(FormattedSource::Formatted(SourceKind::IpyNotebook(
formatted,
notebook,
)))
}
}
}
/// The result of an individual formatting operation.
#[derive(Debug, Clone, is_macro::Is)]
#[derive(Debug, Clone, Copy, is_macro::Is)]
pub(crate) enum FormatResult {
/// The file was formatted.
Formatted,
/// The file was formatted, [`SourceKind`] contains the formatted code
Diff(SourceKind),
/// The file was unchanged, as the formatted contents matched the existing contents.
Unchanged,
}
@@ -363,55 +321,38 @@ pub(crate) enum FormatResult {
#[derive(Debug)]
struct FormatPathResult {
path: PathBuf,
unformatted: SourceKind,
result: FormatResult,
}
/// The results of formatting a set of files
/// A summary of the formatting results.
#[derive(Debug)]
struct FormatResults<'a> {
struct FormatSummary<'a> {
/// The individual formatting results.
results: &'a [FormatPathResult],
/// The format mode that was used.
mode: FormatMode,
}
impl<'a> FormatResults<'a> {
impl<'a> FormatSummary<'a> {
fn new(results: &'a [FormatPathResult], mode: FormatMode) -> Self {
Self { results, mode }
}
/// Returns `true` if any of the files require formatting.
fn any_formatted(&self) -> bool {
self.results.iter().any(|result| match result.result {
FormatResult::Formatted | FormatResult::Diff { .. } => true,
FormatResult::Unchanged => false,
})
self.results
.iter()
.any(|result| result.result.is_formatted())
}
}
fn write_diff(&self, f: &mut impl Write) -> io::Result<()> {
for result in self.results {
if let FormatResult::Diff(formatted) = &result.result {
let text_diff =
TextDiff::from_lines(result.unformatted.source_code(), formatted.source_code());
let mut unified_diff = text_diff.unified_diff();
unified_diff.header(
&fs::relativize_path(&result.path),
&fs::relativize_path(&result.path),
);
unified_diff.to_writer(&mut *f)?;
}
}
Ok(())
}
fn write_summary(&self, f: &mut impl Write) -> io::Result<()> {
impl Display for FormatSummary<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// Compute the number of changed and unchanged files.
let mut changed = 0u32;
let mut formatted = 0u32;
let mut unchanged = 0u32;
for result in self.results {
match &result.result {
match result.result {
FormatResult::Formatted => {
// If we're running in check mode, report on any files that would be formatted.
if self.mode.is_check() {
@@ -421,42 +362,39 @@ impl<'a> FormatResults<'a> {
fs::relativize_path(&result.path).bold()
)?;
}
changed += 1;
formatted += 1;
}
FormatResult::Unchanged => unchanged += 1,
FormatResult::Diff(_) => {
changed += 1;
}
}
}
// Write out a summary of the formatting results.
if changed > 0 && unchanged > 0 {
writeln!(
if formatted > 0 && unchanged > 0 {
write!(
f,
"{} file{} {}, {} file{} left unchanged",
changed,
if changed == 1 { "" } else { "s" },
formatted,
if formatted == 1 { "" } else { "s" },
match self.mode {
FormatMode::Write => "reformatted",
FormatMode::Check | FormatMode::Diff => "would be reformatted",
FormatMode::Check => "would be reformatted",
},
unchanged,
if unchanged == 1 { "" } else { "s" },
)
} else if changed > 0 {
writeln!(
} else if formatted > 0 {
write!(
f,
"{} file{} {}",
changed,
if changed == 1 { "" } else { "s" },
formatted,
if formatted == 1 { "" } else { "s" },
match self.mode {
FormatMode::Write => "reformatted",
FormatMode::Check | FormatMode::Diff => "would be reformatted",
FormatMode::Check => "would be reformatted",
}
)
} else if unchanged > 0 {
writeln!(
write!(
f,
"{} file{} left unchanged",
unchanged,

View File

@@ -3,8 +3,6 @@ use std::path::Path;
use anyhow::Result;
use log::error;
use ruff_linter::fs;
use similar::TextDiff;
use ruff_linter::source_kind::SourceKind;
use ruff_python_ast::{PySourceType, SourceType};
@@ -12,9 +10,7 @@ use ruff_workspace::resolver::{match_exclusion, python_file_at_path};
use ruff_workspace::FormatterSettings;
use crate::args::{CliOverrides, FormatArguments};
use crate::commands::format::{
format_source, FormatCommandError, FormatMode, FormatResult, FormattedSource,
};
use crate::commands::format::{format_source, FormatCommandError, FormatMode, FormatResult};
use crate::resolve::resolve;
use crate::stdin::read_from_stdin;
use crate::ExitStatus;
@@ -27,7 +23,11 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
overrides,
cli.stdin_filename.as_deref(),
)?;
let mode = FormatMode::from_cli(cli);
let mode = if cli.check {
FormatMode::Check
} else {
FormatMode::Write
};
if let Some(filename) = cli.stdin_filename.as_deref() {
if !python_file_at_path(filename, &pyproject_config, overrides)? {
@@ -58,7 +58,7 @@ pub(crate) fn format_stdin(cli: &FormatArguments, overrides: &CliOverrides) -> R
) {
Ok(result) => match mode {
FormatMode::Write => Ok(ExitStatus::Success),
FormatMode::Check | FormatMode::Diff => {
FormatMode::Check => {
if result.is_formatted() {
Ok(ExitStatus::Failure)
} else {
@@ -93,37 +93,15 @@ fn format_source_code(
};
// Format the source.
let formatted = format_source(&source_kind, source_type, path, settings)?;
let formatted = format_source(source_kind, source_type, path, settings)?;
match &formatted {
FormattedSource::Formatted(formatted) => match mode {
FormatMode::Write => {
let mut writer = stdout().lock();
formatted
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
}
FormatMode::Check => {}
FormatMode::Diff => {
let mut writer = stdout().lock();
let text_diff =
TextDiff::from_lines(source_kind.source_code(), formatted.source_code());
let mut unified_diff = text_diff.unified_diff();
if let Some(path) = path {
unified_diff.header(&fs::relativize_path(path), &fs::relativize_path(path));
}
unified_diff.to_writer(&mut writer).unwrap();
}
},
FormattedSource::Unchanged => {
// Write to stdout regardless of whether the source was formatted
if mode.is_write() {
let mut writer = stdout().lock();
source_kind
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
}
}
// Write to stdout regardless of whether the source was formatted.
if mode.is_write() {
let mut writer = stdout().lock();
formatted
.source_kind()
.write(&mut writer)
.map_err(|err| FormatCommandError::Write(path.map(Path::to_path_buf), err))?;
}
Ok(FormatResult::from(formatted))

View File

@@ -1,7 +1,6 @@
#![cfg(not(target_family = "wasm"))]
use std::fs;
use std::path::Path;
use std::process::Command;
use std::str;
@@ -290,125 +289,3 @@ format = "json"
});
Ok(())
}
#[test]
fn test_diff() {
let args = ["format", "--isolated", "--diff"];
let fixtures = Path::new("resources").join("test").join("fixtures");
let paths = [
fixtures.join("unformatted.py"),
fixtures.join("formatted.py"),
fixtures.join("unformatted.ipynb"),
];
insta::with_settings!({filters => vec![
// Replace windows paths
(r"\\", "/"),
]}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths),
@r###"
success: false
exit_code: 1
----- stdout -----
--- resources/test/fixtures/unformatted.ipynb
+++ resources/test/fixtures/unformatted.ipynb
@@ -1,3 +1,4 @@
import numpy
-maths = (numpy.arange(100)**2).sum()
-stats= numpy.asarray([1,2,3,4]).median()
+
+maths = (numpy.arange(100) ** 2).sum()
+stats = numpy.asarray([1, 2, 3, 4]).median()
--- resources/test/fixtures/unformatted.py
+++ resources/test/fixtures/unformatted.py
@@ -1,3 +1,3 @@
x = 1
-y=2
+y = 2
z = 3
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
2 files would be reformatted, 1 file left unchanged
"###);
});
}
#[test]
fn test_diff_no_change() {
let args = ["format", "--isolated", "--diff"];
let fixtures = Path::new("resources").join("test").join("fixtures");
let paths = [fixtures.join("unformatted.py")];
insta::with_settings!({filters => vec![
// Replace windows paths
(r"\\", "/"),
]}, {
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).args(args).args(paths),
@r###"
success: false
exit_code: 1
----- stdout -----
--- resources/test/fixtures/unformatted.py
+++ resources/test/fixtures/unformatted.py
@@ -1,3 +1,3 @@
x = 1
-y=2
+y = 2
z = 3
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
1 file would be reformatted
"###
);
});
}
#[test]
fn test_diff_stdin_unformatted() {
let args = [
"format",
"--isolated",
"--diff",
"-",
"--stdin-filename",
"unformatted.py",
];
let fixtures = Path::new("resources").join("test").join("fixtures");
let unformatted = fs::read(fixtures.join("unformatted.py")).unwrap();
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted),
@r###"
success: false
exit_code: 1
----- stdout -----
--- unformatted.py
+++ unformatted.py
@@ -1,3 +1,3 @@
x = 1
-y=2
+y = 2
z = 3
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
"###);
}
#[test]
fn test_diff_stdin_formatted() {
let args = ["format", "--isolated", "--diff", "-"];
let fixtures = Path::new("resources").join("test").join("fixtures");
let unformatted = fs::read(fixtures.join("formatted.py")).unwrap();
assert_cmd_snapshot!(
Command::new(get_cargo_bin(BIN_NAME)).args(args).pass_stdin(unformatted),
@r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: `ruff format` is not yet stable, and subject to change in future versions.
"###);
}

View File

@@ -11,7 +11,7 @@ use strum::IntoEnumIterator;
use ruff_diagnostics::FixAvailability;
use ruff_linter::registry::{Linter, Rule, RuleNamespace};
use ruff_workspace::options::Options;
use ruff_workspace::options_base::{OptionEntry, OptionsMetadata};
use ruff_workspace::options_base::OptionsMetadata;
use crate::ROOT_DIR;
@@ -55,11 +55,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
output.push('\n');
}
process_documentation(
explanation.trim(),
&mut output,
&rule.noqa_code().to_string(),
);
process_documentation(explanation.trim(), &mut output);
let filename = PathBuf::from(ROOT_DIR)
.join("docs")
@@ -78,7 +74,7 @@ pub(crate) fn main(args: &Args) -> Result<()> {
Ok(())
}
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) {
fn process_documentation(documentation: &str, out: &mut String) {
let mut in_options = false;
let mut after = String::new();
@@ -104,17 +100,7 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
if let Some(rest) = line.strip_prefix("- `") {
let option = rest.trim_end().trim_end_matches('`');
match Options::metadata().find(option) {
Some(OptionEntry::Field(field)) => {
if field.deprecated.is_some() {
eprintln!("Rule {rule_name} references deprecated option {option}.");
}
}
Some(_) => {}
None => {
panic!("Unknown option {option} referenced by rule {rule_name}");
}
}
assert!(Options::metadata().has(option), "unknown option {option}");
let anchor = option.replace('.', "-");
out.push_str(&format!("- [`{option}`][{option}]\n"));
@@ -152,7 +138,6 @@ Something [`else`][other].
[other]: http://example.com.",
&mut output,
"example",
);
assert_eq!(
output,

View File

@@ -1,6 +1,7 @@
//! Generate a Markdown-compatible listing of configuration options for `pyproject.toml`.
//!
//! Used for <https://docs.astral.sh/ruff/settings/>.
use itertools::Itertools;
use std::fmt::Write;
use ruff_workspace::options::Options;
@@ -101,30 +102,30 @@ fn emit_field(output: &mut String, name: &str, field: &OptionField, parent_set:
output.push_str(&format!("{header_level} [`{name}`](#{name})\n"));
}
output.push('\n');
if let Some(deprecated) = &field.deprecated {
output.push_str("!!! warning \"Deprecated\"\n");
output.push_str(" This option has been deprecated");
if let Some(since) = deprecated.since {
write!(output, " in {since}").unwrap();
}
output.push('.');
if let Some(message) = deprecated.message {
writeln!(output, " {message}").unwrap();
}
output.push('\n');
}
output.push_str(field.doc);
output.push_str("\n\n");
output.push_str(&format!("**Default value**: `{}`\n", field.default));
output.push('\n');
output.push_str(&format!("**Type**: `{}`\n", field.value_type));
output.push('\n');
if !field.aliases.is_empty() {
let title = if field.aliases.len() == 1 {
"Alias"
} else {
"Aliases"
};
output.push_str(&format!(
"**{title}**: {}\n",
field
.aliases
.iter()
.map(|alias| format!("`{alias}`"))
.join(", ")
));
output.push('\n');
}
output.push_str(&format!(
"**Example usage**:\n\n```toml\n[tool.ruff{}]\n{}\n```\n",
if let Some(set_name) = parent_set.name() {

View File

@@ -575,10 +575,6 @@ where
context: PhantomData,
}
}
pub fn rule(&self) -> &R {
&self.rule
}
}
impl<T, R, O, C> FormatRefWithRule<'_, T, R, C>

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.1.1"
version = "0.1.0"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,19 +0,0 @@
import urllib.request
urllib.request.urlopen(url='http://www.google.com')
urllib.request.urlopen(url='http://www.google.com', **kwargs)
urllib.request.urlopen('http://www.google.com')
urllib.request.urlopen('file:///foo/bar/baz')
urllib.request.urlopen(url)
urllib.request.Request(url='http://www.google.com', **kwargs)
urllib.request.Request(url='http://www.google.com')
urllib.request.Request('http://www.google.com')
urllib.request.Request('file:///foo/bar/baz')
urllib.request.Request(url)
urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
urllib.request.URLopener().open(fullurl='http://www.google.com')
urllib.request.URLopener().open('http://www.google.com')
urllib.request.URLopener().open('file:///foo/bar/baz')
urllib.request.URLopener().open(url)

View File

@@ -1,10 +0,0 @@
global price # W0604
price = 25
if True:
global X # W0604
def no_error():
global price
price = 30

View File

@@ -21,9 +21,6 @@ use crate::settings::types::PythonVersion;
pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
match stmt {
Stmt::Global(ast::StmtGlobal { names, range: _ }) => {
if checker.enabled(Rule::GlobalAtModuleLevel) {
pylint::rules::global_at_module_level(checker, stmt);
}
if checker.enabled(Rule::AmbiguousVariableName) {
checker.diagnostics.extend(names.iter().filter_map(|name| {
pycodestyle::rules::ambiguous_variable_name(name, name.range())

View File

@@ -264,7 +264,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
(Pylint, "W0406") => (RuleGroup::Stable, rules::pylint::rules::ImportSelf),
(Pylint, "W0602") => (RuleGroup::Stable, rules::pylint::rules::GlobalVariableNotAssigned),
(Pylint, "W0604") => (RuleGroup::Preview, rules::pylint::rules::GlobalAtModuleLevel),
(Pylint, "W0603") => (RuleGroup::Stable, rules::pylint::rules::GlobalStatement),
(Pylint, "W0711") => (RuleGroup::Stable, rules::pylint::rules::BinaryOpException),
(Pylint, "W1508") => (RuleGroup::Stable, rules::pylint::rules::InvalidEnvvarDefault),

View File

@@ -253,7 +253,7 @@ impl FileExemption {
#[allow(deprecated)]
let line = locator.compute_line_index(range.start());
let path_display = relativize_path(path);
warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line. For line-level suppression, omit the `ruff:` prefix.");
warn!("Unexpected `# ruff: noqa` directive at {path_display}:{line}. File-level suppression comments must appear on their own line.");
continue;
}

View File

@@ -42,7 +42,6 @@ mod tests {
#[test_case(Rule::SubprocessWithoutShellEqualsTrue, Path::new("S603.py"))]
#[test_case(Rule::SuspiciousPickleUsage, Path::new("S301.py"))]
#[test_case(Rule::SuspiciousEvalUsage, Path::new("S307.py"))]
#[test_case(Rule::SuspiciousURLOpenUsage, Path::new("S310.py"))]
#[test_case(Rule::SuspiciousTelnetUsage, Path::new("S312.py"))]
#[test_case(Rule::TryExceptContinue, Path::new("S112.py"))]
#[test_case(Rule::TryExceptPass, Path::new("S110.py"))]

View File

@@ -1,9 +1,10 @@
//! Check for calls to suspicious functions, or calls into suspicious modules.
//!
//! See: <https://bandit.readthedocs.io/en/latest/blacklists/blacklist_calls.html>
use ruff_python_ast::ExprCall;
use ruff_diagnostics::{Diagnostic, DiagnosticKind, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr, ExprCall};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -849,23 +850,10 @@ pub(crate) fn suspicious_function_call(checker: &mut Checker, call: &ExprCall) {
["" | "builtins", "eval"] => Some(SuspiciousEvalUsage.into()),
// MarkSafe
["django", "utils", "safestring", "mark_safe"] => Some(SuspiciousMarkSafeUsage.into()),
// URLOpen (`urlopen`, `urlretrieve`, `Request`)
["urllib", "request", "urlopen" | "urlretrieve" | "Request"] |
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "Request"] => {
// If the `url` argument is a string literal, allow `http` and `https` schemes.
if call.arguments.args.iter().all(|arg| !arg.is_starred_expr()) && call.arguments.keywords.iter().all(|keyword| keyword.arg.is_some()) {
if let Some(Expr::Constant(ast::ExprConstant { value: ast::Constant::Str(url), .. })) = &call.arguments.find_argument("url", 0) {
let url = url.trim_start();
if url.starts_with("http://") || url.starts_with("https://") {
return None;
}
}
}
Some(SuspiciousURLOpenUsage.into())
},
// URLOpen (`URLopener`, `FancyURLopener`)
["urllib", "request", "URLopener" | "FancyURLopener"] |
["six", "moves", "urllib", "request", "URLopener" | "FancyURLopener"] => Some(SuspiciousURLOpenUsage.into()),
// URLOpen
["urllib", "urlopen" | "urlretrieve" | "URLopener" | "FancyURLopener" | "Request"] |
["urllib", "request", "urlopen" | "urlretrieve" | "URLopener" | "FancyURLopener"] |
["six", "moves", "urllib", "request", "urlopen" | "urlretrieve" | "URLopener" | "FancyURLopener"] => Some(SuspiciousURLOpenUsage.into()),
// NonCryptographicRandom
["random", "random" | "randrange" | "randint" | "choice" | "choices" | "uniform" | "triangular"] => Some(SuspiciousNonCryptographicRandomUsage.into()),
// UnverifiedContext

View File

@@ -1,107 +0,0 @@
---
source: crates/ruff_linter/src/rules/flake8_bandit/mod.rs
---
S310.py:4:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
3 | urllib.request.urlopen(url='http://www.google.com')
4 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
5 | urllib.request.urlopen('http://www.google.com')
6 | urllib.request.urlopen('file:///foo/bar/baz')
|
S310.py:6:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
4 | urllib.request.urlopen(url='http://www.google.com', **kwargs)
5 | urllib.request.urlopen('http://www.google.com')
6 | urllib.request.urlopen('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
7 | urllib.request.urlopen(url)
|
S310.py:7:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
5 | urllib.request.urlopen('http://www.google.com')
6 | urllib.request.urlopen('file:///foo/bar/baz')
7 | urllib.request.urlopen(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
8 |
9 | urllib.request.Request(url='http://www.google.com', **kwargs)
|
S310.py:9:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
7 | urllib.request.urlopen(url)
8 |
9 | urllib.request.Request(url='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
10 | urllib.request.Request(url='http://www.google.com')
11 | urllib.request.Request('http://www.google.com')
|
S310.py:12:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
10 | urllib.request.Request(url='http://www.google.com')
11 | urllib.request.Request('http://www.google.com')
12 | urllib.request.Request('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
13 | urllib.request.Request(url)
|
S310.py:13:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
11 | urllib.request.Request('http://www.google.com')
12 | urllib.request.Request('file:///foo/bar/baz')
13 | urllib.request.Request(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
14 |
15 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
|
S310.py:15:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
13 | urllib.request.Request(url)
14 |
15 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
16 | urllib.request.URLopener().open(fullurl='http://www.google.com')
17 | urllib.request.URLopener().open('http://www.google.com')
|
S310.py:16:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
15 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
16 | urllib.request.URLopener().open(fullurl='http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
17 | urllib.request.URLopener().open('http://www.google.com')
18 | urllib.request.URLopener().open('file:///foo/bar/baz')
|
S310.py:17:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
15 | urllib.request.URLopener().open(fullurl='http://www.google.com', **kwargs)
16 | urllib.request.URLopener().open(fullurl='http://www.google.com')
17 | urllib.request.URLopener().open('http://www.google.com')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
18 | urllib.request.URLopener().open('file:///foo/bar/baz')
19 | urllib.request.URLopener().open(url)
|
S310.py:18:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
16 | urllib.request.URLopener().open(fullurl='http://www.google.com')
17 | urllib.request.URLopener().open('http://www.google.com')
18 | urllib.request.URLopener().open('file:///foo/bar/baz')
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
19 | urllib.request.URLopener().open(url)
|
S310.py:19:1: S310 Audit URL open for permitted schemes. Allowing use of `file:` or custom schemes is often unexpected.
|
17 | urllib.request.URLopener().open('http://www.google.com')
18 | urllib.request.URLopener().open('file:///foo/bar/baz')
19 | urllib.request.URLopener().open(url)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ S310
|

View File

@@ -12,7 +12,6 @@ mod tests {
use test_case::test_case;
use crate::registry::Rule;
use crate::settings::types::PreviewMode;
use crate::test::test_path;
use crate::{assert_messages, settings};
@@ -108,33 +107,6 @@ mod tests {
Ok(())
}
#[test_case(Rule::TripleSingleQuotes, Path::new("D.py"))]
#[test_case(Rule::TripleSingleQuotes, Path::new("D300.py"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
// Tests for rules with preview features
let snapshot = format!(
"preview__{}_{}",
rule_code.noqa_code(),
path.to_string_lossy()
);
let diagnostics = test_path(
Path::new("pydocstyle").join(path).as_path(),
&settings::LinterSettings {
pydocstyle: Settings {
convention: None,
ignore_decorators: BTreeSet::from_iter(["functools.wraps".to_string()]),
property_decorators: BTreeSet::from_iter([
"gi.repository.GObject.Property".to_string()
]),
},
preview: PreviewMode::Enabled,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test]
fn bom() -> Result<()> {
let diagnostics = test_path(

View File

@@ -78,14 +78,12 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let mut diagnostic =
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
if checker.settings.preview.is_enabled() {
let body = docstring.body().as_str();
if !body.ends_with('\'') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}'''{body}'''"),
docstring.range(),
)));
}
let body = docstring.body().as_str();
if !body.ends_with('\'') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}'''{body}'''"),
docstring.range(),
)));
}
checker.diagnostics.push(diagnostic);
@@ -96,14 +94,12 @@ pub(crate) fn triple_quotes(checker: &mut Checker, docstring: &Docstring) {
let mut diagnostic =
Diagnostic::new(TripleSingleQuotes { expected_quote }, docstring.range());
if checker.settings.preview.is_enabled() {
let body = docstring.body().as_str();
if !body.ends_with('"') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}\"\"\"{body}\"\"\""),
docstring.range(),
)));
}
let body = docstring.body().as_str();
if !body.ends_with('"') {
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
format!("{prefixes}\"\"\"{body}\"\"\""),
docstring.range(),
)));
}
checker.diagnostics.push(diagnostic);

View File

@@ -1,7 +1,7 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:307:5: D300 Use triple double quotes `"""`
D.py:307:5: D300 [*] Use triple double quotes `"""`
|
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 | def triple_single_quotes_raw():
@@ -10,7 +10,17 @@ D.py:307:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:312:5: D300 Use triple double quotes `"""`
Fix
304 304 |
305 305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 306 | def triple_single_quotes_raw():
307 |- r'''Summary.'''
307 |+ r"""Summary."""
308 308 |
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
D.py:312:5: D300 [*] Use triple double quotes `"""`
|
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 | def triple_single_quotes_raw_uppercase():
@@ -19,7 +29,17 @@ D.py:312:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:317:5: D300 Use triple double quotes `"""`
Fix
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 311 | def triple_single_quotes_raw_uppercase():
312 |- R'''Summary.'''
312 |+ R"""Summary."""
313 313 |
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:317:5: D300 [*] Use triple double quotes `"""`
|
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 | def single_quotes_raw():
@@ -28,7 +48,17 @@ D.py:317:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:322:5: D300 Use triple double quotes `"""`
Fix
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 316 | def single_quotes_raw():
317 |- r'Summary.'
317 |+ r"""Summary."""
318 318 |
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:322:5: D300 [*] Use triple double quotes `"""`
|
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 | def single_quotes_raw_uppercase():
@@ -37,7 +67,17 @@ D.py:322:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:328:5: D300 Use triple double quotes `"""`
Fix
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 321 | def single_quotes_raw_uppercase():
322 |- R'Summary.'
322 |+ R"""Summary."""
323 323 |
324 324 |
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:328:5: D300 [*] Use triple double quotes `"""`
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
@@ -46,7 +86,17 @@ D.py:328:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:645:5: D300 Use triple double quotes `"""`
Fix
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
326 326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 327 | def single_quotes_raw_uppercase_backslash():
328 |- R'Sum\mary.'
328 |+ R"""Sum\mary."""
329 329 |
330 330 |
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
D.py:645:5: D300 [*] Use triple double quotes `"""`
|
644 | def single_line_docstring_with_an_escaped_backslash():
645 | "\
@@ -58,7 +108,19 @@ D.py:645:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:649:5: D300 Use triple double quotes `"""`
Fix
642 642 |
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
645 |- "\
646 |- "
645 |+ """\
646 |+ """
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
D.py:649:5: D300 [*] Use triple double quotes `"""`
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
@@ -68,7 +130,17 @@ D.py:649:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:654:5: D300 Use triple double quotes `"""`
Fix
646 646 | "
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
649 |+ """After this docstring there's another statement on the same line separated by a semicolon.""" ; priorities=1
650 650 | def sort_services(self):
651 651 | pass
652 652 |
D.py:654:5: D300 [*] Use triple double quotes `"""`
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
@@ -76,7 +148,17 @@ D.py:654:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:658:5: D300 Use triple double quotes `"""`
Fix
651 651 | pass
652 652 |
653 653 | class StatementOnSameLineAsDocstring:
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
654 |+ """After this docstring there's another statement on the same line separated by a semicolon."""; priorities=1
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
D.py:658:5: D300 [*] Use triple double quotes `"""`
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
@@ -86,7 +168,17 @@ D.py:658:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D.py:664:5: D300 Use triple double quotes `"""`
Fix
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
658 |- "After this docstring there's a comment." # priorities=1
658 |+ """After this docstring there's a comment.""" # priorities=1
659 659 | def sort_services(self):
660 660 | pass
661 661 |
D.py:664:5: D300 [*] Use triple double quotes `"""`
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
@@ -96,4 +188,13 @@ D.py:664:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
Fix
661 661 |
662 662 |
663 663 | def newline_after_closing_quote(self):
664 |- "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
664 |+ """We enforce a newline after the closing quote for a multi-line docstring \
665 |+ but continuations shouldn't be considered multi-line"""

View File

@@ -9,7 +9,7 @@ D300.py:6:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
D300.py:10:5: D300 Use triple double quotes `"""`
D300.py:10:5: D300 [*] Use triple double quotes `"""`
|
9 | def contains_quote():
10 | 'Sum"\\mary.'
@@ -17,4 +17,11 @@ D300.py:10:5: D300 Use triple double quotes `"""`
|
= help: Convert to triple double quotes
Fix
7 7 |
8 8 |
9 9 | def contains_quote():
10 |- 'Sum"\\mary.'
10 |+ """Sum"\\mary."""

View File

@@ -1,11 +1,15 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
bom.py:1:1: D300 Use triple double quotes `"""`
bom.py:1:1: D300 [*] Use triple double quotes `"""`
|
1 | ''' SAM macro definitions '''
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
1 |-''' SAM macro definitions '''
1 |+""" SAM macro definitions """

View File

@@ -1,200 +0,0 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D.py:307:5: D300 [*] Use triple double quotes `"""`
|
305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 | def triple_single_quotes_raw():
307 | r'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
304 304 |
305 305 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
306 306 | def triple_single_quotes_raw():
307 |- r'''Summary.'''
307 |+ r"""Summary."""
308 308 |
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
D.py:312:5: D300 [*] Use triple double quotes `"""`
|
310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 | def triple_single_quotes_raw_uppercase():
312 | R'''Summary.'''
| ^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
309 309 |
310 310 | @expect('D300: Use """triple double quotes""" (found \'\'\'-quotes)')
311 311 | def triple_single_quotes_raw_uppercase():
312 |- R'''Summary.'''
312 |+ R"""Summary."""
313 313 |
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:317:5: D300 [*] Use triple double quotes `"""`
|
315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 | def single_quotes_raw():
317 | r'Summary.'
| ^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
314 314 |
315 315 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
316 316 | def single_quotes_raw():
317 |- r'Summary.'
317 |+ r"""Summary."""
318 318 |
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:322:5: D300 [*] Use triple double quotes `"""`
|
320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 | def single_quotes_raw_uppercase():
322 | R'Summary.'
| ^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
319 319 |
320 320 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
321 321 | def single_quotes_raw_uppercase():
322 |- R'Summary.'
322 |+ R"""Summary."""
323 323 |
324 324 |
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
D.py:328:5: D300 [*] Use triple double quotes `"""`
|
326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 | def single_quotes_raw_uppercase_backslash():
328 | R'Sum\mary.'
| ^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
325 325 | @expect('D300: Use """triple double quotes""" (found \'-quotes)')
326 326 | @expect('D301: Use r""" if any backslashes in a docstring')
327 327 | def single_quotes_raw_uppercase_backslash():
328 |- R'Sum\mary.'
328 |+ R"""Sum\mary."""
329 329 |
330 330 |
331 331 | @expect('D301: Use r""" if any backslashes in a docstring')
D.py:645:5: D300 [*] Use triple double quotes `"""`
|
644 | def single_line_docstring_with_an_escaped_backslash():
645 | "\
| _____^
646 | | "
| |_____^ D300
647 |
648 | class StatementOnSameLineAsDocstring:
|
= help: Convert to triple double quotes
Fix
642 642 |
643 643 |
644 644 | def single_line_docstring_with_an_escaped_backslash():
645 |- "\
646 |- "
645 |+ """\
646 |+ """
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
D.py:649:5: D300 [*] Use triple double quotes `"""`
|
648 | class StatementOnSameLineAsDocstring:
649 | "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
650 | def sort_services(self):
651 | pass
|
= help: Convert to triple double quotes
Fix
646 646 | "
647 647 |
648 648 | class StatementOnSameLineAsDocstring:
649 |- "After this docstring there's another statement on the same line separated by a semicolon." ; priorities=1
649 |+ """After this docstring there's another statement on the same line separated by a semicolon.""" ; priorities=1
650 650 | def sort_services(self):
651 651 | pass
652 652 |
D.py:654:5: D300 [*] Use triple double quotes `"""`
|
653 | class StatementOnSameLineAsDocstring:
654 | "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
651 651 | pass
652 652 |
653 653 | class StatementOnSameLineAsDocstring:
654 |- "After this docstring there's another statement on the same line separated by a semicolon."; priorities=1
654 |+ """After this docstring there's another statement on the same line separated by a semicolon."""; priorities=1
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
D.py:658:5: D300 [*] Use triple double quotes `"""`
|
657 | class CommentAfterDocstring:
658 | "After this docstring there's a comment." # priorities=1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ D300
659 | def sort_services(self):
660 | pass
|
= help: Convert to triple double quotes
Fix
655 655 |
656 656 |
657 657 | class CommentAfterDocstring:
658 |- "After this docstring there's a comment." # priorities=1
658 |+ """After this docstring there's a comment.""" # priorities=1
659 659 | def sort_services(self):
660 660 | pass
661 661 |
D.py:664:5: D300 [*] Use triple double quotes `"""`
|
663 | def newline_after_closing_quote(self):
664 | "We enforce a newline after the closing quote for a multi-line docstring \
| _____^
665 | | but continuations shouldn't be considered multi-line"
| |_________________________________________________________^ D300
|
= help: Convert to triple double quotes
Fix
661 661 |
662 662 |
663 663 | def newline_after_closing_quote(self):
664 |- "We enforce a newline after the closing quote for a multi-line docstring \
665 |- but continuations shouldn't be considered multi-line"
664 |+ """We enforce a newline after the closing quote for a multi-line docstring \
665 |+ but continuations shouldn't be considered multi-line"""

View File

@@ -1,27 +0,0 @@
---
source: crates/ruff_linter/src/rules/pydocstyle/mod.rs
---
D300.py:6:5: D300 Use triple double quotes `"""`
|
5 | def ends_in_quote():
6 | 'Sum\\mary."'
| ^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
D300.py:10:5: D300 [*] Use triple double quotes `"""`
|
9 | def contains_quote():
10 | 'Sum"\\mary.'
| ^^^^^^^^^^^^^ D300
|
= help: Convert to triple double quotes
Fix
7 7 |
8 8 |
9 9 | def contains_quote():
10 |- 'Sum"\\mary.'
10 |+ """Sum"\\mary."""

View File

@@ -138,7 +138,6 @@ mod tests {
#[test_case(Rule::NoSelfUse, Path::new("no_self_use.py"))]
#[test_case(Rule::MisplacedBareRaise, Path::new("misplaced_bare_raise.py"))]
#[test_case(Rule::LiteralMembership, Path::new("literal_membership.py"))]
#[test_case(Rule::GlobalAtModuleLevel, Path::new("global_at_module_level.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(

View File

@@ -1,34 +0,0 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::Stmt;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for uses of the `global` keyword at the module level.
///
/// ## Why is this bad?
/// The `global` keyword is used within functions to indicate that a name
/// refers to a global variable, rather than a local variable.
///
/// At the module level, all names are global by default, so the `global`
/// keyword is redundant.
#[violation]
pub struct GlobalAtModuleLevel;
impl Violation for GlobalAtModuleLevel {
#[derive_message_formats]
fn message(&self) -> String {
format!("`global` at module level is redundant")
}
}
/// PLW0604
pub(crate) fn global_at_module_level(checker: &mut Checker, stmt: &Stmt) {
if checker.semantic().current_scope().kind.is_module() {
checker
.diagnostics
.push(Diagnostic::new(GlobalAtModuleLevel, stmt.range()));
}
}

View File

@@ -14,7 +14,6 @@ pub(crate) use comparison_with_itself::*;
pub(crate) use continue_in_finally::*;
pub(crate) use duplicate_bases::*;
pub(crate) use eq_without_hash::*;
pub(crate) use global_at_module_level::*;
pub(crate) use global_statement::*;
pub(crate) use global_variable_not_assigned::*;
pub(crate) use import_self::*;
@@ -79,7 +78,6 @@ mod comparison_with_itself;
mod continue_in_finally;
mod duplicate_bases;
mod eq_without_hash;
mod global_at_module_level;
mod global_statement;
mod global_variable_not_assigned;
mod import_self;

View File

@@ -15,6 +15,7 @@ pub enum ConstantType {
Float,
Int,
Str,
Tuple,
}
impl TryFrom<&Constant> for ConstantType {

View File

@@ -1,21 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
global_at_module_level.py:1:1: PLW0604 `global` at module level is redundant
|
1 | global price # W0604
| ^^^^^^^^^^^^ PLW0604
2 |
3 | price = 25
|
global_at_module_level.py:6:5: PLW0604 `global` at module level is redundant
|
5 | if True:
6 | global X # W0604
| ^^^^^^^^ PLW0604
7 |
8 | def no_error():
|

View File

@@ -16,6 +16,7 @@ doctest = false
[dependencies]
ruff_python_trivia = { path = "../ruff_python_trivia" }
serde_derive_internals = "0.29.0"
proc-macro2 = { workspace = true }
quote = { workspace = true }

View File

@@ -18,7 +18,6 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenS
Ok(quote! {
impl crate::configuration::CombinePluginOptions for #ident {
fn combine(self, other: Self) -> Self {
#[allow(deprecated)]
Self {
#(
#output

View File

@@ -1,15 +1,16 @@
use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, quote_spanned};
use syn::meta::ParseNestedMeta;
use serde_derive_internals::Ctxt;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
AngleBracketedGenericArguments, Attribute, Data, DataStruct, DeriveInput, ExprLit, Field,
Fields, Lit, LitStr, Meta, Path, PathArguments, PathSegment, Type, TypePath,
Fields, Lit, LitStr, Path, PathArguments, PathSegment, Token, Type, TypePath,
};
use ruff_python_trivia::textwrap::dedent;
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
let DeriveInput {
ident,
data,
@@ -37,25 +38,14 @@ pub(crate) fn derive_impl(input: DeriveInput) -> syn::Result<TokenStream> {
.any(|attr| attr.path().is_ident("option_group"))
{
output.push(handle_option_group(field)?);
} else if let Some(serde) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("serde"))
{
} else if let Type::Path(ty) = &field.ty {
let serde_field = serde_field_metadata(field)?;
// If a field has the `serde(flatten)` attribute, flatten the options into the parent
// by calling `Type::record` instead of `visitor.visit_set`
if let (Type::Path(ty), Meta::List(list)) = (&field.ty, &serde.meta) {
for token in list.tokens.clone() {
if let TokenTree::Ident(ident) = token {
if ident == "flatten" {
let ty_name = ty.path.require_ident()?;
output.push(quote_spanned!(
ident.span() => (#ty_name::record(visit))
));
break;
}
}
}
if serde_field.flatten() {
let ty_name = ty.path.require_ident()?;
output.push(quote_spanned!(ident.span() => (#ty_name::record(visit))));
}
}
}
@@ -189,29 +179,12 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
default,
value_type,
example,
} = parse_field_attributes(attr)?;
} = attr.parse_args::<FieldAttributes>()?;
let kebab_name = LitStr::new(&ident.to_string().replace('_', "-"), ident.span());
let deprecated = if let Some(deprecated) = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("deprecated"))
{
fn quote_option(option: Option<String>) -> TokenStream {
match option {
None => quote!(None),
Some(value) => quote!(Some(#value)),
}
}
let deprecated = parse_deprecated_attribute(deprecated)?;
let note = quote_option(deprecated.note);
let since = quote_option(deprecated.since);
quote!(Some(crate::options_base::Deprecated { since: #since, message: #note }))
} else {
quote!(None)
};
let serde_field = serde_field_metadata(field)?;
let attributed_aliases = serde_field.aliases();
let aliases = quote!(BTreeSet::from_iter([#(#attributed_aliases),*]));
Ok(quote_spanned!(
ident.span() => {
@@ -220,7 +193,7 @@ fn handle_option(field: &Field, attr: &Attribute) -> syn::Result<proc_macro2::To
default: &#default,
value_type: &#value_type,
example: &#example,
deprecated: #deprecated
aliases: #aliases
})
}
))
@@ -233,109 +206,53 @@ struct FieldAttributes {
example: String,
}
fn parse_field_attributes(attribute: &Attribute) -> syn::Result<FieldAttributes> {
let mut default = None;
let mut value_type = None;
let mut example = None;
attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("default") {
default = Some(get_string_literal(&meta, "default", "option")?.value());
} else if meta.path.is_ident("value_type") {
value_type = Some(get_string_literal(&meta, "value_type", "option")?.value());
} else if meta.path.is_ident("example") {
let example_text = get_string_literal(&meta, "value_type", "option")?.value();
example = Some(dedent(&example_text).trim_matches('\n').to_string());
} else {
return Err(syn::Error::new(
meta.path.span(),
format!(
"Deprecated meta {:?} is not supported by ruff's option macro.",
meta.path.get_ident()
),
));
impl Parse for FieldAttributes {
fn parse(input: ParseStream) -> syn::Result<Self> {
let default = _parse_key_value(input, "default")?;
input.parse::<Comma>()?;
let value_type = _parse_key_value(input, "value_type")?;
input.parse::<Comma>()?;
let example = _parse_key_value(input, "example")?;
if !input.is_empty() {
input.parse::<Comma>()?;
}
Ok(())
})?;
let Some(default) = default else {
return Err(syn::Error::new(attribute.span(), "Mandatory `default` field is missing in `#[option]` attribute. Specify the default using `#[option(default=\"..\")]`."));
};
let Some(value_type) = value_type else {
return Err(syn::Error::new(attribute.span(), "Mandatory `value_type` field is missing in `#[option]` attribute. Specify the value type using `#[option(value_type=\"..\")]`."));
};
let Some(example) = example else {
return Err(syn::Error::new(attribute.span(), "Mandatory `example` field is missing in `#[option]` attribute. Add an example using `#[option(example=\"..\")]`."));
};
Ok(FieldAttributes {
default,
value_type,
example,
})
}
fn parse_deprecated_attribute(attribute: &Attribute) -> syn::Result<DeprecatedAttribute> {
let mut deprecated = DeprecatedAttribute::default();
attribute.parse_nested_meta(|meta| {
if meta.path.is_ident("note") {
deprecated.note = Some(get_string_literal(&meta, "note", "deprecated")?.value());
} else if meta.path.is_ident("since") {
deprecated.since = Some(get_string_literal(&meta, "since", "deprecated")?.value());
} else {
return Err(syn::Error::new(
meta.path.span(),
format!(
"Deprecated meta {:?} is not supported by ruff's option macro.",
meta.path.get_ident()
),
));
}
Ok(())
})?;
Ok(deprecated)
}
fn get_string_literal(
meta: &ParseNestedMeta,
meta_name: &str,
attribute_name: &str,
) -> syn::Result<syn::LitStr> {
let expr: syn::Expr = meta.value()?.parse()?;
let mut value = &expr;
while let syn::Expr::Group(e) = value {
value = &e.expr;
}
if let syn::Expr::Lit(ExprLit {
lit: Lit::Str(lit), ..
}) = value
{
let suffix = lit.suffix();
if !suffix.is_empty() {
return Err(syn::Error::new(
lit.span(),
format!("unexpected suffix `{suffix}` on string literal"),
));
}
Ok(lit.clone())
} else {
Err(syn::Error::new(
expr.span(),
format!("expected {attribute_name} attribute to be a string: `{meta_name} = \"...\"`"),
))
Ok(Self {
default,
value_type,
example: dedent(&example).trim_matches('\n').to_string(),
})
}
}
#[derive(Default, Debug)]
struct DeprecatedAttribute {
since: Option<String>,
note: Option<String>,
fn _parse_key_value(input: ParseStream, name: &str) -> syn::Result<String> {
let ident: proc_macro2::Ident = input.parse()?;
if ident != name {
return Err(syn::Error::new(
ident.span(),
format!("Expected `{name}` name"),
));
}
input.parse::<Token![=]>()?;
let value: Lit = input.parse()?;
match &value {
Lit::Str(v) => Ok(v.value()),
_ => Err(syn::Error::new(value.span(), "Expected literal string")),
}
}
fn serde_field_metadata(field: &Field) -> syn::Result<serde_derive_internals::attr::Field> {
let context = Ctxt::new();
let field = serde_derive_internals::attr::Field::from_ast(
&context,
0,
field,
None,
&serde_derive_internals::attr::Default::Default,
);
context.check()?;
Ok(field)
}

View File

@@ -333,6 +333,7 @@ pub enum ComparableConstant<'a> {
Str { value: &'a str, unicode: bool },
Bytes(&'a [u8]),
Int(&'a ast::Int),
Tuple(Vec<ComparableConstant<'a>>),
Float(u64),
Complex { real: u64, imag: u64 },
Ellipsis,

View File

@@ -4817,7 +4817,7 @@ pub enum AnyNodeRef<'a> {
ElifElseClause(&'a ast::ElifElseClause),
}
impl<'a> AnyNodeRef<'a> {
impl AnyNodeRef<'_> {
pub fn as_ptr(&self) -> NonNull<()> {
match self {
AnyNodeRef::ModModule(node) => NonNull::from(*node).cast(),
@@ -5456,9 +5456,9 @@ impl<'a> AnyNodeRef<'a> {
)
}
pub fn visit_preorder<'b, V>(&'b self, visitor: &mut V)
pub fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'b> + ?Sized,
V: PreorderVisitor<'a> + ?Sized,
{
match self {
AnyNodeRef::ModModule(node) => node.visit_preorder(visitor),
@@ -5544,66 +5544,6 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::ElifElseClause(node) => node.visit_preorder(visitor),
}
}
/// The last child of the last branch, if the node has multiple branches.
pub fn last_child_in_body(&self) -> Option<AnyNodeRef<'a>> {
let body = match self {
AnyNodeRef::StmtFunctionDef(ast::StmtFunctionDef { body, .. })
| AnyNodeRef::StmtClassDef(ast::StmtClassDef { body, .. })
| AnyNodeRef::StmtWith(ast::StmtWith { body, .. })
| AnyNodeRef::MatchCase(MatchCase { body, .. })
| AnyNodeRef::ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler {
body,
..
})
| AnyNodeRef::ElifElseClause(ast::ElifElseClause { body, .. }) => body,
AnyNodeRef::StmtIf(ast::StmtIf {
body,
elif_else_clauses,
..
}) => elif_else_clauses.last().map_or(body, |clause| &clause.body),
AnyNodeRef::StmtFor(ast::StmtFor { body, orelse, .. })
| AnyNodeRef::StmtWhile(ast::StmtWhile { body, orelse, .. }) => {
if orelse.is_empty() {
body
} else {
orelse
}
}
AnyNodeRef::StmtMatch(ast::StmtMatch { cases, .. }) => {
return cases.last().map(AnyNodeRef::from);
}
AnyNodeRef::StmtTry(ast::StmtTry {
body,
handlers,
orelse,
finalbody,
..
}) => {
if finalbody.is_empty() {
if orelse.is_empty() {
if handlers.is_empty() {
body
} else {
return handlers.last().map(AnyNodeRef::from);
}
} else {
orelse
}
} else {
finalbody
}
}
// Not a node that contains an indented child node.
_ => return None,
};
body.last().map(AnyNodeRef::from)
}
}
impl<'a> From<&'a ast::ModModule> for AnyNodeRef<'a> {

View File

@@ -4,44 +4,35 @@ use ruff_text_size::{Ranged, TextLen, TextRange};
use crate::AnyNodeRef;
use crate::ExpressionRef;
/// Returns an iterator over the ranges of the optional parentheses surrounding an expression.
///
/// E.g. for `((f()))` with `f()` as expression, the iterator returns the ranges (1, 6) and (0, 7).
///
/// Note that without a parent the range can be inaccurate, e.g. `f(a)` we falsely return a set of
/// parentheses around `a` even if the parentheses actually belong to `f`. That is why you should
/// generally prefer [`parenthesized_range`].
pub fn parentheses_iterator<'a>(
expr: ExpressionRef<'a>,
parent: Option<AnyNodeRef>,
comment_ranges: &'a CommentRanges,
source: &'a str,
) -> impl Iterator<Item = TextRange> + 'a {
let right_tokenizer = if let Some(parent) = parent {
// If the parent is a node that brings its own parentheses, exclude the closing parenthesis
// from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which
// the open and close parentheses are part of the `Arguments` node.
//
// There are a few other nodes that may have their own parentheses, but are fine to exclude:
// - `Parameters`: The parameters to a function definition. Any expressions would represent
// default arguments, and so must be preceded by _at least_ the parameter name. As such,
// we won't mistake any parentheses for the opening and closing parentheses on the
// `Parameters` node itself.
// - `Tuple`: The elements of a tuple. The only risk is a single-element tuple (e.g., `(x,)`),
// which must have a trailing comma anyway.
let exclusive_parent_end = if parent.is_arguments() {
parent.end() - ")".text_len()
} else {
parent.end()
};
SimpleTokenizer::new(source, TextRange::new(expr.end(), exclusive_parent_end))
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
/// parenthesized; or `None`, if the expression is not parenthesized.
pub fn parenthesized_range(
expr: ExpressionRef,
parent: AnyNodeRef,
comment_ranges: &CommentRanges,
source: &str,
) -> Option<TextRange> {
// If the parent is a node that brings its own parentheses, exclude the closing parenthesis
// from our search range. Otherwise, we risk matching on calls, like `func(x)`, for which
// the open and close parentheses are part of the `Arguments` node.
//
// There are a few other nodes that may have their own parentheses, but are fine to exclude:
// - `Parameters`: The parameters to a function definition. Any expressions would represent
// default arguments, and so must be preceded by _at least_ the parameter name. As such,
// we won't mistake any parentheses for the opening and closing parentheses on the
// `Parameters` node itself.
// - `Tuple`: The elements of a tuple. The only risk is a single-element tuple (e.g., `(x,)`),
// which must have a trailing comma anyway.
let exclusive_parent_end = if parent.is_arguments() {
parent.end() - ")".text_len()
} else {
SimpleTokenizer::starts_at(expr.end(), source)
parent.end()
};
let right_tokenizer = right_tokenizer
.skip_trivia()
.take_while(|token| token.kind == SimpleTokenKind::RParen);
let right_tokenizer =
SimpleTokenizer::new(source, TextRange::new(expr.end(), exclusive_parent_end))
.skip_trivia()
.take_while(|token| token.kind == SimpleTokenKind::RParen);
let left_tokenizer = BackwardsTokenizer::up_to(expr.start(), source, comment_ranges)
.skip_trivia()
@@ -52,16 +43,6 @@ pub fn parentheses_iterator<'a>(
// the `right_tokenizer` is exhausted.
right_tokenizer
.zip(left_tokenizer)
.last()
.map(|(right, left)| TextRange::new(left.start(), right.end()))
}
/// Returns the [`TextRange`] of a given expression including parentheses, if the expression is
/// parenthesized; or `None`, if the expression is not parenthesized.
pub fn parenthesized_range(
expr: ExpressionRef,
parent: AnyNodeRef,
comment_ranges: &CommentRanges,
source: &str,
) -> Option<TextRange> {
parentheses_iterator(expr, Some(parent), comment_ranges, source).last()
}

View File

@@ -75,7 +75,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
if [
aaaaaaaaaaaaa,
@@ -84,7 +84,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
# Right only can break
if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
@@ -94,7 +94,7 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & [
aaaaaaaaaaaaa,
@@ -103,7 +103,7 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
# Left or right can break
@@ -114,7 +114,7 @@ if [2222, 333] & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -123,7 +123,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & [2222, 333]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -132,7 +132,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & [fffffffffffffffff, gggggggggggggggggggg, hhhhhhhhhhhhhhhhhhhhh, iiiiiiiiiiiiiiii, jjjjjjjjjjjjj]:
pass
...
if (
# comment
@@ -152,7 +152,7 @@ if (
]:
pass
pass
...
# Nesting
if (aaaa + b) & [
@@ -162,7 +162,7 @@ if (aaaa + b) & [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
pass
...
if [
fffffffffffffffff,
@@ -171,7 +171,7 @@ if [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
] & (a + b):
pass
...
if [
@@ -185,7 +185,7 @@ if [
a
+ b
):
pass
...
if (
[
@@ -199,7 +199,7 @@ if (
# comment
a + b
):
pass
...
# Unstable formatting in https://github.com/realtyem/synapse-unraid/blob/unraid_develop/synapse/handlers/presence.py

View File

@@ -16,7 +16,7 @@ if (
and self._returncode
and self._proc.poll()
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -26,14 +26,14 @@ if (
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas
and aaaaaaaaaaaaaaaaa
):
pass
...
if [2222, 333] and [
@@ -43,7 +43,7 @@ if [2222, 333] and [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,

View File

@@ -102,6 +102,3 @@ aaaaaaaaaaaaaaaaaaaaa = [
c # negative decimal
]
# Parenthesized targets and iterators.
[x for (x) in y]
[x for x in (y)]

View File

@@ -65,7 +65,7 @@ d3 = "d"[
# Spacing around the colon(s)
def a():
pass
...
e00 = "e"[:]
e01 = "e"[:1]

View File

@@ -139,18 +139,18 @@ if not \
# Regression: https://github.com/astral-sh/ruff/issues/5338
if a and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
if (
not
# comment
a):
pass
...
if (
not # comment
a):
pass
...
# Regression test for: https://github.com/astral-sh/ruff/issues/7423
if True:
@@ -161,14 +161,3 @@ if True:
+ "WARNING: Removing listed files. Do you really want to continue. yes/n)? "
):
pass
# https://github.com/astral-sh/ruff/issues/7448
x = (
# a
not # b
# c
( # d
# e
True
)
)

View File

@@ -51,7 +51,7 @@ aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbb
for converter in connection.ops.get_db_converters(
expression
) + expression.get_db_converters(connection):
pass
...
aaa = (

View File

@@ -1,7 +1,6 @@
###
# Blank lines around functions
###
import sys
x = 1
@@ -160,97 +159,3 @@ def f():
# comment
x = 1
def f():
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
if True:
def double(s):
return s + s
#
print("below comment function")
if True:
class A:
x = 1
#
print("below comment class")
if True:
def double(s):
return s + s
#
print("below comment function 2")
if True:
def double(s):
return s + s
#
def outer():
def inner():
pass
print("below nested functions")
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
def outer():
def inner():
pass
print("below nested functions")
class Path:
if sys.version_info >= (3, 11):
def joinpath(self): ...
# The .open method comes from pathlib.pyi and should be kept in sync.
@overload
def open(self): ...
def fakehttp():
class FakeHTTPConnection:
if mock_close:
def close(self):
pass
FakeHTTPConnection.fakedata = fakedata
if True:
if False:
def x():
def y():
pass
#comment
print()
# NOTE: Please keep this the last block in this file. This tests that we don't insert
# empty line(s) at the end of the file due to nested function
if True:
def nested_trailing_function():
pass

View File

@@ -1,113 +0,0 @@
list_with_parenthesized_elements1 = [
# comment leading outer
(
# comment leading inner
1 + 2 # comment trailing inner
) # comment trailing outer
]
list_with_parenthesized_elements2 = [
# leading outer
(1 + 2)
]
list_with_parenthesized_elements3 = [
# leading outer
(1 + 2) # trailing outer
]
list_with_parenthesized_elements4 = [
# leading outer
(1 + 2), # trailing outer
]
list_with_parenthesized_elements5 = [
(1), # trailing outer
(2), # trailing outer
]
nested_parentheses1 = (
(
(
1
) # i
) # j
) # k
nested_parentheses2 = [
(
(
(
1
) # i
# i2
) # j
# j2
) # k
# k2
]
nested_parentheses3 = (
( # a
( # b
1
) # i
) # j
) # k
nested_parentheses4 = [
# a
( # b
# c
( # d
# e
( #f
1
) # i
# i2
) # j
# j2
) # k
# k2
]
x = (
# unary comment
not
# in-between comment
(
# leading inner
"a"
),
not # in-between comment
(
# leading inner
"b"
),
not
( # in-between comment
# leading inner
"c"
),
# 1
not # 2
( # 3
# 4
"d"
)
)
if (
# unary comment
not
# in-between comment
(
# leading inner
1
)
):
pass
# Make sure we keep a inside the parentheses
# https://github.com/astral-sh/ruff/issues/7892
x = (
# a
( # b
1
)
)

View File

@@ -15,23 +15,23 @@ for aVeryLongNameThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGoesOnAnd
pass
else:
pass
...
for (
x,
y,
) in z: # comment
pass
...
# remove brackets around x,y but keep them around z,w
for (x, y) in (z, w):
pass
...
# type comment
for x in (): # type: int
pass
...
# Tuple parentheses for iterable.
for x in 1, 2, 3:

View File

@@ -19,12 +19,12 @@ else: # 12 trailing else condition
if x == y:
if y == z:
pass
...
if a == b:
pass
...
else: # trailing comment
pass
...
# trailing else comment
@@ -34,11 +34,11 @@ elif aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
2222222222222222222222,
3333333333
]:
pass
...
else:
pass
...
# Regression test: Don't drop the trailing comment by associating it with the elif
# instead of the else.

View File

@@ -206,9 +206,9 @@ match pattern_singleton:
case (
True # trailing
):
pass
...
case False:
pass
...
match foo:
@@ -406,39 +406,39 @@ match pattern_match_class:
case Point2D(
# own line
):
pass
...
case (
Point2D
# own line
()
):
pass
...
case Point2D( # end of line line
):
pass
...
case Point2D( # end of line
0, 0
):
pass
...
case Point2D(0, 0):
pass
...
case Point2D(
( # end of line
# own line
0
), 0):
pass
...
case Point3D(x=0, y=0, z=000000000000000000000000000000000000000000000000000000000000000000000000000000000):
pass
...
case Bar(0, a=None, b="hello"):
pass
...
case FooBar(# leading
# leading
@@ -449,7 +449,7 @@ match pattern_match_class:
# trailing
# trailing
):
pass
...
case A(
b # b
@@ -481,26 +481,26 @@ match pattern_match_or:
# own line 4
c # trailing 5
):
pass
...
case (
(a)
| # trailing
( b )
):
pass
...
case (a|b|c):
pass
...
case foo | bar | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh:
pass
...
case ( # end of line
a | b
# own line
):
pass
...
# Single-element tuples.

View File

@@ -1,72 +1,72 @@
try:
pass
...
except:
pass
...
try:
pass
...
except (KeyError): # should remove brackets and be a single line
pass
...
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try:
pass
...
except:
pass
...
try:
pass
...
except (Exception, Exception, Exception, Exception, Exception, Exception, Exception) as exc: # splits exception over multiple lines
pass
...
try:
pass
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2
@@ -74,21 +74,21 @@ except:
# try/except*, mostly the same as try
try: # try
pass
...
# end of body
# before except
except* (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except* KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# try and try star are statements with body
# Minimized from https://github.com/python/cpython/blob/99b00efd5edfd5b26bf9e2a35cbfc96277fdcbb1/Lib/getpass.py#L68-L91

View File

@@ -15,7 +15,7 @@ while aVeryLongConditionThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGo
pass
else:
pass
...
while (
some_condition(unformatted, args) and anotherCondition or aThirdCondition

View File

@@ -1,16 +1,16 @@
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
# trailing
with a, a: # after colon
pass
...
# trailing
with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
):
pass
...
# trailing
@@ -19,7 +19,7 @@ with (
, # comma
b # c
): # colon
pass
...
with (
@@ -30,7 +30,7 @@ with (
, # comma
c # c
): # colon
pass # body
... # body
# body trailing own
with (
@@ -42,14 +42,14 @@ with (
with (a,): # magic trailing comma
pass
...
with (a): # should remove brackets
pass
...
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
pass
...
# currently unparsable by black: https://github.com/psf/black/issues/3678
@@ -60,41 +60,45 @@ with (a, *b):
with (
# leading comment
a) as b: pass
a) as b: ...
with (
# leading comment
a as b
): pass
): ...
with (
a as b
# trailing comment
): pass
): ...
with (
a as (
# leading comment
b
)
): pass
): ...
with (
a as (
b
# trailing comment
)
): pass
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
a # trailing same line comment
# trailing own line comment
as b
): pass
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: pass
) as b: ...
with (
(a
@@ -102,7 +106,7 @@ with (
)
as # trailing as same line comment
b # trailing b same line comment
): pass
): ...
with (
# comment
@@ -153,7 +157,7 @@ with (
CtxManager2() as example2,
CtxManager2() as example2,
):
pass
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
@@ -161,7 +165,7 @@ with [
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
pass
...
# Comments on open parentheses
with ( # comment
@@ -169,7 +173,7 @@ with ( # comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
( # inner comment
@@ -178,25 +182,25 @@ with ( # outer comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
CtxManager()
) as example:
pass
...
with ( # outer comment
CtxManager()
) as example, ( # inner comment
CtxManager2()
) as example2:
pass
...
with ( # outer comment
CtxManager1(),
CtxManager2(),
) as example:
pass
...
with ( # outer comment
( # inner comment
@@ -204,7 +208,7 @@ with ( # outer comment
),
CtxManager2(),
) as example:
pass
...
# Breaking of with items.
with (test # bar

View File

@@ -17,7 +17,7 @@ class Test:
c = 30
while a == 10:
print(a)
...
# trailing comment with one line before
@@ -26,7 +26,7 @@ while a == 10:
d = 40
while b == 20:
print(b)
...
# no empty line before
e = 50 # one empty line before

View File

@@ -347,9 +347,9 @@ fn handle_end_of_line_comment_around_body<'a>(
// ```
// The first earlier branch filters out ambiguities e.g. around try-except-finally.
if let Some(preceding) = comment.preceding_node() {
if let Some(last_child) = preceding.last_child_in_body() {
if let Some(last_child) = last_child_in_body(preceding) {
let innermost_child =
std::iter::successors(Some(last_child), AnyNodeRef::last_child_in_body)
std::iter::successors(Some(last_child), |parent| last_child_in_body(*parent))
.last()
.unwrap_or(last_child);
return CommentPlacement::trailing(innermost_child, comment);
@@ -670,7 +670,7 @@ fn handle_own_line_comment_after_branch<'a>(
preceding: AnyNodeRef<'a>,
locator: &Locator,
) -> CommentPlacement<'a> {
let Some(last_child) = preceding.last_child_in_body() else {
let Some(last_child) = last_child_in_body(preceding) else {
return CommentPlacement::Default(comment);
};
@@ -734,7 +734,7 @@ fn handle_own_line_comment_after_branch<'a>(
return CommentPlacement::trailing(last_child_in_parent, comment);
}
Ordering::Greater => {
if let Some(nested_child) = last_child_in_parent.last_child_in_body() {
if let Some(nested_child) = last_child_in_body(last_child_in_parent) {
// The comment belongs to the inner block.
parent = Some(last_child_in_parent);
last_child_in_parent = nested_child;
@@ -1878,7 +1878,8 @@ fn handle_lambda_comment<'a>(
CommentPlacement::Default(comment)
}
/// Move comment between a unary op and its operand before the unary op by marking them as trailing.
/// Attach trailing end-of-line comments on the operator as dangling comments on the enclosing
/// node.
///
/// For example, given:
/// ```python
@@ -1895,27 +1896,26 @@ fn handle_unary_op_comment<'a>(
unary_op: &'a ast::ExprUnaryOp,
locator: &Locator,
) -> CommentPlacement<'a> {
let mut tokenizer = SimpleTokenizer::new(
locator.contents(),
TextRange::new(unary_op.start(), unary_op.operand.start()),
)
.skip_trivia();
let op_token = tokenizer.next();
debug_assert!(op_token.is_some_and(|token| matches!(
token.kind,
SimpleTokenKind::Tilde
| SimpleTokenKind::Not
| SimpleTokenKind::Plus
| SimpleTokenKind::Minus
)));
let up_to = tokenizer
.find(|token| token.kind == SimpleTokenKind::LParen)
.map_or(unary_op.operand.start(), |lparen| lparen.start());
if comment.end() < up_to {
CommentPlacement::leading(unary_op, comment)
} else {
CommentPlacement::Default(comment)
if comment.line_position().is_own_line() {
return CommentPlacement::Default(comment);
}
if comment.start() > unary_op.operand.start() {
return CommentPlacement::Default(comment);
}
let tokenizer = SimpleTokenizer::new(
locator.contents(),
TextRange::new(comment.start(), unary_op.operand.start()),
);
if tokenizer
.skip_trivia()
.any(|token| token.kind == SimpleTokenKind::LParen)
{
return CommentPlacement::Default(comment);
}
CommentPlacement::dangling(comment.enclosing_node(), comment)
}
/// Attach an end-of-line comment immediately following an open bracket as a dangling comment on
@@ -2176,6 +2176,65 @@ where
right.is_some_and(|right| left.ptr_eq(right.into()))
}
/// The last child of the last branch, if the node has multiple branches.
fn last_child_in_body(node: AnyNodeRef) -> Option<AnyNodeRef> {
let body = match node {
AnyNodeRef::StmtFunctionDef(ast::StmtFunctionDef { body, .. })
| AnyNodeRef::StmtClassDef(ast::StmtClassDef { body, .. })
| AnyNodeRef::StmtWith(ast::StmtWith { body, .. })
| AnyNodeRef::MatchCase(MatchCase { body, .. })
| AnyNodeRef::ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler {
body, ..
})
| AnyNodeRef::ElifElseClause(ast::ElifElseClause { body, .. }) => body,
AnyNodeRef::StmtIf(ast::StmtIf {
body,
elif_else_clauses,
..
}) => elif_else_clauses.last().map_or(body, |clause| &clause.body),
AnyNodeRef::StmtFor(ast::StmtFor { body, orelse, .. })
| AnyNodeRef::StmtWhile(ast::StmtWhile { body, orelse, .. }) => {
if orelse.is_empty() {
body
} else {
orelse
}
}
AnyNodeRef::StmtMatch(ast::StmtMatch { cases, .. }) => {
return cases.last().map(AnyNodeRef::from);
}
AnyNodeRef::StmtTry(ast::StmtTry {
body,
handlers,
orelse,
finalbody,
..
}) => {
if finalbody.is_empty() {
if orelse.is_empty() {
if handlers.is_empty() {
body
} else {
return handlers.last().map(AnyNodeRef::from);
}
} else {
orelse
}
} else {
finalbody
}
}
// Not a node that contains an indented child node.
_ => return None,
};
body.last().map(AnyNodeRef::from)
}
/// Returns `true` if `statement` is the first statement in an alternate `body` (e.g. the else of an if statement)
fn is_first_statement_in_alternate_body(statement: AnyNodeRef, has_body: AnyNodeRef) -> bool {
match has_body {

View File

@@ -1,18 +1,18 @@
use std::cmp::Ordering;
use std::slice;
use itertools::Itertools;
use ruff_formatter::{
write, FormatOwnedWithRule, FormatRefWithRule, FormatRule, FormatRuleWithOptions,
};
use ruff_python_ast as ast;
use ruff_python_ast::parenthesize::parentheses_iterator;
use ruff_python_ast::visitor::preorder::{walk_expr, PreorderVisitor};
use ruff_python_ast::{AnyNodeRef, Constant, Expr, ExpressionRef, Operator};
use ruff_python_ast::AnyNodeRef;
use ruff_python_ast::{Constant, Expr, ExpressionRef, Operator};
use ruff_python_trivia::CommentRanges;
use ruff_text_size::Ranged;
use crate::builders::parenthesize_if_expands;
use crate::comments::{leading_comments, trailing_comments, LeadingDanglingTrailingComments};
use crate::comments::leading_comments;
use crate::context::{NodeLevel, WithNodeLevel};
use crate::expression::parentheses::{
is_expression_parenthesized, optional_parentheses, parenthesized, NeedsParentheses,
@@ -102,6 +102,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
Expr::Slice(expr) => expr.format().fmt(f),
Expr::IpyEscapeCommand(expr) => expr.format().fmt(f),
});
let parenthesize = match parentheses {
Parentheses::Preserve => is_expression_parenthesized(
expression.into(),
@@ -112,13 +113,32 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
// Fluent style means we already have parentheses
Parentheses::Never => false,
};
if parenthesize {
let comment = f.context().comments().clone();
let node_comments = comment.leading_dangling_trailing(expression);
if !node_comments.has_leading() && !node_comments.has_trailing() {
parenthesized("(", &format_expr, ")").fmt(f)
// Any comments on the open parenthesis of a `node`.
//
// For example, `# comment` in:
// ```python
// ( # comment
// foo.bar
// )
// ```
let comments = f.context().comments().clone();
let leading = comments.leading(expression);
if let Some((index, open_parenthesis_comment)) = leading
.iter()
.find_position(|comment| comment.line_position().is_end_of_line())
{
write!(
f,
[
leading_comments(&leading[..index]),
parenthesized("(", &format_expr, ")")
.with_dangling_comments(std::slice::from_ref(open_parenthesis_comment))
]
)
} else {
format_with_parentheses_comments(expression, &node_comments, f)
parenthesized("(", &format_expr, ")").fmt(f)
}
} else {
let level = match f.context().node_level() {
@@ -135,185 +155,6 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
}
}
/// The comments below are trailing on the addition, but it's also outside the
/// parentheses
/// ```python
/// x = [
/// # comment leading
/// (1 + 2) # comment trailing
/// ]
/// ```
/// as opposed to
/// ```python
/// x = [(
/// # comment leading
/// 1 + 2 # comment trailing
/// )]
/// ```
/// , where the comments are inside the parentheses. That is also affects list
/// formatting, where we want to avoid moving the comments after the comma inside
/// the parentheses:
/// ```python
/// data = [
/// (
/// b"\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"
/// b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
/// ), # Point (0 0)
/// ]
/// ```
/// We could mark those comments as trailing in list but it's easier to handle
/// them here too.
///
/// So given
/// ```python
/// x = [
/// # comment leading outer
/// (
/// # comment leading inner
/// 1 + 2 # comment trailing inner
/// ) # comment trailing outer
/// ]
/// ```
/// we want to keep the inner an outer comments outside the parentheses and the inner ones inside.
/// This is independent of whether they are own line or end-of-line comments, though end-of-line
/// comments can become own line comments when we discard nested parentheses.
///
/// Style decision: When there are multiple nested parentheses around an expression, we consider the
/// outermost parentheses the relevant ones and discard the others.
fn format_with_parentheses_comments(
expression: &Expr,
node_comments: &LeadingDanglingTrailingComments,
f: &mut PyFormatter,
) -> FormatResult<()> {
// First part: Split the comments
// TODO(konstin): We don't have the parent, which is a problem:
// ```python
// f(
// # a
// (a)
// )
// ```
// gets formatted as
// ```python
// f(
// (
// # a
// a
// )
// )
// ```
let range_with_parens = parentheses_iterator(
expression.into(),
None,
f.context().comments().ranges(),
f.context().source(),
)
.last();
let (leading_split, trailing_split) = if let Some(range_with_parens) = range_with_parens {
let leading_split = node_comments
.leading
.partition_point(|comment| comment.start() < range_with_parens.start());
let trailing_split = node_comments
.trailing
.partition_point(|comment| comment.start() < range_with_parens.end());
(leading_split, trailing_split)
} else {
(0, node_comments.trailing.len())
};
let (leading_outer, leading_inner) = node_comments.leading.split_at(leading_split);
let (trailing_inner, trailing_outer) = node_comments.trailing.split_at(trailing_split);
// Preserve an opening parentheses comment
// ```python
// a = ( # opening parentheses comment
// # leading inner
// 1
// )
// ```
let (parentheses_comment, leading_inner) = match leading_inner.split_first() {
Some((first, rest)) if first.line_position().is_end_of_line() => {
(slice::from_ref(first), rest)
}
_ => (Default::default(), node_comments.leading),
};
// Second Part: Format
// The code order is a bit strange here, we format:
// * outer leading comment
// * opening parenthesis
// * opening parenthesis comment
// * inner leading comments
// * the expression itself
// * inner trailing comments
// * the closing parenthesis
// * outer trailing comments
let fmt_fields = format_with(|f| match expression {
Expr::BoolOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::NamedExpr(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::BinOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::UnaryOp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Lambda(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::IfExp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Dict(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Set(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::ListComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::SetComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::DictComp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::GeneratorExp(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Await(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Yield(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::YieldFrom(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Compare(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Call(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::FormattedValue(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::FString(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Constant(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Attribute(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Subscript(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Starred(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Name(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::List(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Tuple(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::Slice(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
Expr::IpyEscapeCommand(expr) => FormatNodeRule::fmt_fields(expr.format().rule(), expr, f),
});
leading_comments(leading_outer).fmt(f)?;
// Custom FormatNodeRule::fmt variant that only formats the inner comments
let format_node_rule_fmt = format_with(|f| {
// No need to handle suppression comments, those are statement only
leading_comments(leading_inner).fmt(f)?;
let is_source_map_enabled = f.options().source_map_generation().is_enabled();
if is_source_map_enabled {
source_position(expression.start()).fmt(f)?;
}
fmt_fields.fmt(f)?;
if is_source_map_enabled {
source_position(expression.end()).fmt(f)?;
}
trailing_comments(trailing_inner).fmt(f)
});
// The actual parenthesized formatting
parenthesized("(", &format_node_rule_fmt, ")")
.with_dangling_comments(parentheses_comment)
.fmt(f)?;
trailing_comments(trailing_outer).fmt(f)?;
Ok(())
}
/// Wraps an expression in an optional parentheses except if its [`NeedsParentheses::needs_parentheses`] implementation
/// indicates that it is okay to omit the parentheses. For example, parentheses can always be omitted for lists,
/// because they already bring their own parentheses.

View File

@@ -60,6 +60,7 @@ where
}
self.fmt_fields(node, f)?;
self.fmt_dangling_comments(node_comments.dangling, f)?;
if is_source_map_enabled {
source_position(node.end()).fmt(f)?;

View File

@@ -1,6 +1,6 @@
use ruff_formatter::{format_args, write, Buffer, FormatResult};
use ruff_formatter::{format_args, write, Buffer, FormatError, FormatResult};
use ruff_python_ast::{Comprehension, Expr};
use ruff_python_trivia::{find_only_token_in_range, SimpleTokenKind};
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};
use crate::comments::{leading_comments, trailing_comments, SourceComment};
@@ -42,14 +42,27 @@ impl FormatNodeRule<Comprehension> for FormatComprehension {
dangling_item_comments.partition_point(|comment| comment.end() < target.start()),
);
let in_token = find_only_token_in_range(
TextRange::new(target.end(), iter.start()),
SimpleTokenKind::In,
let maybe_in_token = SimpleTokenizer::new(
f.context().source(),
);
TextRange::new(target.end(), iter.start()),
)
.skip_trivia()
.next();
let Some(
in_keyword @ SimpleToken {
kind: SimpleTokenKind::In,
..
},
) = maybe_in_token
else {
return Err(FormatError::syntax_error(
"Expected `in` keyword between the `target` and `iter`.",
));
};
let (before_in_comments, dangling_comments) = dangling_comments.split_at(
dangling_comments.partition_point(|comment| comment.end() < in_token.start()),
dangling_comments.partition_point(|comment| comment.end() < in_keyword.start()),
);
let (trailing_in_comments, dangling_if_comments) = dangling_comments

View File

@@ -155,65 +155,13 @@ impl FormatRule<Suite, PyFormatContext<'_>> for FormatSuite {
while let Some(following) = iter.next() {
let following_comments = comments.leading_dangling_trailing(following);
let needs_empty_lines = if is_class_or_function_definition(following) {
// Here we insert empty lines even if the preceding has a trailing own line comment
true
} else {
// Find nested class or function definitions that need an empty line after them.
//
// ```python
// def f():
// if True:
//
// def double(s):
// return s + s
//
// print("below function")
// ```
std::iter::successors(
Some(AnyNodeRef::from(preceding)),
AnyNodeRef::last_child_in_body,
)
.take_while(|last_child|
// If there is a comment between preceding and following the empty lines were
// inserted before the comment by preceding and there are no extra empty lines
// after the comment.
// ```python
// class Test:
// def a(self):
// pass
// # trailing comment
//
//
// # two lines before, one line after
//
// c = 30
// ````
// This also includes nested class/function definitions, so we stop recursing
// once we see a node with a trailing own line comment:
// ```python
// def f():
// if True:
//
// def double(s):
// return s + s
//
// # nested trailing own line comment
// print("below function with trailing own line comment")
// ```
!comments.has_trailing_own_line(*last_child))
.any(|last_child| {
matches!(
last_child,
AnyNodeRef::StmtFunctionDef(_) | AnyNodeRef::StmtClassDef(_)
)
})
};
// Add empty lines before and after a function or class definition. If the preceding
// node is a function or class, and contains trailing comments, then the statement
// itself will add the requisite empty lines when formatting its comments.
if needs_empty_lines {
if (is_class_or_function_definition(preceding)
&& !preceding_comments.has_trailing_own_line())
|| is_class_or_function_definition(following)
{
if source_type.is_stub() {
stub_file_empty_lines(
self.kind,

View File

@@ -320,6 +320,17 @@ long_unmergable_string_with_pragma = (
"formatting"
)
@@ -221,8 +217,8 @@
func_with_bad_comma(
(
"This is a really long string argument to a function that has a trailing comma"
- " which should NOT be there."
- ), # comment after comma
+ " which should NOT be there." # comment after comma
+ ),
)
func_with_bad_parens_that_wont_fit_in_one_line(
```
## Ruff Output
@@ -544,8 +555,8 @@ func_with_bad_comma(
func_with_bad_comma(
(
"This is a really long string argument to a function that has a trailing comma"
" which should NOT be there."
), # comment after comma
" which should NOT be there." # comment after comma
),
)
func_with_bad_parens_that_wont_fit_in_one_line(

View File

@@ -73,7 +73,7 @@ with hmm_but_this_should_get_two_preceding_newlines():
elif os.name == "nt":
try:
import msvcrt
@@ -54,7 +53,6 @@
@@ -54,12 +53,10 @@
class IHopeYouAreHavingALovelyDay:
def __call__(self):
print("i_should_be_followed_by_only_one_newline")
@@ -81,6 +81,11 @@ with hmm_but_this_should_get_two_preceding_newlines():
else:
def foo():
pass
-
with hmm_but_this_should_get_two_preceding_newlines():
pass
```
## Ruff Output
@@ -146,7 +151,6 @@ else:
def foo():
pass
with hmm_but_this_should_get_two_preceding_newlines():
pass
```

View File

@@ -81,7 +81,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
if [
aaaaaaaaaaaaa,
@@ -90,7 +90,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
# Right only can break
if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
@@ -100,7 +100,7 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & [
aaaaaaaaaaaaa,
@@ -109,7 +109,7 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
# Left or right can break
@@ -120,7 +120,7 @@ if [2222, 333] & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -129,7 +129,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & [2222, 333]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -138,7 +138,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & [fffffffffffffffff, gggggggggggggggggggg, hhhhhhhhhhhhhhhhhhhhh, iiiiiiiiiiiiiiii, jjjjjjjjjjjjj]:
pass
...
if (
# comment
@@ -158,7 +158,7 @@ if (
]:
pass
pass
...
# Nesting
if (aaaa + b) & [
@@ -168,7 +168,7 @@ if (aaaa + b) & [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
pass
...
if [
fffffffffffffffff,
@@ -177,7 +177,7 @@ if [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
] & (a + b):
pass
...
if [
@@ -191,7 +191,7 @@ if [
a
+ b
):
pass
...
if (
[
@@ -205,7 +205,7 @@ if (
# comment
a + b
):
pass
...
# Unstable formatting in https://github.com/realtyem/synapse-unraid/blob/unraid_develop/synapse/handlers/presence.py
@@ -532,7 +532,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & aaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
if (
[
@@ -544,7 +544,7 @@ if (
]
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
...
# Right only can break
if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
@@ -554,7 +554,7 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaa & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -566,7 +566,7 @@ if (
eeeeeeeeee,
]
):
pass
...
# Left or right can break
@@ -577,7 +577,7 @@ if [2222, 333] & [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -586,7 +586,7 @@ if [
dddddddddddddddddddd,
eeeeeeeeee,
] & [2222, 333]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -601,7 +601,7 @@ if [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
pass
...
if (
# comment
@@ -621,7 +621,7 @@ if (
]:
pass
pass
...
# Nesting
if (aaaa + b) & [
@@ -631,7 +631,7 @@ if (aaaa + b) & [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
]:
pass
...
if [
fffffffffffffffff,
@@ -640,7 +640,7 @@ if [
iiiiiiiiiiiiiiii,
jjjjjjjjjjjjj,
] & (a + b):
pass
...
if [
@@ -653,7 +653,7 @@ if [
# comment
a + b
):
pass
...
if (
[
@@ -667,7 +667,7 @@ if (
# comment
a + b
):
pass
...
# Unstable formatting in https://github.com/realtyem/synapse-unraid/blob/unraid_develop/synapse/handlers/presence.py

View File

@@ -22,7 +22,7 @@ if (
and self._returncode
and self._proc.poll()
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -32,14 +32,14 @@ if (
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas
and aaaaaaaaaaaaaaaaa
):
pass
...
if [2222, 333] and [
@@ -49,7 +49,7 @@ if [2222, 333] and [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,
@@ -213,7 +213,7 @@ if (
and self._returncode
and self._proc.poll()
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -223,14 +223,14 @@ if (
and aaaaaaaaaaaaaaaaaaaaaaaaaa
and aaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
...
if (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas
and aaaaaaaaaaaaaaaaa
):
pass
...
if [2222, 333] and [
@@ -240,7 +240,7 @@ if [2222, 333] and [
dddddddddddddddddddd,
eeeeeeeeee,
]:
pass
...
if [
aaaaaaaaaaaaa,

View File

@@ -458,9 +458,8 @@ func(
)
func(
(
# outer comment
# inner comment
# outer comment
( # inner comment
[]
)
)

View File

@@ -108,9 +108,6 @@ aaaaaaaaaaaaaaaaaaaaa = [
c # negative decimal
]
# Parenthesized targets and iterators.
[x for (x) in y]
[x for x in (y)]
```
## Output
@@ -250,10 +247,6 @@ aaaaaaaaaaaaaaaaaaaaa = [
for components in b # pylint: disable=undefined-loop-variable # integer 1 may only have decimal 01-09
+ c # negative decimal
]
# Parenthesized targets and iterators.
[x for (x) in y]
[x for x in (y)]
```

View File

@@ -71,7 +71,7 @@ d3 = "d"[
# Spacing around the colon(s)
def a():
pass
...
e00 = "e"[:]
e01 = "e"[:1]
@@ -184,7 +184,7 @@ d3 = "d"[
# Spacing around the colon(s)
def a():
pass
...
e00 = "e"[:]

View File

@@ -145,18 +145,18 @@ if not \
# Regression: https://github.com/astral-sh/ruff/issues/5338
if a and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
if (
not
# comment
a):
pass
...
if (
not # comment
a):
pass
...
# Regression test for: https://github.com/astral-sh/ruff/issues/7423
if True:
@@ -167,17 +167,6 @@ if True:
+ "WARNING: Removing listed files. Do you really want to continue. yes/n)? "
):
pass
# https://github.com/astral-sh/ruff/issues/7448
x = (
# a
not # b
# c
( # d
# e
True
)
)
```
## Output
@@ -228,31 +217,35 @@ if +(
pass
if (
not
# comment
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
~
# comment
~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
-
# comment
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
+
# comment
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -261,8 +254,8 @@ if (
if (
# unary comment
# operand comment
not (
# operand comment
# comment
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
@@ -293,31 +286,28 @@ if not (
## Trailing operator comments
if ( # comment
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
if (
not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # comment
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # comment
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # comment
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
if (
# comment
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # comment
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
):
pass
@@ -334,18 +324,17 @@ if (
and not aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
):
pass
...
if (
not
# comment
not a
a
):
pass
...
if ( # comment
not a
):
pass
if not a: # comment
...
# Regression test for: https://github.com/astral-sh/ruff/issues/7423
if True:
@@ -356,17 +345,6 @@ if True:
+ "WARNING: Removing listed files. Do you really want to continue. yes/n)? "
):
pass
# https://github.com/astral-sh/ruff/issues/7448
x = (
# a
# b
# c
not ( # d
# e
True
)
)
```

View File

@@ -57,7 +57,7 @@ aaaaaaaa = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbb
for converter in connection.ops.get_db_converters(
expression
) + expression.get_db_converters(connection):
pass
...
aaa = (
@@ -161,7 +161,7 @@ aaaaaaaa = (
for converter in connection.ops.get_db_converters(
expression
) + expression.get_db_converters(connection):
pass
...
aaa = (

View File

@@ -7,7 +7,6 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/newlines.p
###
# Blank lines around functions
###
import sys
x = 1
@@ -166,107 +165,13 @@ def f():
# comment
x = 1
def f():
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
if True:
def double(s):
return s + s
#
print("below comment function")
if True:
class A:
x = 1
#
print("below comment class")
if True:
def double(s):
return s + s
#
print("below comment function 2")
if True:
def double(s):
return s + s
#
def outer():
def inner():
pass
print("below nested functions")
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
def outer():
def inner():
pass
print("below nested functions")
class Path:
if sys.version_info >= (3, 11):
def joinpath(self): ...
# The .open method comes from pathlib.pyi and should be kept in sync.
@overload
def open(self): ...
def fakehttp():
class FakeHTTPConnection:
if mock_close:
def close(self):
pass
FakeHTTPConnection.fakedata = fakedata
if True:
if False:
def x():
def y():
pass
#comment
print()
# NOTE: Please keep this the last block in this file. This tests that we don't insert
# empty line(s) at the end of the file due to nested function
if True:
def nested_trailing_function():
pass```
```
## Output
```py
###
# Blank lines around functions
###
import sys
x = 1
@@ -434,118 +339,6 @@ def f():
# comment
x = 1
def f():
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
if True:
def double(s):
return s + s
#
print("below comment function")
if True:
class A:
x = 1
#
print("below comment class")
if True:
def double(s):
return s + s
#
print("below comment function 2")
if True:
def double(s):
return s + s
#
def outer():
def inner():
pass
print("below nested functions")
if True:
def double(s):
return s + s
print("below function")
if True:
class A:
x = 1
print("below class")
def outer():
def inner():
pass
print("below nested functions")
class Path:
if sys.version_info >= (3, 11):
def joinpath(self):
...
# The .open method comes from pathlib.pyi and should be kept in sync.
@overload
def open(self):
...
def fakehttp():
class FakeHTTPConnection:
if mock_close:
def close(self):
pass
FakeHTTPConnection.fakedata = fakedata
if True:
if False:
def x():
def y():
pass
# comment
print()
# NOTE: Please keep this the last block in this file. This tests that we don't insert
# empty line(s) at the end of the file due to nested function
if True:
def nested_trailing_function():
pass
```

View File

@@ -1,225 +0,0 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/parentheses/expression_parentheses_comments.py
---
## Input
```py
list_with_parenthesized_elements1 = [
# comment leading outer
(
# comment leading inner
1 + 2 # comment trailing inner
) # comment trailing outer
]
list_with_parenthesized_elements2 = [
# leading outer
(1 + 2)
]
list_with_parenthesized_elements3 = [
# leading outer
(1 + 2) # trailing outer
]
list_with_parenthesized_elements4 = [
# leading outer
(1 + 2), # trailing outer
]
list_with_parenthesized_elements5 = [
(1), # trailing outer
(2), # trailing outer
]
nested_parentheses1 = (
(
(
1
) # i
) # j
) # k
nested_parentheses2 = [
(
(
(
1
) # i
# i2
) # j
# j2
) # k
# k2
]
nested_parentheses3 = (
( # a
( # b
1
) # i
) # j
) # k
nested_parentheses4 = [
# a
( # b
# c
( # d
# e
( #f
1
) # i
# i2
) # j
# j2
) # k
# k2
]
x = (
# unary comment
not
# in-between comment
(
# leading inner
"a"
),
not # in-between comment
(
# leading inner
"b"
),
not
( # in-between comment
# leading inner
"c"
),
# 1
not # 2
( # 3
# 4
"d"
)
)
if (
# unary comment
not
# in-between comment
(
# leading inner
1
)
):
pass
# Make sure we keep a inside the parentheses
# https://github.com/astral-sh/ruff/issues/7892
x = (
# a
( # b
1
)
)
```
## Output
```py
list_with_parenthesized_elements1 = [
# comment leading outer
(
# comment leading inner
1 + 2 # comment trailing inner
) # comment trailing outer
]
list_with_parenthesized_elements2 = [
# leading outer
(1 + 2)
]
list_with_parenthesized_elements3 = [
# leading outer
(1 + 2) # trailing outer
]
list_with_parenthesized_elements4 = [
# leading outer
(1 + 2), # trailing outer
]
list_with_parenthesized_elements5 = [
(1), # trailing outer
(2), # trailing outer
]
nested_parentheses1 = (
1 # i # j
) # k
nested_parentheses2 = [
(
1 # i
# i2
# j
# j2
) # k
# k2
]
nested_parentheses3 = ( # a
# b
1 # i # j
) # k
nested_parentheses4 = [
# a
( # b
# c
# d
# e
# f
1 # i
# i2
# j
# j2
) # k
# k2
]
x = (
# unary comment
# in-between comment
not (
# leading inner
"a"
),
# in-between comment
not (
# leading inner
"b"
),
not ( # in-between comment
# leading inner
"c"
),
# 1
# 2
not ( # 3
# 4
"d"
),
)
if (
# unary comment
# in-between comment
not (
# leading inner
1
)
):
pass
# Make sure we keep a inside the parentheses
# https://github.com/astral-sh/ruff/issues/7892
x = (
# a
# b
1
)
```

View File

@@ -21,23 +21,23 @@ for aVeryLongNameThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGoesOnAnd
pass
else:
pass
...
for (
x,
y,
) in z: # comment
pass
...
# remove brackets around x,y but keep them around z,w
for (x, y) in (z, w):
pass
...
# type comment
for x in (): # type: int
pass
...
# Tuple parentheses for iterable.
for x in 1, 2, 3:
@@ -97,23 +97,23 @@ for aVeryLongNameThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGoesOnAnd
pass
else:
pass
...
for (
x,
y,
) in z: # comment
pass
...
# remove brackets around x,y but keep them around z,w
for x, y in (z, w):
pass
...
# type comment
for x in (): # type: int
pass
...
# Tuple parentheses for iterable.
for x in 1, 2, 3:

View File

@@ -25,12 +25,12 @@ else: # 12 trailing else condition
if x == y:
if y == z:
pass
...
if a == b:
pass
...
else: # trailing comment
pass
...
# trailing else comment
@@ -40,11 +40,11 @@ elif aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
2222222222222222222222,
3333333333
]:
pass
...
else:
pass
...
# Regression test: Don't drop the trailing comment by associating it with the elif
# instead of the else.
@@ -325,12 +325,12 @@ else: # 12 trailing else condition
if x == y:
if y == z:
pass
...
if a == b:
pass
...
else: # trailing comment
pass
...
# trailing else comment
@@ -340,11 +340,11 @@ elif aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
2222222222222222222222,
3333333333,
]:
pass
...
else:
pass
...
# Regression test: Don't drop the trailing comment by associating it with the elif
# instead of the else.
@@ -410,7 +410,6 @@ else:
pass
# 3
if True:
print("a") # 1
elif True:

View File

@@ -212,9 +212,9 @@ match pattern_singleton:
case (
True # trailing
):
pass
...
case False:
pass
...
match foo:
@@ -412,39 +412,39 @@ match pattern_match_class:
case Point2D(
# own line
):
pass
...
case (
Point2D
# own line
()
):
pass
...
case Point2D( # end of line line
):
pass
...
case Point2D( # end of line
0, 0
):
pass
...
case Point2D(0, 0):
pass
...
case Point2D(
( # end of line
# own line
0
), 0):
pass
...
case Point3D(x=0, y=0, z=000000000000000000000000000000000000000000000000000000000000000000000000000000000):
pass
...
case Bar(0, a=None, b="hello"):
pass
...
case FooBar(# leading
# leading
@@ -455,7 +455,7 @@ match pattern_match_class:
# trailing
# trailing
):
pass
...
case A(
b # b
@@ -487,26 +487,26 @@ match pattern_match_or:
# own line 4
c # trailing 5
):
pass
...
case (
(a)
| # trailing
( b )
):
pass
...
case (a|b|c):
pass
...
case foo | bar | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh:
pass
...
case ( # end of line
a | b
# own line
):
pass
...
# Single-element tuples.
@@ -770,9 +770,9 @@ match pattern_singleton:
):
pass
case True: # trailing
pass
...
case False:
pass
...
match foo:
@@ -996,26 +996,26 @@ match pattern_match_class:
case Point2D(
# own line
):
pass
...
case (
Point2D
# own line
()
):
pass
...
case Point2D( # end of line line
):
pass
...
case Point2D( # end of line
0, 0
):
pass
...
case Point2D(0, 0):
pass
...
case Point2D(
( # end of line
@@ -1024,17 +1024,17 @@ match pattern_match_class:
),
0,
):
pass
...
case Point3D(
x=0,
y=0,
z=000000000000000000000000000000000000000000000000000000000000000000000000000000000,
):
pass
...
case Bar(0, a=None, b="hello"):
pass
...
case FooBar( # leading
# leading
@@ -1045,7 +1045,7 @@ match pattern_match_class:
# trailing
# trailing
):
pass
...
case A(
# b
@@ -1075,7 +1075,7 @@ match pattern_match_or:
# own line 4
| c # trailing 5
):
pass
...
case (
(
@@ -1083,24 +1083,24 @@ match pattern_match_or:
)
| (b)
):
pass
...
case a | b | c:
pass
...
case (
foo
| bar
| aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
):
pass
...
case ( # end of line
a
| b
# own line
):
pass
...
# Single-element tuples.

View File

@@ -5,74 +5,74 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/
## Input
```py
try:
pass
...
except:
pass
...
try:
pass
...
except (KeyError): # should remove brackets and be a single line
pass
...
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try:
pass
...
except:
pass
...
try:
pass
...
except (Exception, Exception, Exception, Exception, Exception, Exception, Exception) as exc: # splits exception over multiple lines
pass
...
try:
pass
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2
@@ -80,21 +80,21 @@ except:
# try/except*, mostly the same as try
try: # try
pass
...
# end of body
# before except
except* (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except* KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# try and try star are statements with body
# Minimized from https://github.com/python/cpython/blob/99b00efd5edfd5b26bf9e2a35cbfc96277fdcbb1/Lib/getpass.py#L68-L91
@@ -177,67 +177,67 @@ finally:
## Output
```py
try:
pass
...
except:
pass
...
try:
pass
...
except KeyError: # should remove brackets and be a single line
pass
...
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try: # try
pass
...
# end of body
# before except
except (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# with line breaks
try:
pass
...
except:
pass
...
try:
pass
...
except (
Exception,
Exception,
@@ -247,11 +247,11 @@ except (
Exception,
Exception,
) as exc: # splits exception over multiple lines
pass
...
try:
pass
...
except:
a = 10 # trailing comment1
b = 11 # trailing comment2
@@ -259,21 +259,21 @@ except:
# try/except*, mostly the same as try
try: # try
pass
...
# end of body
# before except
except* (Exception, ValueError) as exc: # except line
pass
...
# before except 2
except* KeyError as key: # except line 2
pass
...
# in body 2
# before else
else:
pass
...
# before finally
finally:
pass
...
# try and try star are statements with body
# Minimized from https://github.com/python/cpython/blob/99b00efd5edfd5b26bf9e2a35cbfc96277fdcbb1/Lib/getpass.py#L68-L91
@@ -311,7 +311,6 @@ finally:
pass
# d
try:
pass # a
except ZeroDivisionError:

View File

@@ -21,7 +21,7 @@ while aVeryLongConditionThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGo
pass
else:
pass
...
while (
some_condition(unformatted, args) and anotherCondition or aThirdCondition
@@ -55,7 +55,7 @@ while aVeryLongConditionThatSpillsOverToTheNextLineBecauseItIsExtremelyLongAndGo
pass
else:
pass
...
while (
some_condition(unformatted, args) and anotherCondition or aThirdCondition

View File

@@ -5,18 +5,18 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/
## Input
```py
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
# trailing
with a, a: # after colon
pass
...
# trailing
with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
):
pass
...
# trailing
@@ -25,7 +25,7 @@ with (
, # comma
b # c
): # colon
pass
...
with (
@@ -36,7 +36,7 @@ with (
, # comma
c # c
): # colon
pass # body
... # body
# body trailing own
with (
@@ -48,14 +48,14 @@ with (
with (a,): # magic trailing comma
pass
...
with (a): # should remove brackets
pass
...
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
pass
...
# currently unparsable by black: https://github.com/psf/black/issues/3678
@@ -66,41 +66,45 @@ with (a, *b):
with (
# leading comment
a) as b: pass
a) as b: ...
with (
# leading comment
a as b
): pass
): ...
with (
a as b
# trailing comment
): pass
): ...
with (
a as (
# leading comment
b
)
): pass
): ...
with (
a as (
b
# trailing comment
)
): pass
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: ...
with (
a # trailing same line comment
# trailing own line comment
as b
): pass
): ...
with (a # trailing same line comment
# trailing own line comment
) as b: pass
) as b: ...
with (
(a
@@ -108,7 +112,7 @@ with (
)
as # trailing as same line comment
b # trailing b same line comment
): pass
): ...
with (
# comment
@@ -159,7 +163,7 @@ with (
CtxManager2() as example2,
CtxManager2() as example2,
):
pass
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
@@ -167,7 +171,7 @@ with [
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
pass
...
# Comments on open parentheses
with ( # comment
@@ -175,7 +179,7 @@ with ( # comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
( # inner comment
@@ -184,25 +188,25 @@ with ( # outer comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
CtxManager()
) as example:
pass
...
with ( # outer comment
CtxManager()
) as example, ( # inner comment
CtxManager2()
) as example2:
pass
...
with ( # outer comment
CtxManager1(),
CtxManager2(),
) as example:
pass
...
with ( # outer comment
( # inner comment
@@ -210,7 +214,7 @@ with ( # outer comment
),
CtxManager2(),
) as example:
pass
...
# Breaking of with items.
with (test # bar
@@ -314,18 +318,18 @@ if True:
## Output
```py
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
pass
...
# trailing
with a, a: # after colon
pass
...
# trailing
with (
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,
):
pass
...
# trailing
@@ -333,7 +337,7 @@ with (
a, # a # comma
b, # c
): # colon
pass
...
with (
@@ -343,7 +347,7 @@ with (
), # b # comma
c, # c
): # colon
pass # body
... # body
# body trailing own
with (
@@ -358,14 +362,14 @@ with (
with (
a,
): # magic trailing comma
pass
...
with a: # should remove brackets
pass
...
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
pass
...
# currently unparsable by black: https://github.com/psf/black/issues/3678
@@ -378,43 +382,49 @@ with (
# leading comment
a
) as b:
pass
...
with (
# leading comment
a as b
):
pass
...
with (
a as b
# trailing comment
):
pass
...
with a as (
# leading comment
b
):
pass
...
with a as (
b
# trailing comment
):
pass
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
pass
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
pass
...
with (
a # trailing same line comment
# trailing own line comment
) as b:
...
with (
(
@@ -424,7 +434,7 @@ with (
b
) # trailing b same line comment
):
pass
...
with (
# comment
@@ -475,7 +485,7 @@ with (
CtxManager2() as example2,
CtxManager2() as example2,
):
pass
...
with [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
@@ -483,7 +493,7 @@ with [
"cccccccccccccccccccccccccccccccccccccccccc",
dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
pass
...
# Comments on open parentheses
with ( # comment
@@ -491,7 +501,7 @@ with ( # comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
( # inner comment
@@ -500,25 +510,25 @@ with ( # outer comment
CtxManager2() as example2,
CtxManager3() as example3,
):
pass
...
with ( # outer comment
CtxManager()
) as example:
pass
...
with ( # outer comment
CtxManager()
) as example, ( # inner comment
CtxManager2()
) as example2:
pass
...
with ( # outer comment
CtxManager1(),
CtxManager2(),
) as example:
pass
...
with ( # outer comment
( # inner comment
@@ -526,7 +536,7 @@ with ( # outer comment
),
CtxManager2(),
) as example:
pass
...
# Breaking of with items.
with test as ( # bar # foo

View File

@@ -23,7 +23,7 @@ class Test:
c = 30
while a == 10:
print(a)
...
# trailing comment with one line before
@@ -32,7 +32,7 @@ while a == 10:
d = 40
while b == 20:
print(b)
...
# no empty line before
e = 50 # one empty line before
@@ -61,7 +61,7 @@ class Test:
c = 30
while a == 10:
print(a)
...
# trailing comment with one line before
@@ -70,7 +70,7 @@ while a == 10:
d = 40
while b == 20:
print(b)
...
# no empty line before
e = 50 # one empty line before

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_shrinking"
version = "0.1.1"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -567,22 +567,6 @@ pub struct LintConfiguration {
impl LintConfiguration {
fn from_options(options: LintOptions, project_root: &Path) -> Result<Self> {
#[allow(deprecated)]
let ignore = options
.common
.ignore
.into_iter()
.flatten()
.chain(options.common.extend_ignore.into_iter().flatten())
.collect();
#[allow(deprecated)]
let unfixable = options
.common
.unfixable
.into_iter()
.flatten()
.chain(options.common.extend_unfixable.into_iter().flatten())
.collect();
Ok(LintConfiguration {
exclude: options.exclude.map(|paths| {
paths
@@ -597,10 +581,22 @@ impl LintConfiguration {
rule_selections: vec![RuleSelection {
select: options.common.select,
ignore,
ignore: options
.common
.ignore
.into_iter()
.flatten()
.chain(options.common.extend_ignore.into_iter().flatten())
.collect(),
extend_select: options.common.extend_select.unwrap_or_default(),
fixable: options.common.fixable,
unfixable,
unfixable: options
.common
.unfixable
.into_iter()
.flatten()
.chain(options.common.extend_unfixable.into_iter().flatten())
.collect(),
extend_fixable: options.common.extend_fixable.unwrap_or_default(),
}],
extend_safe_fixes: options.common.extend_safe_fixes.unwrap_or_default(),

View File

@@ -471,6 +471,9 @@ pub struct LintCommonOptions {
/// A list of rule codes or prefixes to ignore, in addition to those
/// specified by `ignore`.
///
/// This option has been **deprecated** in favor of `ignore`
/// since its usage is now interchangeable with `ignore`.
#[option(
default = "[]",
value_type = "list[RuleSelector]",
@@ -479,9 +482,7 @@ pub struct LintCommonOptions {
extend-ignore = ["F841"]
"#
)]
#[deprecated(
note = "The `extend-ignore` option is now interchangeable with `ignore`. Please update your configuration to use the `ignore` option instead."
)]
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extend_ignore: Option<Vec<RuleSelector>>,
/// A list of rule codes or prefixes to enable, in addition to those
@@ -510,9 +511,10 @@ pub struct LintCommonOptions {
/// A list of rule codes or prefixes to consider non-auto-fixable, in addition to those
/// specified by `unfixable`.
#[deprecated(
note = "The `extend-unfixable` option is now interchangeable with `unfixable`. Please update your configuration to use the `unfixable` option instead."
)]
///
/// This option has been **deprecated** in favor of `unfixable` since its usage is now
/// interchangeable with `unfixable`.
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extend_unfixable: Option<Vec<RuleSelector>>,
/// A list of rule codes that are unsupported by Ruff, but should be
@@ -2392,7 +2394,7 @@ pub struct PylintOptions {
/// Constant types to ignore when used as "magic values" (see: `PLR2004`).
#[option(
default = r#"["str", "bytes"]"#,
value_type = r#"list["str" | "bytes" | "complex" | "float" | "int"]"#,
value_type = r#"list["str" | "bytes" | "complex" | "float" | "int" | "tuple"]"#,
example = r#"
allow-magic-value-types = ["int"]
"#

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeSet;
use std::fmt::{Debug, Display, Formatter};
/// Visits [`OptionsMetadata`].
@@ -89,7 +90,8 @@ impl OptionSet {
/// ### Test for the existence of a child option
///
/// ```rust
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
/// # use std::collections::BTreeSet;
/// use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
///
/// struct WithOptions;
///
@@ -100,7 +102,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None,
/// aliases: BTreeSet::new()
/// });
/// }
/// }
@@ -111,7 +113,8 @@ impl OptionSet {
/// ### Test for the existence of a nested option
///
/// ```rust
/// # use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
/// # use std::collections::BTreeSet;
/// use ruff_workspace::options_base::{OptionField, OptionsMetadata, Visit};
///
/// struct Root;
///
@@ -122,7 +125,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// aliases: BTreeSet::new()
/// });
///
/// visit.record_set("format", Nested::metadata());
@@ -138,7 +141,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// aliases: BTreeSet::new()
/// });
/// }
/// }
@@ -160,7 +163,8 @@ impl OptionSet {
/// ### Find a child option
///
/// ```rust
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
/// # use std::collections::BTreeSet;
/// use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
///
/// struct WithOptions;
///
@@ -169,7 +173,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// aliases: BTreeSet::new()
/// };
///
/// impl OptionsMetadata for WithOptions {
@@ -184,14 +188,15 @@ impl OptionSet {
/// ### Find a nested option
///
/// ```rust
/// # use ruff_workspace::options_base::{OptionEntry, OptionField, OptionsMetadata, Visit};
/// # use std::collections::BTreeSet;
/// 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: "",
/// deprecated: None
/// aliases: BTreeSet::new()
/// };
///
/// struct Root;
@@ -203,7 +208,7 @@ impl OptionSet {
/// default: "false",
/// value_type: "bool",
/// example: "",
/// deprecated: None
/// aliases: BTreeSet::new()
/// });
///
/// visit.record_set("format", Nested::metadata());
@@ -289,16 +294,8 @@ impl Visit for DisplayVisitor<'_, '_> {
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
}
fn record_field(&mut self, name: &str, field: OptionField) {
self.result = self.result.and_then(|_| {
write!(self.f, "{name}")?;
if field.deprecated.is_some() {
write!(self.f, " (deprecated)")?;
}
writeln!(self.f)
});
fn record_field(&mut self, name: &str, _: OptionField) {
self.result = self.result.and_then(|_| writeln!(self.f, "{name}"));
}
}
@@ -321,14 +318,8 @@ pub struct OptionField {
pub doc: &'static str,
pub default: &'static str,
pub value_type: &'static str,
pub aliases: BTreeSet<&'static str>,
pub example: &'static str,
pub deprecated: Option<Deprecated>,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Deprecated {
pub since: Option<&'static str>,
pub message: Option<&'static str>,
}
impl Display for OptionField {
@@ -337,21 +328,6 @@ impl Display for OptionField {
writeln!(f)?;
writeln!(f, "Default value: {}", self.default)?;
writeln!(f, "Type: {}", self.value_type)?;
if let Some(deprecated) = &self.deprecated {
write!(f, "Deprecated")?;
if let Some(since) = deprecated.since {
write!(f, " (since {since})")?;
}
if let Some(message) = deprecated.message {
write!(f, ": {message}")?;
}
writeln!(f)?;
}
writeln!(f, "Example usage:\n```toml\n{}\n```", self.example)
}
}

View File

@@ -391,7 +391,7 @@ whether a rule supports fixing, see [_Rules_](rules.md).
Ruff labels fixes as "safe" and "unsafe". The meaning and intent of your code will be retained when applying safe fixes, but the meaning could be changed when applying unsafe fixes.
For example, [`unnecessary-iterable-allocation-for-first-element`](rules/unnecessary-iterable-allocation-for-first-element.md) (`RUF015`) is a rule which checks for potentially unperformant use of `list(...)[0]`. The fix replaces this pattern with `next(iter(...))` which can result in a drastic speedup:
For example, [`unnecessary-iterable-allocation-for-first-element`](../rules/unnecessary-iterable-allocation-for-first-element) (`RUF015`) is a rule which checks for potentially unperformant use of `list(...)[0]`. The fix replaces this pattern with `next(iter(...))` which can result in a drastic speedup:
```shell
$ python -m timeit "head = list(range(99999999))[0]"

View File

@@ -10,20 +10,6 @@ Preview mode enables a collection of newer rules and fixes that are considered e
Preview mode can be enabled with the `--preview` flag on the CLI or by setting `preview = true` in your Ruff
configuration file (e.g. `pyproject.toml`).
Preview mode can be configured separately for linting and formatting (requires Ruff v0.1.1+). To enable preview lint rules without preview style formatting:
```toml
[lint]
preview = true
```
To enable preview style formatting without enabling any preview lint rules:
```toml
[format]
preview = true
```
## Using rules that are in preview
If a rule is marked as preview, it can only be selected if preview mode is enabled. For example, consider a

View File

@@ -16,13 +16,13 @@ numbers
Where `numbers.py` contains the following code:
```py
from typing import Iterable
from typing import List
import os
def sum_even_numbers(numbers: Iterable[int]) -> int:
"""Given an iterable of integers, return the sum of all even numbers in the iterable."""
def sum_even_numbers(numbers: List[int]) -> int:
"""Given a list of integers, return the sum of all even numbers in the list."""
return sum(num for num in numbers if num % 2 == 0)
```
@@ -32,17 +32,17 @@ To start, we'll install Ruff through PyPI (or with your [preferred package manag
> pip install ruff
```
We can then run Ruff over our project via `ruff check`:
We can then run Ruff over our project via:
```shell
ruff check .
numbers/numbers.py:3:8: F401 [*] `os` imported but unused
Found 1 error.
[*] 1 fixable with the `--fix` option.
[*] 1 potentially fixable with the --fix option.
```
Ruff identified an unused import, which is a common error in Python code. Ruff considers this a
"fixable" error, so we can resolve the issue automatically by running `ruff check --fix`:
"fixable" error, so we can resolve the issue automatically by running:
```shell
ruff check --fix .
@@ -55,13 +55,13 @@ Running `git diff` shows the following:
--- a/numbers/numbers.py
+++ b/numbers/numbers.py
@@ -1,7 +1,5 @@
from typing import Iterable
from typing import List
-import os
-
def sum_even_numbers(numbers: Iterable[int]) -> int:
"""Given an iterable of integers, return the sum of all even numbers in the iterable."""
def sum_even_numbers(numbers: List[int]) -> int:
"""Given a list of integers, return the sum of all even numbers in the list."""
return sum(num for num in numbers if num % 2 == 0)
```
@@ -77,11 +77,7 @@ Let's create a `pyproject.toml` file in our project's root directory:
```toml
[tool.ruff]
# Add the `line-too-long` rule to the enforced rule set. By default, Ruff omits rules that
# overlap with the use of a formatter, like Black, but we can override this behavior by
# explicitly adding the rule.
extend-select = ["E501"]
# Set the maximum line length to 79 characters.
# Decrease the maximum line length to 79 characters.
line-length = 79
```
@@ -89,7 +85,7 @@ Running Ruff again, we can see that it now enforces a line length of 79 characte
```shell
ruff check .
numbers/numbers.py:5:80: E501 Line too long (90 > 79 characters)
numbers/numbers.py:6:80: E501 Line too long (83 > 79 characters)
Found 1 error.
```
@@ -102,10 +98,9 @@ specifically, we'll want to make note of the minimum supported Python version:
requires-python = ">=3.10"
[tool.ruff]
# Add the `line-too-long` rule to the enforced rule set.
extend-select = ["E501"]
# Set the maximum line length to 79 characters.
# Decrease the maximum line length to 79 characters.
line-length = 79
src = ["src"]
```
### Rule Selection
@@ -114,9 +109,8 @@ Ruff supports [over 700 lint rules](rules.md) split across over 50 built-in plug
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.
By default, Ruff enables Flake8's `F` rules, along with a subset of the `E` rules, omitting any
stylistic rules that overlap with the use of a formatter, like
[Black](https://github.com/psf/black).
By default, Ruff enforces the `E`- and `F`-prefixed rules, which correspond to those derived from
pycodestyle and Pyflakes, respectively.
If you're introducing a linter for the first time, **the default rule set is a great place to
start**: it's narrow and focused while catching a wide variety of common errors (like unused
@@ -124,37 +118,36 @@ imports) with zero configuration.
If you're migrating to Ruff from another linter, you can enable rules that are equivalent to
those enforced in your previous configuration. For example, if we want to enforce the pyupgrade
rules, we can set our `pyproject.toml` to the following:
rules, we can add the following to our `pyproject.toml`:
```toml
[project]
requires-python = ">=3.10"
[tool.ruff]
extend-select = [
select = [
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade
]
```
If we run Ruff again, we'll see that it now enforces the pyupgrade rules. In particular, Ruff flags
the use of the deprecated `typing.Iterable` instead of `collections.abc.Iterable`:
the use of `List` instead of its standard-library variant:
```shell
ruff check .
numbers/numbers.py:1:1: UP035 [*] Import from `collections.abc` instead: `Iterable`
Found 1 error.
[*] 1 fixable with the `--fix` option.
numbers/numbers.py:5:31: UP006 [*] Use `list` instead of `List` for type annotations
numbers/numbers.py:6:80: E501 Line too long (83 > 79 characters)
Found 2 errors.
[*] 1 potentially fixable with the --fix option.
```
Over time, we may choose to enforce additional rules. For example, we may want to enforce that
all functions have docstrings:
```toml
[project]
requires-python = ">=3.10"
[tool.ruff]
extend-select = [
select = [
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade
"D", # pydocstyle
]
@@ -168,32 +161,34 @@ If we run Ruff again, we'll see that it now enforces the pydocstyle rules:
```shell
ruff check .
numbers/__init__.py:1:1: D104 Missing docstring in public package
numbers/numbers.py:1:1: UP035 [*] Import from `collections.abc` instead: `Iterable`
numbers/numbers.py:1:1: D100 Missing docstring in public module
numbers/numbers.py:5:31: UP006 [*] Use `list` instead of `List` for type annotations
numbers/numbers.py:5:80: E501 Line too long (83 > 79 characters)
Found 3 errors.
[*] 1 fixable with the `--fix` option.
[*] 1 potentially fixable with the --fix option.
```
### Ignoring Errors
Any lint rule can be ignored by adding a `# noqa` comment to the line in question. For example,
let's ignore the `UP035` rule for the `Iterable` import:
let's ignore the `UP006` rule for the `List` import:
```py
from typing import Iterable # noqa: UP035
from typing import List
def sum_even_numbers(numbers: Iterable[int]) -> int:
"""Given an iterable of integers, return the sum of all even numbers in the iterable."""
def sum_even_numbers(numbers: List[int]) -> int: # noqa: UP006
"""Given a list of integers, return the sum of all even numbers in the list."""
return sum(num for num in numbers if num % 2 == 0)
```
Running Ruff again, we'll see that it no longer flags the `Iterable` import:
Running Ruff again, we'll see that it no longer flags the `List` import:
```shell
ruff check .
numbers/__init__.py:1:1: D104 Missing docstring in public package
numbers/numbers.py:1:1: D100 Missing docstring in public module
numbers/numbers.py:5:80: E501 Line too long (83 > 79 characters)
Found 3 errors.
```
@@ -201,16 +196,17 @@ If we want to ignore a rule for an entire file, we can add a `# ruff: noqa` comm
the file:
```py
# ruff: noqa: UP035
from typing import Iterable
# ruff: noqa: UP006
from typing import List
def sum_even_numbers(numbers: Iterable[int]) -> int:
"""Given an iterable of integers, return the sum of all even numbers in the iterable."""
def sum_even_numbers(numbers: List[int]) -> int:
"""Given a list of integers, return the sum of all even numbers in the list."""
return sum(num for num in numbers if num % 2 == 0)
```
For more in-depth instructions on ignoring errors, see [_Configuration_](configuration.md#error-suppression).
For more in-depth instructions on ignoring errors,
please see [_Configuration_](configuration.md#error-suppression).
### Adding Rules
@@ -219,10 +215,10 @@ violations of that rule and instead focus on enforcing it going forward.
Ruff enables this workflow via the `--add-noqa` flag, which will adds a `# noqa` directive to each
line based on its existing violations. We can combine `--add-noqa` with the `--select` command-line
flag to add `# noqa` directives to all existing `UP035` violations:
flag to add `# noqa` directives to all existing `UP006` violations:
```shell
ruff check --select UP035 --add-noqa .
ruff check --select UP006 --add-noqa .
Added 1 noqa directive.
```
@@ -233,12 +229,14 @@ diff --git a/tutorial/src/main.py b/tutorial/src/main.py
index b9291c5ca..b9f15b8c1 100644
--- a/numbers/numbers.py
+++ b/numbers/numbers.py
@@ -1,4 +1,4 @@
-from typing import Iterable
+from typing import Iterable # noqa: UP035
@@ -1,6 +1,6 @@
from typing import List
def sum_even_numbers(numbers: Iterable[int]) -> int:
-def sum_even_numbers(numbers: List[int]) -> int:
+def sum_even_numbers(numbers: List[int]) -> int: # noqa: UP006
"""Given a list of integers, return the sum of all even numbers in the list."""
return sum(num for num in numbers if num % 2 == 0)
```
## Continuous Integration
@@ -249,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.1.1
rev: v0.1.0
hooks:
- id: ruff
```

View File

@@ -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.1.1
rev: v0.1.0
hooks:
- id: ruff
```
@@ -33,7 +33,7 @@ Or, to enable fixes:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.1.1
rev: v0.1.0
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.1.1
rev: v0.1.0
hooks:
- id: ruff
types_or: [python, pyi, jupyter]

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.1.1"
version = "0.1.0"
description = "An extremely fast Python linter, written in Rust."
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
readme = "README.md"

48
ruff.schema.json generated
View File

@@ -84,17 +84,6 @@
"$ref": "#/definitions/RuleSelector"
}
},
"extend-ignore": {
"description": "A list of rule codes or prefixes to ignore, in addition to those specified by `ignore`.",
"deprecated": true,
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/RuleSelector"
}
},
"extend-include": {
"description": "A list of file patterns to include when linting, in addition to those specified by `include`.\n\nInclusion are based on globs, and should be single-path patterns, like `*.pyw`, to include any file with the `.pyw` extension.\n\nFor more information on the glob syntax, refer to the [`globset` documentation](https://docs.rs/globset/latest/globset/#syntax).",
"type": [
@@ -138,17 +127,6 @@
"$ref": "#/definitions/RuleSelector"
}
},
"extend-unfixable": {
"description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those specified by `unfixable`.",
"deprecated": true,
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/RuleSelector"
}
},
"extend-unsafe-fixes": {
"description": "A list of rule codes or prefixes for which safe fixes should be considered unsafe.",
"type": [
@@ -708,7 +686,8 @@
"complex",
"float",
"int",
"str"
"str",
"tuple"
]
},
"Convention": {
@@ -1650,17 +1629,6 @@
"$ref": "#/definitions/RuleSelector"
}
},
"extend-ignore": {
"description": "A list of rule codes or prefixes to ignore, in addition to those specified by `ignore`.",
"deprecated": true,
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/RuleSelector"
}
},
"extend-per-file-ignores": {
"description": "A list of mappings from file pattern to rule codes or prefixes to exclude, in addition to any rules excluded by `per-file-ignores`.",
"type": [
@@ -1694,17 +1662,6 @@
"$ref": "#/definitions/RuleSelector"
}
},
"extend-unfixable": {
"description": "A list of rule codes or prefixes to consider non-auto-fixable, in addition to those specified by `unfixable`.",
"deprecated": true,
"type": [
"array",
"null"
],
"items": {
"$ref": "#/definitions/RuleSelector"
}
},
"extend-unsafe-fixes": {
"description": "A list of rule codes or prefixes for which safe fixes should be considered unsafe.",
"type": [
@@ -3070,7 +3027,6 @@
"PLW060",
"PLW0602",
"PLW0603",
"PLW0604",
"PLW07",
"PLW071",
"PLW0711",

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "scripts"
version = "0.1.1"
version = "0.1.0"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]

View File

@@ -48,6 +48,7 @@ LINK_REWRITES: dict[str, str] = {
),
"https://docs.astral.sh/ruff/installation/": "installation.md",
"https://docs.astral.sh/ruff/rules/": "rules.md",
"https://docs.astral.sh/ruff/rules/#error-e": "rules.md#error-e",
"https://docs.astral.sh/ruff/settings/": "settings.md",
}