Compare commits
34 Commits
zanie/exte
...
v0.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e7b92991b | ||
|
|
25d4ddaa60 | ||
|
|
63a5a12a41 | ||
|
|
c32f943d86 | ||
|
|
d211074f59 | ||
|
|
4ffd4ed61f | ||
|
|
a4dd1e5fad | ||
|
|
be3307e9a6 | ||
|
|
317d3dd612 | ||
|
|
f5e850745c | ||
|
|
a7d1f7e1ec | ||
|
|
88c8b47326 | ||
|
|
133a745de1 | ||
|
|
6983d96d27 | ||
|
|
3c3d9ab173 | ||
|
|
ff9fb0da54 | ||
|
|
9792b1551b | ||
|
|
d1c67f91bd | ||
|
|
dbd84c947b | ||
|
|
c2ec5f0bc9 | ||
|
|
31032f4f70 | ||
|
|
f55b724254 | ||
|
|
fd07a12a52 | ||
|
|
1ee73bdedf | ||
|
|
23b55aea30 | ||
|
|
e36afc3324 | ||
|
|
8304c41714 | ||
|
|
6f31e9c00e | ||
|
|
a6cc56fd98 | ||
|
|
0236e0751c | ||
|
|
2d0769e324 | ||
|
|
80473c3f5c | ||
|
|
4d7f90e045 | ||
|
|
75bd95e58c |
46
CHANGELOG.md
46
CHANGELOG.md
@@ -1,9 +1,53 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.3
|
||||
|
||||
This release includes a variety of improvements to the Ruff formatter, removing several known and
|
||||
unintentional deviations from Black.
|
||||
|
||||
### Formatter
|
||||
|
||||
- Avoid space around pow for `None`, `True` and `False` ([#8189](https://github.com/astral-sh/ruff/pull/8189))
|
||||
- Avoid sorting all paths in the format command ([#8181](https://github.com/astral-sh/ruff/pull/8181))
|
||||
- Insert necessary blank line between class and leading comments ([#8224](https://github.com/astral-sh/ruff/pull/8224))
|
||||
- Avoid introducing new parentheses in annotated assignments ([#8233](https://github.com/astral-sh/ruff/pull/8233))
|
||||
- Refine the warnings about incompatible linter options ([#8196](https://github.com/astral-sh/ruff/pull/8196))
|
||||
- Add test and basic implementation for formatter preview mode ([#8044](https://github.com/astral-sh/ruff/pull/8044))
|
||||
- Refine warning about incompatible `isort` settings ([#8192](https://github.com/astral-sh/ruff/pull/8192))
|
||||
- Only omit optional parentheses for starting or ending with parentheses ([#8238](https://github.com/astral-sh/ruff/pull/8238))
|
||||
- Use source type to determine parser mode for formatting ([#8205](https://github.com/astral-sh/ruff/pull/8205))
|
||||
- Don't warn about magic trailing comma when `isort.force-single-line` is true ([#8244](https://github.com/astral-sh/ruff/pull/8244))
|
||||
- Use `SourceKind::diff` for formatter ([#8240](https://github.com/astral-sh/ruff/pull/8240))
|
||||
- Fix `fmt:off` with trailing child comment ([#8234](https://github.com/astral-sh/ruff/pull/8234))
|
||||
- Formatter parentheses support for `IpyEscapeCommand` ([#8207](https://github.com/astral-sh/ruff/pull/8207))
|
||||
|
||||
### Linter
|
||||
|
||||
- \[`pylint`\] Add buffer methods to `bad-dunder-method-name` (`PLW3201`) exclusions ([#8190](https://github.com/astral-sh/ruff/pull/8190))
|
||||
- Match rule prefixes from `external` codes setting in `unused-noqa` ([#8177](https://github.com/astral-sh/ruff/pull/8177))
|
||||
- Use `line-length` setting for isort in lieu of `pycodestyle.max-line-length` ([#8235](https://github.com/astral-sh/ruff/pull/8235))
|
||||
- Update fix for `unnecessary-paren-on-raise-exception` to unsafe for unknown types ([#8231](https://github.com/astral-sh/ruff/pull/8231))
|
||||
- Correct quick fix message for `W605` ([#8255](https://github.com/astral-sh/ruff/pull/8255))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Fix typo in max-doc-length documentation ([#8201](https://github.com/astral-sh/ruff/pull/8201))
|
||||
- Improve documentation around linter-formatter conflicts ([#8257](https://github.com/astral-sh/ruff/pull/8257))
|
||||
- Fix link to error suppression documentation in `unused-noqa` ([#8172](https://github.com/astral-sh/ruff/pull/8172))
|
||||
- Add `external` option to `unused-noqa` documentation ([#8171](https://github.com/astral-sh/ruff/pull/8171))
|
||||
- Add title attribute to icons ([#8060](https://github.com/astral-sh/ruff/pull/8060))
|
||||
- Clarify unsafe case in RSE102 ([#8256](https://github.com/astral-sh/ruff/pull/8256))
|
||||
- Fix skipping formatting examples ([#8210](https://github.com/astral-sh/ruff/pull/8210))
|
||||
- docs: fix name of `magic-trailing-comma` option in README ([#8200](https://github.com/astral-sh/ruff/pull/8200))
|
||||
- Add note about scope of rule changing in versioning policy ([#8169](https://github.com/astral-sh/ruff/pull/8169))
|
||||
- Document: Fix default lint rules ([#8218](https://github.com/astral-sh/ruff/pull/8218))
|
||||
- Fix a wrong setting in configuration.md ([#8186](https://github.com/astral-sh/ruff/pull/8186))
|
||||
- Fix misspelled TOML headers in the tutorial ([#8209](https://github.com/astral-sh/ruff/pull/8209))
|
||||
|
||||
## 0.1.2
|
||||
|
||||
This release includes the Beta version of the Ruff formatter — an extremely fast, Black-compatible Python formatter.
|
||||
Try it today with `ruff format`.
|
||||
Try it today with `ruff format`! [Check out the blog post](https://astral.sh/blog/the-ruff-formatter) and [read the docs](https://docs.astral.sh/ruff/formatter/).
|
||||
|
||||
### Preview features
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -810,7 +810,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2051,7 +2051,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2188,7 +2188,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.1",
|
||||
@@ -2438,7 +2438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
# Run the Ruff linter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.2
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
# Run the Ruff linter.
|
||||
- id: ruff
|
||||
@@ -238,7 +238,7 @@ quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
magic-trailing-comma = "respect"
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
@@ -409,6 +409,7 @@ Ruff is used by a number of major open-source projects and companies, including:
|
||||
- [Mypy](https://github.com/python/mypy)
|
||||
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
|
||||
- [Neon](https://github.com/neondatabase/neon)
|
||||
- [NoneBot](https://github.com/nonebot/nonebot2)
|
||||
- [ONNX](https://github.com/onnx/onnx)
|
||||
- [OpenBB](https://github.com/OpenBB-finance/OpenBBTerminal)
|
||||
- [PDM](https://github.com/pdm-project/pdm)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -11,6 +11,41 @@
|
||||
"maths = (numpy.arange(100)**2).sum()\n",
|
||||
"stats= numpy.asarray([1,2,3,4]).median()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "83a0b1b8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"A markdown cell"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ae12f012",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# A cell with IPython escape command\n",
|
||||
"def some_function(foo, bar):\n",
|
||||
" pass\n",
|
||||
"%matplotlib inline"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "10f3bbf9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"foo = %pwd\n",
|
||||
"def some_function(foo,bar,):\n",
|
||||
" # Another cell with IPython escape command\n",
|
||||
" foo = %pwd\n",
|
||||
" print(foo)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
||||
@@ -11,7 +11,6 @@ use itertools::Itertools;
|
||||
use log::{error, warn};
|
||||
use rayon::iter::Either::{Left, Right};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use similar::TextDiff;
|
||||
use thiserror::Error;
|
||||
use tracing::debug;
|
||||
|
||||
@@ -19,12 +18,11 @@ use ruff_diagnostics::SourceMap;
|
||||
use ruff_linter::fs;
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::registry::Rule;
|
||||
use ruff_linter::rules::isort;
|
||||
use ruff_linter::settings::rule_table::RuleTable;
|
||||
use ruff_linter::rules::flake8_quotes::settings::Quote;
|
||||
use ruff_linter::source_kind::{SourceError, SourceKind};
|
||||
use ruff_linter::warn_user_once;
|
||||
use ruff_python_ast::{PySourceType, SourceType};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError};
|
||||
use ruff_python_formatter::{format_module_source, FormatModuleError, QuoteStyle};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use ruff_workspace::resolver::{
|
||||
match_exclusion, python_files_in_path, PyprojectConfig, ResolvedFile, Resolver,
|
||||
@@ -107,7 +105,7 @@ pub(crate) fn format(
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
let (mut results, mut errors): (Vec<_>, Vec<_>) = paths
|
||||
let (results, mut errors): (Vec<_>, Vec<_>) = paths
|
||||
.par_iter()
|
||||
.filter_map(|entry| {
|
||||
match entry {
|
||||
@@ -168,27 +166,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(),
|
||||
@@ -198,15 +175,21 @@ pub(crate) fn format(
|
||||
caches.persist()?;
|
||||
|
||||
// Report on any errors.
|
||||
errors.sort_unstable_by(|a, b| a.path().cmp(&b.path()));
|
||||
|
||||
for error in &errors {
|
||||
error!("{error}");
|
||||
}
|
||||
|
||||
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())?;
|
||||
match mode {
|
||||
FormatMode::Write => {}
|
||||
FormatMode::Check => {
|
||||
results.write_changed(&mut stdout().lock())?;
|
||||
}
|
||||
FormatMode::Diff => {
|
||||
results.write_diff(&mut stdout().lock())?;
|
||||
}
|
||||
}
|
||||
|
||||
// Report on the formatting changes.
|
||||
@@ -470,27 +453,51 @@ impl<'a> FormatResults<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Write a diff of the formatting changes to the given writer.
|
||||
fn write_diff(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
for result in self.results {
|
||||
if let FormatResult::Diff {
|
||||
unformatted,
|
||||
formatted,
|
||||
} = &result.result
|
||||
{
|
||||
let text_diff =
|
||||
TextDiff::from_lines(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)?;
|
||||
}
|
||||
for (path, unformatted, formatted) in self
|
||||
.results
|
||||
.iter()
|
||||
.filter_map(|result| {
|
||||
if let FormatResult::Diff {
|
||||
unformatted,
|
||||
formatted,
|
||||
} = &result.result
|
||||
{
|
||||
Some((result.path.as_path(), unformatted, formatted))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_unstable_by_key(|(path, _, _)| *path)
|
||||
{
|
||||
unformatted.diff(formatted, Some(path), f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a list of the files that would be changed to the given writer.
|
||||
fn write_changed(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
for path in self
|
||||
.results
|
||||
.iter()
|
||||
.filter_map(|result| {
|
||||
if result.result.is_formatted() {
|
||||
Some(result.path.as_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.sorted_unstable()
|
||||
{
|
||||
writeln!(f, "Would reformat: {}", fs::relativize_path(path).bold())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Write a summary of the formatting results to the given writer.
|
||||
fn write_summary(&self, f: &mut impl Write) -> io::Result<()> {
|
||||
// Compute the number of changed and unchanged files.
|
||||
let mut changed = 0u32;
|
||||
@@ -498,14 +505,6 @@ impl<'a> FormatResults<'a> {
|
||||
for result in self.results {
|
||||
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() {
|
||||
writeln!(
|
||||
f,
|
||||
"Would reformat: {}",
|
||||
fs::relativize_path(&result.path).bold()
|
||||
)?;
|
||||
}
|
||||
changed += 1;
|
||||
}
|
||||
FormatResult::Unchanged => unchanged += 1,
|
||||
@@ -562,6 +561,26 @@ pub(crate) enum FormatCommandError {
|
||||
Read(Option<PathBuf>, SourceError),
|
||||
Format(Option<PathBuf>, FormatModuleError),
|
||||
Write(Option<PathBuf>, SourceError),
|
||||
Diff(Option<PathBuf>, io::Error),
|
||||
}
|
||||
|
||||
impl FormatCommandError {
|
||||
fn path(&self) -> Option<&Path> {
|
||||
match self {
|
||||
Self::Ignore(err) => {
|
||||
if let ignore::Error::WithPath { path, .. } = err {
|
||||
Some(path.as_path())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Self::Panic(path, _)
|
||||
| Self::Read(path, _)
|
||||
| Self::Format(path, _)
|
||||
| Self::Write(path, _)
|
||||
| Self::Diff(path, _) => path.as_deref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FormatCommandError {
|
||||
@@ -627,6 +646,24 @@ impl Display for FormatCommandError {
|
||||
write!(f, "{}{} {err}", "Failed to format".bold(), ":".bold())
|
||||
}
|
||||
}
|
||||
Self::Diff(path, err) => {
|
||||
if let Some(path) = path {
|
||||
write!(
|
||||
f,
|
||||
"{}{}{} {err}",
|
||||
"Failed to generate diff for ".bold(),
|
||||
fs::relativize_path(path).bold(),
|
||||
":".bold()
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}{} {err}",
|
||||
"Failed to generate diff".bold(),
|
||||
":".bold()
|
||||
)
|
||||
}
|
||||
}
|
||||
Self::Panic(path, err) => {
|
||||
let message = r#"This indicates a bug in Ruff. If you could open an issue at:
|
||||
|
||||
@@ -663,59 +700,110 @@ pub(super) fn warn_incompatible_formatter_settings(
|
||||
{
|
||||
let mut incompatible_rules = Vec::new();
|
||||
|
||||
for incompatible_rule in RuleTable::from_iter([
|
||||
Rule::LineTooLong,
|
||||
Rule::TabIndentation,
|
||||
Rule::IndentationWithInvalidMultiple,
|
||||
Rule::IndentationWithInvalidMultipleComment,
|
||||
Rule::OverIndented,
|
||||
Rule::IndentWithSpaces,
|
||||
for rule in [
|
||||
// The formatter might collapse implicit string concatenation on a single line.
|
||||
Rule::SingleLineImplicitStringConcatenation,
|
||||
// Flags missing trailing commas when all arguments are on its own line:
|
||||
// ```python
|
||||
// def args(
|
||||
// aaaaaaaa, bbbbbbbbb, cccccccccc, ddddddddd, eeeeeeee, ffffff, gggggggggggg, hhhh
|
||||
// ):
|
||||
// pass
|
||||
// ```
|
||||
Rule::MissingTrailingComma,
|
||||
Rule::ProhibitedTrailingComma,
|
||||
Rule::BadQuotesInlineString,
|
||||
Rule::BadQuotesMultilineString,
|
||||
Rule::BadQuotesDocstring,
|
||||
Rule::AvoidableEscapedQuote,
|
||||
])
|
||||
.iter_enabled()
|
||||
{
|
||||
if setting.linter.rules.enabled(incompatible_rule) {
|
||||
incompatible_rules.push(format!("'{}'", incompatible_rule.noqa_code()));
|
||||
] {
|
||||
if setting.linter.rules.enabled(rule) {
|
||||
incompatible_rules.push(rule);
|
||||
}
|
||||
}
|
||||
|
||||
// Rules asserting for space indentation
|
||||
if setting.formatter.indent_style.is_tab() {
|
||||
for rule in [Rule::TabIndentation, Rule::IndentWithSpaces] {
|
||||
if setting.linter.rules.enabled(rule) {
|
||||
incompatible_rules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rules asserting for indent-width=4
|
||||
if setting.formatter.indent_width.value() != 4 {
|
||||
for rule in [
|
||||
Rule::IndentationWithInvalidMultiple,
|
||||
Rule::IndentationWithInvalidMultipleComment,
|
||||
] {
|
||||
if setting.linter.rules.enabled(rule) {
|
||||
incompatible_rules.push(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !incompatible_rules.is_empty() {
|
||||
incompatible_rules.sort();
|
||||
warn!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.", incompatible_rules.join(", "));
|
||||
let mut rule_names: Vec<_> = incompatible_rules
|
||||
.into_iter()
|
||||
.map(|rule| format!("`{}`", rule.noqa_code()))
|
||||
.collect();
|
||||
rule_names.sort();
|
||||
warn!("The following rules may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.", rule_names.join(", "));
|
||||
}
|
||||
|
||||
let mut incompatible_options = Vec::new();
|
||||
|
||||
let isort_defaults = isort::settings::Settings::default();
|
||||
|
||||
if setting.linter.isort.force_single_line != isort_defaults.force_single_line {
|
||||
incompatible_options.push("'isort.force-single-line'");
|
||||
// Rules with different quote styles.
|
||||
if setting
|
||||
.linter
|
||||
.rules
|
||||
.any_enabled(&[Rule::BadQuotesInlineString, Rule::AvoidableEscapedQuote])
|
||||
{
|
||||
match (
|
||||
setting.linter.flake8_quotes.inline_quotes,
|
||||
setting.formatter.quote_style,
|
||||
) {
|
||||
(Quote::Double, QuoteStyle::Single) => {
|
||||
warn!("The `flake8-quotes.inline-quotes=\"double\"` option is incompatible with the formatter's `format.quote-style=\"single\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||
}
|
||||
(Quote::Single, QuoteStyle::Double) => {
|
||||
warn!("The `flake8-quotes.inline-quotes=\"single\"` option is incompatible with the formatter's `format.quote-style=\"double\"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `\"single\"` or `\"double\"`.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if setting.linter.isort.force_wrap_aliases != isort_defaults.force_wrap_aliases {
|
||||
incompatible_options.push("'isort.force-wrap-aliases'");
|
||||
if setting.linter.rules.enabled(Rule::BadQuotesMultilineString)
|
||||
&& setting.linter.flake8_quotes.multiline_quotes == Quote::Single
|
||||
{
|
||||
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `\"double\"`.`");
|
||||
}
|
||||
|
||||
if setting.linter.isort.lines_after_imports != isort_defaults.lines_after_imports {
|
||||
incompatible_options.push("'isort.lines-after-imports'");
|
||||
if setting.linter.rules.enabled(Rule::BadQuotesDocstring)
|
||||
&& setting.linter.flake8_quotes.docstring_quotes == Quote::Single
|
||||
{
|
||||
warn!("The `flake8-quotes.multiline-quotes=\"single\"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `\"double\"`.`");
|
||||
}
|
||||
|
||||
if setting.linter.isort.lines_between_types != isort_defaults.lines_between_types {
|
||||
incompatible_options.push("'isort.lines_between_types'");
|
||||
}
|
||||
if setting.linter.rules.enabled(Rule::UnsortedImports) {
|
||||
// The formatter removes empty lines if the value is larger than 2 but always inserts a empty line after imports.
|
||||
// Two empty lines are okay because `isort` only uses this setting for top-level imports (not in nested blocks).
|
||||
if !matches!(setting.linter.isort.lines_after_imports, 1 | 2 | -1) {
|
||||
warn!("The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).");
|
||||
}
|
||||
|
||||
if setting.linter.isort.split_on_trailing_comma != isort_defaults.split_on_trailing_comma {
|
||||
incompatible_options.push("'isort.split_on_trailing_comma'");
|
||||
}
|
||||
// Values larger than two get reduced to one line by the formatter if the import is in a nested block.
|
||||
if setting.linter.isort.lines_between_types > 1 {
|
||||
warn!("The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).");
|
||||
}
|
||||
|
||||
if !incompatible_options.is_empty() {
|
||||
warn!("The following isort options may cause conflicts when used with the formatter: {}. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.", incompatible_options.join(", "));
|
||||
// isort inserts a trailing comma which the formatter preserves, but only if `skip-magic-trailing-comma` isn't false.
|
||||
// This isn't relevant when using `force-single-line`, since isort will never include a trailing comma in that case.
|
||||
if setting.formatter.magic_trailing_comma.is_ignore()
|
||||
&& !setting.linter.isort.force_single_line
|
||||
{
|
||||
if setting.linter.isort.force_wrap_aliases {
|
||||
warn!("The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.");
|
||||
}
|
||||
|
||||
if setting.linter.isort.split_on_trailing_comma {
|
||||
warn!("The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
@@ -109,14 +107,9 @@ fn format_source_code(
|
||||
}
|
||||
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();
|
||||
source_kind
|
||||
.diff(formatted, path, &mut stdout().lock())
|
||||
.map_err(|err| FormatCommandError::Diff(path.map(Path::to_path_buf), err))?;
|
||||
}
|
||||
},
|
||||
FormattedSource::Unchanged => {
|
||||
|
||||
@@ -230,7 +230,9 @@ fn format_option_inheritance() -> Result<()> {
|
||||
&ruff_toml,
|
||||
r#"
|
||||
extend = "base.toml"
|
||||
extend-select = ["Q000"]
|
||||
|
||||
[lint]
|
||||
extend-select = ["COM812"]
|
||||
|
||||
[format]
|
||||
quote-style = "single"
|
||||
@@ -273,7 +275,7 @@ if condition:
|
||||
print('Should change quotes')
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'Q000'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -359,15 +361,27 @@ fn conflicting_options() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
indent-width = 2
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212"]
|
||||
|
||||
[isort]
|
||||
force-single-line = true
|
||||
force-wrap-aliases = true
|
||||
lines-after-imports = 0
|
||||
[lint.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "single"
|
||||
multiline-quotes = "single"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = true
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -389,8 +403,14 @@ def say_hy(name: str):
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'COM812', 'COM819', 'D206', 'E501', 'ISC001', 'Q000', 'Q001', 'Q002', 'Q003', 'W191'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following isort options may cause conflicts when used with the formatter: 'isort.force-single-line', 'isort.force-wrap-aliases', 'isort.lines-after-imports', 'isort.lines_between_types'. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
@@ -402,15 +422,27 @@ fn conflicting_options_stdin() -> Result<()> {
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
indent-width = 2
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212"]
|
||||
|
||||
[isort]
|
||||
force-single-line = true
|
||||
force-wrap-aliases = true
|
||||
lines-after-imports = 0
|
||||
[lint.isort]
|
||||
lines-after-imports = 3
|
||||
lines-between-types = 2
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "single"
|
||||
multiline-quotes = "single"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = true
|
||||
indent-style = "tab"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
@@ -425,14 +457,110 @@ def say_hy(name: str):
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")
|
||||
print(f"Hy {name}")
|
||||
|
||||
----- stderr -----
|
||||
warning: The following rules may cause conflicts when used with the formatter: 'COM812', 'COM819', 'D206', 'E501', 'ISC001', 'Q000', 'Q001', 'Q002', 'Q003', 'W191'. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The following isort options may cause conflicts when used with the formatter: 'isort.force-single-line', 'isort.force-wrap-aliases', 'isort.lines-after-imports', 'isort.lines_between_types'. To avoid unexpected behavior, we recommend disabling these options by removing them from the configuration.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `D206`, `ISC001`, `W191`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
warning: The `flake8-quotes.inline-quotes="single"` option is incompatible with the formatter's `format.quote-style="double"`. We recommend disabling `Q000` and `Q003` when using the formatter, which enforces a consistent quote style. Alternatively, set both options to either `"single"` or `"double"`.
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q001` when using the formatter, which enforces double quotes for multiline strings. Alternatively, set the `flake8-quotes.multiline-quotes` option to `"double"`.`
|
||||
warning: The `flake8-quotes.multiline-quotes="single"` option is incompatible with the formatter. We recommend disabling `Q002` when using the formatter, which enforces double quotes for docstrings. Alternatively, set the `flake8-quotes.docstring-quotes` option to `"double"`.`
|
||||
warning: The isort option `isort.lines-after-imports` with a value other than `-1`, `1` or `2` is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `2`, `1`, or `-1` (default).
|
||||
warning: The isort option `isort.lines-between-types` with a value greater than 1 is incompatible with the formatter. To avoid unexpected behavior, we recommend setting the option to one of: `1` or `0` (default).
|
||||
warning: The isort option `isort.force-wrap-aliases` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.force-wrap-aliases=false` or `format.skip-magic-trailing-comma=false`.
|
||||
warning: The isort option `isort.split-on-trailing-comma` is incompatible with the formatter `format.skip-magic-trailing-comma=true` option. To avoid unexpected behavior, we recommend either setting `isort.split-on-trailing-comma=false` or `format.skip-magic-trailing-comma=false`.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_linter_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = ["D203", "D212", "COM812", "ISC001"]
|
||||
|
||||
[lint.isort]
|
||||
lines-after-imports = 2
|
||||
lines-between-types = 1
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
split-on-trailing-comma = true
|
||||
|
||||
[lint.flake8-quotes]
|
||||
inline-quotes = "single"
|
||||
docstring-quotes = "double"
|
||||
multiline-quotes = "double"
|
||||
|
||||
[format]
|
||||
skip-magic-trailing-comma = false
|
||||
quote-style = "single"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg(test_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_rules_default_options() -> Result<()> {
|
||||
let tempdir = TempDir::new()?;
|
||||
let ruff_toml = tempdir.path().join("ruff.toml");
|
||||
|
||||
fs::write(
|
||||
&ruff_toml,
|
||||
r#"
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let test_path = tempdir.path().join("test.py");
|
||||
fs::write(
|
||||
&test_path,
|
||||
r#"
|
||||
def say_hy(name: str):
|
||||
print(f"Hy {name}")"#,
|
||||
)?;
|
||||
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.args(["format", "--no-cache", "--config"])
|
||||
.arg(&ruff_toml)
|
||||
.arg(test_path), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
1 file reformatted
|
||||
|
||||
----- stderr -----
|
||||
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
|
||||
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
|
||||
warning: The following rules may cause conflicts when used with the formatter: `COM812`, `ISC001`. To avoid unexpected behavior, we recommend disabling these rules, either by removing them from the `select` or `extend-select` configuration, or adding then to the `ignore` configuration.
|
||||
"###);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diff() {
|
||||
let args = ["format", "--no-cache", "--isolated", "--diff"];
|
||||
@@ -452,8 +580,8 @@ fn test_diff() {
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
--- resources/test/fixtures/unformatted.ipynb
|
||||
+++ resources/test/fixtures/unformatted.ipynb
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 1
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 1
|
||||
@@ -1,3 +1,4 @@
|
||||
import numpy
|
||||
-maths = (numpy.arange(100)**2).sum()
|
||||
@@ -461,6 +589,30 @@ fn test_diff() {
|
||||
+
|
||||
+maths = (numpy.arange(100) ** 2).sum()
|
||||
+stats = numpy.asarray([1, 2, 3, 4]).median()
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 3
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 3
|
||||
@@ -1,4 +1,6 @@
|
||||
# A cell with IPython escape command
|
||||
def some_function(foo, bar):
|
||||
pass
|
||||
+
|
||||
+
|
||||
%matplotlib inline
|
||||
--- resources/test/fixtures/unformatted.ipynb:cell 4
|
||||
+++ resources/test/fixtures/unformatted.ipynb:cell 4
|
||||
@@ -1,5 +1,10 @@
|
||||
foo = %pwd
|
||||
-def some_function(foo,bar,):
|
||||
+
|
||||
+
|
||||
+def some_function(
|
||||
+ foo,
|
||||
+ bar,
|
||||
+):
|
||||
# Another cell with IPython escape command
|
||||
foo = %pwd
|
||||
print(foo)
|
||||
|
||||
--- resources/test/fixtures/unformatted.py
|
||||
+++ resources/test/fixtures/unformatted.py
|
||||
@@ -1,3 +1,3 @@
|
||||
@@ -469,6 +621,7 @@ fn test_diff() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
2 files would be reformatted, 1 file left unchanged
|
||||
"###);
|
||||
@@ -498,6 +651,7 @@ fn test_diff_no_change() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
1 file would be reformatted
|
||||
"###
|
||||
@@ -531,6 +685,7 @@ fn test_diff_stdin_unformatted() {
|
||||
+y = 2
|
||||
z = 3
|
||||
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use ruff_formatter::{FormatError, LineWidth, PrintError};
|
||||
use ruff_linter::logging::LogLevel;
|
||||
use ruff_linter::settings::types::{FilePattern, FilePatternSet};
|
||||
use ruff_python_formatter::{
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PyFormatOptions,
|
||||
format_module_source, FormatModuleError, MagicTrailingComma, PreviewMode, PyFormatOptions,
|
||||
};
|
||||
use ruff_workspace::resolver::{python_files_in_path, PyprojectConfig, ResolvedFile, Resolver};
|
||||
|
||||
@@ -871,9 +871,7 @@ struct BlackOptions {
|
||||
line_length: NonZeroU16,
|
||||
#[serde(alias = "skip-magic-trailing-comma")]
|
||||
skip_magic_trailing_comma: bool,
|
||||
#[allow(unused)]
|
||||
#[serde(alias = "force-exclude")]
|
||||
force_exclude: Option<String>,
|
||||
preview: bool,
|
||||
}
|
||||
|
||||
impl Default for BlackOptions {
|
||||
@@ -881,7 +879,7 @@ impl Default for BlackOptions {
|
||||
Self {
|
||||
line_length: NonZeroU16::new(88).unwrap(),
|
||||
skip_magic_trailing_comma: false,
|
||||
force_exclude: None,
|
||||
preview: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -929,6 +927,11 @@ impl BlackOptions {
|
||||
} else {
|
||||
MagicTrailingComma::Respect
|
||||
})
|
||||
.with_preview(if self.preview {
|
||||
PreviewMode::Enabled
|
||||
} else {
|
||||
PreviewMode::Disabled
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,14 +22,16 @@ fn generate_table(table_out: &mut String, rules: impl IntoIterator<Item = Rule>,
|
||||
for rule in rules {
|
||||
let fix_token = match rule.fixable() {
|
||||
FixAvailability::Always | FixAvailability::Sometimes => {
|
||||
format!("<span style='opacity: 1'>{FIX_SYMBOL}</span>")
|
||||
format!("<span title='Automatic fix available'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => {
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{FIX_SYMBOL}</span>")
|
||||
}
|
||||
FixAvailability::None => format!("<span style='opacity: 0.1'>{FIX_SYMBOL}</span>"),
|
||||
};
|
||||
let preview_token = if rule.is_preview() || rule.is_nursery() {
|
||||
format!("<span style='opacity: 1'>{PREVIEW_SYMBOL}</span>")
|
||||
format!("<span title='Rule is in preview'>{PREVIEW_SYMBOL}</span>")
|
||||
} else {
|
||||
format!("<span style='opacity: 0.1'>{PREVIEW_SYMBOL}</span>")
|
||||
format!("<span style='opacity: 0.1' aria-hidden='true'>{PREVIEW_SYMBOL}</span>")
|
||||
};
|
||||
let status_token = format!("{fix_token} {preview_token}");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -79,3 +79,6 @@ from ZeroDivisionError
|
||||
raise IndexError() from ZeroDivisionError
|
||||
|
||||
raise IndexError();
|
||||
|
||||
# RSE102
|
||||
raise Foo()
|
||||
|
||||
@@ -49,6 +49,13 @@ class Apples:
|
||||
def __doc__(self):
|
||||
return "Docstring"
|
||||
|
||||
# Added in Python 3.12
|
||||
def __buffer__(self):
|
||||
return memoryview(b'')
|
||||
|
||||
def __release_buffer__(self, buf):
|
||||
pass
|
||||
|
||||
# Allow dunder methods recommended by attrs.
|
||||
def __attrs_post_init__(self):
|
||||
pass
|
||||
|
||||
@@ -21,6 +21,9 @@ def f() -> None:
|
||||
# Invalid (but external)
|
||||
d = 1 # noqa: F841, V101
|
||||
|
||||
# Invalid (but external)
|
||||
d = 1 # noqa: V500
|
||||
|
||||
# fmt: off
|
||||
# Invalid - no space before #
|
||||
d = 1# noqa: E501
|
||||
|
||||
@@ -128,7 +128,10 @@ pub(crate) fn check_noqa(
|
||||
}
|
||||
|
||||
if line.matches.iter().any(|match_| *match_ == code)
|
||||
|| settings.external.contains(code)
|
||||
|| settings
|
||||
.external
|
||||
.iter()
|
||||
.any(|external| code.starts_with(external))
|
||||
{
|
||||
valid_codes.push(code);
|
||||
} else {
|
||||
|
||||
@@ -27,6 +27,13 @@ use crate::settings::LinterSettings;
|
||||
/// ```python
|
||||
/// foo = "bar's"
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter automatically removes unnecessary escapes, making the rule
|
||||
/// redundant.
|
||||
///
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct AvoidableEscapedQuote;
|
||||
|
||||
|
||||
@@ -32,6 +32,13 @@ use super::super::settings::Quote;
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-quotes.inline-quotes`
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent quotes for inline strings, making the rule
|
||||
/// redundant.
|
||||
///
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct BadQuotesInlineString {
|
||||
preferred_quote: Quote,
|
||||
@@ -81,6 +88,13 @@ impl AlwaysFixableViolation for BadQuotesInlineString {
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-quotes.multiline-quotes`
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces double quotes for multiline strings, making the rule
|
||||
/// redundant.
|
||||
///
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct BadQuotesMultilineString {
|
||||
preferred_quote: Quote,
|
||||
@@ -129,6 +143,13 @@ impl AlwaysFixableViolation for BadQuotesMultilineString {
|
||||
///
|
||||
/// ## Options
|
||||
/// - `flake8-quotes.docstring-quotes`
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces double quotes for docstrings, making the rule
|
||||
/// redundant.
|
||||
///
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct BadQuotesDocstring {
|
||||
preferred_quote: Quote,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{self as ast, Expr};
|
||||
use ruff_python_semantic::BindingKind;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
@@ -15,6 +16,16 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// Removing the parentheses makes the code more concise.
|
||||
///
|
||||
/// ## Known problems
|
||||
/// Parentheses can only be omitted if the exception is a class, as opposed to
|
||||
/// a function call. This rule isn't always capable of distinguishing between
|
||||
/// the two.
|
||||
///
|
||||
/// For example, if you import a function `module.get_exception` from another
|
||||
/// module, and `module.get_exception` returns an exception object, this rule will
|
||||
/// incorrectly mark the parentheses in `raise module.get_exception()` as
|
||||
/// unnecessary.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// raise TypeError()
|
||||
@@ -54,25 +65,32 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||
|
||||
if arguments.is_empty() {
|
||||
// `raise func()` still requires parentheses; only `raise Class()` does not.
|
||||
if checker
|
||||
.semantic()
|
||||
.lookup_attribute(func)
|
||||
.is_some_and(|id| checker.semantic().binding(id).kind.is_function_definition())
|
||||
{
|
||||
return;
|
||||
}
|
||||
let exception_type = if let Some(id) = checker.semantic().lookup_attribute(func) {
|
||||
match checker.semantic().binding(id).kind {
|
||||
BindingKind::FunctionDefinition(_) => return,
|
||||
BindingKind::ClassDefinition(_) => Some(ExceptionType::Class),
|
||||
BindingKind::Builtin => Some(ExceptionType::Builtin),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// `ctypes.WinError()` is a function, not a class. It's part of the standard library, so
|
||||
// we might as well get it right.
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["ctypes", "WinError"]))
|
||||
if exception_type
|
||||
.as_ref()
|
||||
.is_some_and(ExceptionType::is_builtin)
|
||||
&& checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["ctypes", "WinError"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(UnnecessaryParenOnRaiseException, arguments.range());
|
||||
|
||||
// If the arguments are immediately followed by a `from`, insert whitespace to avoid
|
||||
// a syntax error, as in:
|
||||
// ```python
|
||||
@@ -85,13 +103,25 @@ pub(crate) fn unnecessary_paren_on_raise_exception(checker: &mut Checker, expr:
|
||||
.next()
|
||||
.is_some_and(char::is_alphanumeric)
|
||||
{
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_replacement(
|
||||
" ".to_string(),
|
||||
arguments.range(),
|
||||
)));
|
||||
diagnostic.set_fix(if exception_type.is_some() {
|
||||
Fix::safe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
|
||||
} else {
|
||||
Fix::unsafe_edit(Edit::range_replacement(" ".to_string(), arguments.range()))
|
||||
});
|
||||
} else {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(arguments.range())));
|
||||
diagnostic.set_fix(if exception_type.is_some() {
|
||||
Fix::safe_edit(Edit::range_deletion(arguments.range()))
|
||||
} else {
|
||||
Fix::unsafe_edit(Edit::range_deletion(arguments.range()))
|
||||
});
|
||||
}
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, is_macro::Is)]
|
||||
enum ExceptionType {
|
||||
Class,
|
||||
Builtin,
|
||||
}
|
||||
|
||||
@@ -238,6 +238,7 @@ RSE102.py:79:17: RSE102 [*] Unnecessary parentheses on raised exception
|
||||
79 |+raise IndexError from ZeroDivisionError
|
||||
80 80 |
|
||||
81 81 | raise IndexError();
|
||||
82 82 |
|
||||
|
||||
RSE102.py:81:17: RSE102 [*] Unnecessary parentheses on raised exception
|
||||
|
|
||||
@@ -245,6 +246,8 @@ RSE102.py:81:17: RSE102 [*] Unnecessary parentheses on raised exception
|
||||
80 |
|
||||
81 | raise IndexError();
|
||||
| ^^ RSE102
|
||||
82 |
|
||||
83 | # RSE102
|
||||
|
|
||||
= help: Remove unnecessary parentheses
|
||||
|
||||
@@ -254,5 +257,23 @@ RSE102.py:81:17: RSE102 [*] Unnecessary parentheses on raised exception
|
||||
80 80 |
|
||||
81 |-raise IndexError();
|
||||
81 |+raise IndexError;
|
||||
82 82 |
|
||||
83 83 | # RSE102
|
||||
84 84 | raise Foo()
|
||||
|
||||
RSE102.py:84:10: RSE102 [*] Unnecessary parentheses on raised exception
|
||||
|
|
||||
83 | # RSE102
|
||||
84 | raise Foo()
|
||||
| ^^ RSE102
|
||||
|
|
||||
= help: Remove unnecessary parentheses
|
||||
|
||||
ℹ Suggested fix
|
||||
81 81 | raise IndexError();
|
||||
82 82 |
|
||||
83 83 | # RSE102
|
||||
84 |-raise Foo()
|
||||
84 |+raise Foo
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ pub(crate) fn organize_imports(
|
||||
block,
|
||||
comments,
|
||||
locator,
|
||||
settings.pycodestyle.max_line_length,
|
||||
settings.line_length,
|
||||
LineWidthBuilder::new(settings.tab_size).add_str(indentation),
|
||||
stylist,
|
||||
&settings.src,
|
||||
|
||||
@@ -5,7 +5,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_index::Indexer;
|
||||
use ruff_python_parser::Tok;
|
||||
use ruff_source_file::Locator;
|
||||
use ruff_text_size::{Ranged, TextLen, TextRange, TextSize};
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
|
||||
use crate::fix::edits::pad_start;
|
||||
|
||||
@@ -25,23 +25,51 @@ use crate::fix::edits::pad_start;
|
||||
/// regex = r"\.png$"
|
||||
/// ```
|
||||
///
|
||||
/// Or, if the string already contains a valid escape sequence:
|
||||
/// ```python
|
||||
/// value = "new line\nand invalid escape \_ here"
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// value = "new line\nand invalid escape \\_ here"
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: String and Bytes literals](https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals)
|
||||
#[violation]
|
||||
pub struct InvalidEscapeSequence(char);
|
||||
pub struct InvalidEscapeSequence {
|
||||
ch: char,
|
||||
fix_title: FixTitle,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for InvalidEscapeSequence {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let InvalidEscapeSequence(char) = self;
|
||||
format!("Invalid escape sequence: `\\{char}`")
|
||||
let InvalidEscapeSequence { ch, .. } = self;
|
||||
format!("Invalid escape sequence: `\\{ch}`")
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Add backslash to escape sequence".to_string()
|
||||
match self.fix_title {
|
||||
FixTitle::AddBackslash => format!("Add backslash to escape sequence"),
|
||||
FixTitle::UseRawStringLiteral => format!("Use a raw string literal"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum FixTitle {
|
||||
AddBackslash,
|
||||
UseRawStringLiteral,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InvalidEscapeChar {
|
||||
ch: char,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
/// W605
|
||||
pub(crate) fn invalid_escape_sequence(
|
||||
diagnostics: &mut Vec<Diagnostic>,
|
||||
@@ -67,7 +95,7 @@ pub(crate) fn invalid_escape_sequence(
|
||||
};
|
||||
|
||||
let mut contains_valid_escape_sequence = false;
|
||||
let mut invalid_escape_sequence = Vec::new();
|
||||
let mut invalid_escape_chars = Vec::new();
|
||||
|
||||
let mut prev = None;
|
||||
let bytes = token_source_code.as_bytes();
|
||||
@@ -154,16 +182,28 @@ pub(crate) fn invalid_escape_sequence(
|
||||
|
||||
let location = token_range.start() + TextSize::try_from(i).unwrap();
|
||||
let range = TextRange::at(location, next_char.text_len() + TextSize::from(1));
|
||||
invalid_escape_sequence.push(Diagnostic::new(InvalidEscapeSequence(next_char), range));
|
||||
invalid_escape_chars.push(InvalidEscapeChar {
|
||||
ch: next_char,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
let mut invalid_escape_sequence = Vec::new();
|
||||
if contains_valid_escape_sequence {
|
||||
// Escape with backslash.
|
||||
for diagnostic in &mut invalid_escape_sequence {
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
for invalid_escape_char in &invalid_escape_chars {
|
||||
let diagnostic = Diagnostic::new(
|
||||
InvalidEscapeSequence {
|
||||
ch: invalid_escape_char.ch,
|
||||
fix_title: FixTitle::AddBackslash,
|
||||
},
|
||||
invalid_escape_char.range,
|
||||
)
|
||||
.with_fix(Fix::safe_edit(Edit::insertion(
|
||||
r"\".to_string(),
|
||||
diagnostic.start() + TextSize::from(1),
|
||||
invalid_escape_char.range.start() + TextSize::from(1),
|
||||
)));
|
||||
invalid_escape_sequence.push(diagnostic);
|
||||
}
|
||||
} else {
|
||||
let tok_start = if token.is_f_string_middle() {
|
||||
@@ -178,14 +218,24 @@ pub(crate) fn invalid_escape_sequence(
|
||||
token_range.start()
|
||||
};
|
||||
// Turn into raw string.
|
||||
for diagnostic in &mut invalid_escape_sequence {
|
||||
// If necessary, add a space between any leading keyword (`return`, `yield`,
|
||||
// `assert`, etc.) and the string. For example, `return"foo"` is valid, but
|
||||
// `returnr"foo"` is not.
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::insertion(
|
||||
pad_start("r".to_string(), tok_start, locator),
|
||||
tok_start,
|
||||
)));
|
||||
for invalid_escape_char in &invalid_escape_chars {
|
||||
let diagnostic = Diagnostic::new(
|
||||
InvalidEscapeSequence {
|
||||
ch: invalid_escape_char.ch,
|
||||
fix_title: FixTitle::UseRawStringLiteral,
|
||||
},
|
||||
invalid_escape_char.range,
|
||||
)
|
||||
.with_fix(
|
||||
// If necessary, add a space between any leading keyword (`return`, `yield`,
|
||||
// `assert`, etc.) and the string. For example, `return"foo"` is valid, but
|
||||
// `returnr"foo"` is not.
|
||||
Fix::safe_edit(Edit::insertion(
|
||||
pad_start("r".to_string(), tok_start, locator),
|
||||
tok_start,
|
||||
)),
|
||||
);
|
||||
invalid_escape_sequence.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,16 @@ use super::LogicalLine;
|
||||
/// a = 1
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent indentation, making the rule redundant.
|
||||
///
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `indent-width` with a value other than `4`.
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#indentation
|
||||
/// [formatter]:https://docs.astral.sh/ruff/formatter/
|
||||
#[violation]
|
||||
pub struct IndentationWithInvalidMultiple {
|
||||
indent_size: usize,
|
||||
@@ -55,7 +64,15 @@ impl Violation for IndentationWithInvalidMultiple {
|
||||
/// # a = 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent indentation, making the rule redundant.
|
||||
///
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `indent-width` with a value other than `4`.
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#indentation
|
||||
/// [formatter]:https://docs.astral.sh/ruff/formatter/
|
||||
#[violation]
|
||||
pub struct IndentationWithInvalidMultipleComment {
|
||||
indent_size: usize,
|
||||
|
||||
@@ -26,7 +26,15 @@ use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
/// a = 1
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent indentation, making the rule redundant.
|
||||
///
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `format.indent-style="tab"`.
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#tabs-or-spaces
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct TabIndentation;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ W605_0.py:2:10: W605 [*] Invalid escape sequence: `\.`
|
||||
3 |
|
||||
4 | #: W605:2:1
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
1 1 | #: W605:1:10
|
||||
@@ -27,7 +27,7 @@ W605_0.py:6:1: W605 [*] Invalid escape sequence: `\.`
|
||||
| ^^ W605
|
||||
7 | '''
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
2 2 | regex = '\.png$'
|
||||
@@ -47,7 +47,7 @@ W605_0.py:11:6: W605 [*] Invalid escape sequence: `\_`
|
||||
| ^^ W605
|
||||
12 | )
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
@@ -68,7 +68,7 @@ W605_0.py:18:6: W605 [*] Invalid escape sequence: `\_`
|
||||
19 | in the middle
|
||||
20 | """
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
12 12 | )
|
||||
@@ -107,7 +107,7 @@ W605_0.py:28:12: W605 [*] Invalid escape sequence: `\.`
|
||||
29 |
|
||||
30 | #: Okay
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
25 25 |
|
||||
|
||||
@@ -9,7 +9,7 @@ W605_1.py:2:10: W605 [*] Invalid escape sequence: `\.`
|
||||
3 |
|
||||
4 | #: W605:2:1
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
1 1 | #: W605:1:10
|
||||
@@ -27,7 +27,7 @@ W605_1.py:6:1: W605 [*] Invalid escape sequence: `\.`
|
||||
| ^^ W605
|
||||
7 | '''
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
2 2 | regex = '\.png$'
|
||||
@@ -47,7 +47,7 @@ W605_1.py:11:6: W605 [*] Invalid escape sequence: `\_`
|
||||
| ^^ W605
|
||||
12 | )
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
8 8 |
|
||||
@@ -68,7 +68,7 @@ W605_1.py:18:6: W605 [*] Invalid escape sequence: `\_`
|
||||
19 | in the middle
|
||||
20 | """
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
12 12 | )
|
||||
@@ -89,7 +89,7 @@ W605_1.py:25:12: W605 [*] Invalid escape sequence: `\.`
|
||||
26 |
|
||||
27 | #: Okay
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
22 22 |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/pycodestyle/mod.rs
|
||||
source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
|
||||
---
|
||||
W605_2.py:4:11: W605 [*] Invalid escape sequence: `\.`
|
||||
|
|
||||
@@ -9,7 +9,7 @@ W605_2.py:4:11: W605 [*] Invalid escape sequence: `\.`
|
||||
5 |
|
||||
6 | #: W605:2:1
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
1 1 | # Same as `W605_0.py` but using f-strings instead.
|
||||
@@ -29,7 +29,7 @@ W605_2.py:8:1: W605 [*] Invalid escape sequence: `\.`
|
||||
| ^^ W605
|
||||
9 | '''
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
4 4 | regex = f'\.png$'
|
||||
@@ -49,7 +49,7 @@ W605_2.py:13:7: W605 [*] Invalid escape sequence: `\_`
|
||||
| ^^ W605
|
||||
14 | )
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
10 10 |
|
||||
@@ -70,7 +70,7 @@ W605_2.py:20:6: W605 [*] Invalid escape sequence: `\_`
|
||||
21 | in the middle
|
||||
22 | """
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
14 14 | )
|
||||
@@ -129,7 +129,7 @@ W605_2.py:44:11: W605 [*] Invalid escape sequence: `\{`
|
||||
45 | value = f'\{1}'
|
||||
46 | value = f'{1:\}'
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
41 41 | ''' # noqa
|
||||
@@ -150,7 +150,7 @@ W605_2.py:45:11: W605 [*] Invalid escape sequence: `\{`
|
||||
46 | value = f'{1:\}'
|
||||
47 | value = f"{f"\{1}"}"
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
42 42 |
|
||||
@@ -171,7 +171,7 @@ W605_2.py:46:14: W605 [*] Invalid escape sequence: `\}`
|
||||
47 | value = f"{f"\{1}"}"
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
43 43 | regex = f'\\\_'
|
||||
@@ -191,7 +191,7 @@ W605_2.py:47:14: W605 [*] Invalid escape sequence: `\{`
|
||||
| ^^ W605
|
||||
48 | value = rf"{f"\{1}"}"
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
44 44 | value = f'\{{1}}'
|
||||
@@ -212,7 +212,7 @@ W605_2.py:48:15: W605 [*] Invalid escape sequence: `\{`
|
||||
49 |
|
||||
50 | # Okay
|
||||
|
|
||||
= help: Add backslash to escape sequence
|
||||
= help: Use a raw string literal
|
||||
|
||||
ℹ Fix
|
||||
45 45 | value = f'\{1}'
|
||||
|
||||
@@ -14,9 +14,7 @@ use crate::registry::Rule;
|
||||
/// Checks for docstrings that are indented with tabs.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// [PEP 8](https://peps.python.org/pep-0008/#tabs-or-spaces) recommends using
|
||||
/// spaces over tabs for indentation.
|
||||
///
|
||||
/// [PEP 8] recommends using spaces over tabs for indentation.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -38,10 +36,20 @@ use crate::registry::Rule;
|
||||
/// """
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent indentation, making the rule redundant.
|
||||
///
|
||||
/// The rule is also incompatible with the [formatter] when using
|
||||
/// `format.indent-style="tab"`.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/#tabs-or-spaces
|
||||
/// [formatter]: https://docs.astral.sh/ruff/formatter
|
||||
#[violation]
|
||||
pub struct IndentWithSpaces;
|
||||
|
||||
@@ -126,12 +134,17 @@ impl AlwaysFixableViolation for UnderIndentation {
|
||||
/// """
|
||||
/// ```
|
||||
///
|
||||
/// ## Formatter compatibility
|
||||
/// We recommend against using this rule alongside the [formatter]. The
|
||||
/// formatter enforces consistent indentation, making the rule redundant.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
/// - [NumPy Style Guide](https://numpydoc.readthedocs.io/en/latest/format.html)
|
||||
/// - [Google Python Style Guide - Docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
||||
///
|
||||
/// [PEP 257]: https://peps.python.org/pep-0257/
|
||||
/// [formatter]:https://docs.astral.sh/ruff/formatter/
|
||||
#[violation]
|
||||
pub struct OverIndentation;
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ fn is_known_dunder_method(method: &str) -> bool {
|
||||
| "__attrs_pre_init__"
|
||||
| "__await__"
|
||||
| "__bool__"
|
||||
| "__buffer__"
|
||||
| "__bytes__"
|
||||
| "__call__"
|
||||
| "__ceil__"
|
||||
@@ -166,6 +167,7 @@ fn is_known_dunder_method(method: &str) -> bool {
|
||||
| "__rdivmod__"
|
||||
| "__reduce__"
|
||||
| "__reduce_ex__"
|
||||
| "__release_buffer__"
|
||||
| "__repr__"
|
||||
| "__reversed__"
|
||||
| "__rfloordiv__"
|
||||
|
||||
@@ -106,7 +106,26 @@ mod tests {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/RUF100_0.py"),
|
||||
&settings::LinterSettings {
|
||||
external: FxHashSet::from_iter(vec!["V101".to_string()]),
|
||||
external: vec!["V101".to_string()],
|
||||
..settings::LinterSettings::for_rules(vec![
|
||||
Rule::UnusedNOQA,
|
||||
Rule::LineTooLong,
|
||||
Rule::UnusedImport,
|
||||
Rule::UnusedVariable,
|
||||
Rule::TabIndentation,
|
||||
])
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruf100_0_prefix() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff/RUF100_0.py"),
|
||||
&settings::LinterSettings {
|
||||
external: vec!["V".to_string()],
|
||||
..settings::LinterSettings::for_rules(vec
|
||||
/// - [Ruff error suppression](https://docs.astral.sh/ruff/linter/#error-suppression)
|
||||
#[violation]
|
||||
pub struct UnusedNOQA {
|
||||
pub codes: Option<UnusedCodes>,
|
||||
|
||||
@@ -86,7 +86,7 @@ RUF100_0.py:22:12: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
22 | d = 1 # noqa: F841, V101
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
23 |
|
||||
24 | # fmt: off
|
||||
24 | # Invalid (but external)
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
@@ -97,171 +97,191 @@ RUF100_0.py:22:12: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
22 |- d = 1 # noqa: F841, V101
|
||||
22 |+ d = 1 # noqa: V101
|
||||
23 23 |
|
||||
24 24 | # fmt: off
|
||||
25 25 | # Invalid - no space before #
|
||||
24 24 | # Invalid (but external)
|
||||
25 25 | d = 1 # noqa: V500
|
||||
|
||||
RUF100_0.py:26:10: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
RUF100_0.py:25:12: RUF100 [*] Unused `noqa` directive (unknown: `V500`)
|
||||
|
|
||||
24 | # fmt: off
|
||||
25 | # Invalid - no space before #
|
||||
26 | d = 1# noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
27 |
|
||||
28 | # Invalid - many spaces before #
|
||||
24 | # Invalid (but external)
|
||||
25 | d = 1 # noqa: V500
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
26 |
|
||||
27 | # fmt: off
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
22 22 | d = 1 # noqa: F841, V101
|
||||
23 23 |
|
||||
24 24 | # fmt: off
|
||||
25 25 | # Invalid - no space before #
|
||||
26 |- d = 1# noqa: E501
|
||||
26 |+ d = 1
|
||||
27 27 |
|
||||
28 28 | # Invalid - many spaces before #
|
||||
29 29 | d = 1 # noqa: E501
|
||||
24 24 | # Invalid (but external)
|
||||
25 |- d = 1 # noqa: V500
|
||||
25 |+ d = 1
|
||||
26 26 |
|
||||
27 27 | # fmt: off
|
||||
28 28 | # Invalid - no space before #
|
||||
|
||||
RUF100_0.py:29:5: F841 [*] Local variable `d` is assigned to but never used
|
||||
RUF100_0.py:29:10: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
28 | # Invalid - many spaces before #
|
||||
29 | d = 1 # noqa: E501
|
||||
27 | # fmt: off
|
||||
28 | # Invalid - no space before #
|
||||
29 | d = 1# noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
30 |
|
||||
31 | # Invalid - many spaces before #
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
26 26 |
|
||||
27 27 | # fmt: off
|
||||
28 28 | # Invalid - no space before #
|
||||
29 |- d = 1# noqa: E501
|
||||
29 |+ d = 1
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 32 | d = 1 # noqa: E501
|
||||
|
||||
RUF100_0.py:32:5: F841 [*] Local variable `d` is assigned to but never used
|
||||
|
|
||||
31 | # Invalid - many spaces before #
|
||||
32 | d = 1 # noqa: E501
|
||||
| ^ F841
|
||||
30 | # fmt: on
|
||||
33 | # fmt: on
|
||||
|
|
||||
= help: Remove assignment to unused variable `d`
|
||||
|
||||
ℹ Suggested fix
|
||||
26 26 | d = 1# noqa: E501
|
||||
27 27 |
|
||||
28 28 | # Invalid - many spaces before #
|
||||
29 |- d = 1 # noqa: E501
|
||||
30 29 | # fmt: on
|
||||
31 30 |
|
||||
32 31 |
|
||||
29 29 | d = 1# noqa: E501
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 |- d = 1 # noqa: E501
|
||||
33 32 | # fmt: on
|
||||
34 33 |
|
||||
35 34 |
|
||||
|
||||
RUF100_0.py:29:33: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
RUF100_0.py:32:33: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
28 | # Invalid - many spaces before #
|
||||
29 | d = 1 # noqa: E501
|
||||
31 | # Invalid - many spaces before #
|
||||
32 | d = 1 # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
30 | # fmt: on
|
||||
33 | # fmt: on
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
26 26 | d = 1# noqa: E501
|
||||
27 27 |
|
||||
28 28 | # Invalid - many spaces before #
|
||||
29 |- d = 1 # noqa: E501
|
||||
29 |+ d = 1
|
||||
30 30 | # fmt: on
|
||||
31 31 |
|
||||
32 32 |
|
||||
29 29 | d = 1# noqa: E501
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 |- d = 1 # noqa: E501
|
||||
32 |+ d = 1
|
||||
33 33 | # fmt: on
|
||||
34 34 |
|
||||
35 35 |
|
||||
|
||||
RUF100_0.py:55:6: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
RUF100_0.py:58:6: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
|
|
||||
54 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
55 | """ # noqa: E501, F841
|
||||
57 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
58 | """ # noqa: E501, F841
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
56 |
|
||||
57 | # Invalid
|
||||
59 |
|
||||
60 | # Invalid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
52 52 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
53 53 |
|
||||
54 54 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
55 |-""" # noqa: E501, F841
|
||||
55 |+""" # noqa: E501
|
||||
55 55 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
56 56 |
|
||||
57 57 | # Invalid
|
||||
58 58 | _ = """Lorem ipsum dolor sit amet.
|
||||
57 57 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
58 |-""" # noqa: E501, F841
|
||||
58 |+""" # noqa: E501
|
||||
59 59 |
|
||||
60 60 | # Invalid
|
||||
61 61 | _ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
RUF100_0.py:63:6: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
RUF100_0.py:66:6: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
62 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
63 | """ # noqa: E501
|
||||
65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
66 | """ # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
64 |
|
||||
65 | # Invalid
|
||||
67 |
|
||||
68 | # Invalid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
60 60 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
61 61 |
|
||||
62 62 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
63 |-""" # noqa: E501
|
||||
63 |+"""
|
||||
63 63 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
64 64 |
|
||||
65 65 | # Invalid
|
||||
66 66 | _ = """Lorem ipsum dolor sit amet.
|
||||
65 65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
66 |-""" # noqa: E501
|
||||
66 |+"""
|
||||
67 67 |
|
||||
68 68 | # Invalid
|
||||
69 69 | _ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
RUF100_0.py:71:6: RUF100 [*] Unused blanket `noqa` directive
|
||||
RUF100_0.py:74:6: RUF100 [*] Unused blanket `noqa` directive
|
||||
|
|
||||
70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
71 | """ # noqa
|
||||
73 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
74 | """ # noqa
|
||||
| ^^^^^^ RUF100
|
||||
72 |
|
||||
73 | # Valid
|
||||
75 |
|
||||
76 | # Valid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
68 68 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
69 69 |
|
||||
70 70 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
71 |-""" # noqa
|
||||
71 |+"""
|
||||
71 71 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
72 72 |
|
||||
73 73 | # Valid
|
||||
74 74 | # this is a veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy long comment # noqa: E501
|
||||
73 73 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
74 |-""" # noqa
|
||||
74 |+"""
|
||||
75 75 |
|
||||
76 76 | # Valid
|
||||
77 77 | # this is a veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy long comment # noqa: E501
|
||||
|
||||
RUF100_0.py:85:8: F401 [*] `shelve` imported but unused
|
||||
RUF100_0.py:88:8: F401 [*] `shelve` imported but unused
|
||||
|
|
||||
83 | import collections # noqa
|
||||
84 | import os # noqa: F401, RUF100
|
||||
85 | import shelve # noqa: RUF100
|
||||
86 | import collections # noqa
|
||||
87 | import os # noqa: F401, RUF100
|
||||
88 | import shelve # noqa: RUF100
|
||||
| ^^^^^^ F401
|
||||
86 | import sys # noqa: F401, RUF100
|
||||
89 | import sys # noqa: F401, RUF100
|
||||
|
|
||||
= help: Remove unused import: `shelve`
|
||||
|
||||
ℹ Fix
|
||||
82 82 |
|
||||
83 83 | import collections # noqa
|
||||
84 84 | import os # noqa: F401, RUF100
|
||||
85 |-import shelve # noqa: RUF100
|
||||
86 85 | import sys # noqa: F401, RUF100
|
||||
87 86 |
|
||||
88 87 | print(sys.path)
|
||||
85 85 |
|
||||
86 86 | import collections # noqa
|
||||
87 87 | import os # noqa: F401, RUF100
|
||||
88 |-import shelve # noqa: RUF100
|
||||
89 88 | import sys # noqa: F401, RUF100
|
||||
90 89 |
|
||||
91 90 | print(sys.path)
|
||||
|
||||
RUF100_0.py:90:89: E501 Line too long (89 > 88)
|
||||
RUF100_0.py:93:89: E501 Line too long (89 > 88)
|
||||
|
|
||||
88 | print(sys.path)
|
||||
89 |
|
||||
90 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
91 | print(sys.path)
|
||||
92 |
|
||||
93 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
| ^ E501
|
||||
|
|
||||
|
||||
RUF100_0.py:90:92: RUF100 [*] Unused `noqa` directive (unused: `F401`)
|
||||
RUF100_0.py:93:92: RUF100 [*] Unused `noqa` directive (unused: `F401`)
|
||||
|
|
||||
88 | print(sys.path)
|
||||
89 |
|
||||
90 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
91 | print(sys.path)
|
||||
92 |
|
||||
93 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
87 87 |
|
||||
88 88 | print(sys.path)
|
||||
89 89 |
|
||||
90 |-"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
90 |+"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]"
|
||||
91 91 |
|
||||
90 90 |
|
||||
91 91 | print(sys.path)
|
||||
92 92 |
|
||||
93 93 | def f():
|
||||
93 |-"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
93 |+"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]"
|
||||
94 94 |
|
||||
95 95 |
|
||||
96 96 | def f():
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/ruff/mod.rs
|
||||
---
|
||||
RUF100_0.py:9:12: RUF100 [*] Unused blanket `noqa` directive
|
||||
|
|
||||
8 | # Invalid
|
||||
9 | c = 1 # noqa
|
||||
| ^^^^^^ RUF100
|
||||
10 | print(c)
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
6 6 | b = 2 # noqa: F841
|
||||
7 7 |
|
||||
8 8 | # Invalid
|
||||
9 |- c = 1 # noqa
|
||||
9 |+ c = 1
|
||||
10 10 | print(c)
|
||||
11 11 |
|
||||
12 12 | # Invalid
|
||||
|
||||
RUF100_0.py:13:12: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
12 | # Invalid
|
||||
13 | d = 1 # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
14 |
|
||||
15 | # Invalid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
10 10 | print(c)
|
||||
11 11 |
|
||||
12 12 | # Invalid
|
||||
13 |- d = 1 # noqa: E501
|
||||
13 |+ d = 1
|
||||
14 14 |
|
||||
15 15 | # Invalid
|
||||
16 16 | d = 1 # noqa: F841, E501
|
||||
|
||||
RUF100_0.py:16:12: RUF100 [*] Unused `noqa` directive (unused: `F841`, `E501`)
|
||||
|
|
||||
15 | # Invalid
|
||||
16 | d = 1 # noqa: F841, E501
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
17 |
|
||||
18 | # Invalid (and unimplemented or not enabled)
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
13 13 | d = 1 # noqa: E501
|
||||
14 14 |
|
||||
15 15 | # Invalid
|
||||
16 |- d = 1 # noqa: F841, E501
|
||||
16 |+ d = 1
|
||||
17 17 |
|
||||
18 18 | # Invalid (and unimplemented or not enabled)
|
||||
19 19 | d = 1 # noqa: F841, W191, F821
|
||||
|
||||
RUF100_0.py:19:12: RUF100 [*] Unused `noqa` directive (unused: `F841`, `W191`; non-enabled: `F821`)
|
||||
|
|
||||
18 | # Invalid (and unimplemented or not enabled)
|
||||
19 | d = 1 # noqa: F841, W191, F821
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^ RUF100
|
||||
20 |
|
||||
21 | # Invalid (but external)
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
16 16 | d = 1 # noqa: F841, E501
|
||||
17 17 |
|
||||
18 18 | # Invalid (and unimplemented or not enabled)
|
||||
19 |- d = 1 # noqa: F841, W191, F821
|
||||
19 |+ d = 1
|
||||
20 20 |
|
||||
21 21 | # Invalid (but external)
|
||||
22 22 | d = 1 # noqa: F841, V101
|
||||
|
||||
RUF100_0.py:22:12: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
|
|
||||
21 | # Invalid (but external)
|
||||
22 | d = 1 # noqa: F841, V101
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
23 |
|
||||
24 | # Invalid (but external)
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
19 19 | d = 1 # noqa: F841, W191, F821
|
||||
20 20 |
|
||||
21 21 | # Invalid (but external)
|
||||
22 |- d = 1 # noqa: F841, V101
|
||||
22 |+ d = 1 # noqa: V101
|
||||
23 23 |
|
||||
24 24 | # Invalid (but external)
|
||||
25 25 | d = 1 # noqa: V500
|
||||
|
||||
RUF100_0.py:29:10: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
27 | # fmt: off
|
||||
28 | # Invalid - no space before #
|
||||
29 | d = 1# noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
30 |
|
||||
31 | # Invalid - many spaces before #
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
26 26 |
|
||||
27 27 | # fmt: off
|
||||
28 28 | # Invalid - no space before #
|
||||
29 |- d = 1# noqa: E501
|
||||
29 |+ d = 1
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 32 | d = 1 # noqa: E501
|
||||
|
||||
RUF100_0.py:32:5: F841 [*] Local variable `d` is assigned to but never used
|
||||
|
|
||||
31 | # Invalid - many spaces before #
|
||||
32 | d = 1 # noqa: E501
|
||||
| ^ F841
|
||||
33 | # fmt: on
|
||||
|
|
||||
= help: Remove assignment to unused variable `d`
|
||||
|
||||
ℹ Suggested fix
|
||||
29 29 | d = 1# noqa: E501
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 |- d = 1 # noqa: E501
|
||||
33 32 | # fmt: on
|
||||
34 33 |
|
||||
35 34 |
|
||||
|
||||
RUF100_0.py:32:33: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
31 | # Invalid - many spaces before #
|
||||
32 | d = 1 # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
33 | # fmt: on
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
29 29 | d = 1# noqa: E501
|
||||
30 30 |
|
||||
31 31 | # Invalid - many spaces before #
|
||||
32 |- d = 1 # noqa: E501
|
||||
32 |+ d = 1
|
||||
33 33 | # fmt: on
|
||||
34 34 |
|
||||
35 35 |
|
||||
|
||||
RUF100_0.py:58:6: RUF100 [*] Unused `noqa` directive (unused: `F841`)
|
||||
|
|
||||
57 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
58 | """ # noqa: E501, F841
|
||||
| ^^^^^^^^^^^^^^^^^^ RUF100
|
||||
59 |
|
||||
60 | # Invalid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
55 55 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
56 56 |
|
||||
57 57 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
58 |-""" # noqa: E501, F841
|
||||
58 |+""" # noqa: E501
|
||||
59 59 |
|
||||
60 60 | # Invalid
|
||||
61 61 | _ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
RUF100_0.py:66:6: RUF100 [*] Unused `noqa` directive (unused: `E501`)
|
||||
|
|
||||
65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
66 | """ # noqa: E501
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
67 |
|
||||
68 | # Invalid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
63 63 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
64 64 |
|
||||
65 65 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
66 |-""" # noqa: E501
|
||||
66 |+"""
|
||||
67 67 |
|
||||
68 68 | # Invalid
|
||||
69 69 | _ = """Lorem ipsum dolor sit amet.
|
||||
|
||||
RUF100_0.py:74:6: RUF100 [*] Unused blanket `noqa` directive
|
||||
|
|
||||
73 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
74 | """ # noqa
|
||||
| ^^^^^^ RUF100
|
||||
75 |
|
||||
76 | # Valid
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
71 71 | https://github.com/PyCQA/pycodestyle/pull/258/files#diff-841c622497a8033d10152bfdfb15b20b92437ecdea21a260944ea86b77b51533
|
||||
72 72 |
|
||||
73 73 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.
|
||||
74 |-""" # noqa
|
||||
74 |+"""
|
||||
75 75 |
|
||||
76 76 | # Valid
|
||||
77 77 | # this is a veryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy long comment # noqa: E501
|
||||
|
||||
RUF100_0.py:88:8: F401 [*] `shelve` imported but unused
|
||||
|
|
||||
86 | import collections # noqa
|
||||
87 | import os # noqa: F401, RUF100
|
||||
88 | import shelve # noqa: RUF100
|
||||
| ^^^^^^ F401
|
||||
89 | import sys # noqa: F401, RUF100
|
||||
|
|
||||
= help: Remove unused import: `shelve`
|
||||
|
||||
ℹ Fix
|
||||
85 85 |
|
||||
86 86 | import collections # noqa
|
||||
87 87 | import os # noqa: F401, RUF100
|
||||
88 |-import shelve # noqa: RUF100
|
||||
89 88 | import sys # noqa: F401, RUF100
|
||||
90 89 |
|
||||
91 90 | print(sys.path)
|
||||
|
||||
RUF100_0.py:93:89: E501 Line too long (89 > 88)
|
||||
|
|
||||
91 | print(sys.path)
|
||||
92 |
|
||||
93 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
| ^ E501
|
||||
|
|
||||
|
||||
RUF100_0.py:93:92: RUF100 [*] Unused `noqa` directive (unused: `F401`)
|
||||
|
|
||||
91 | print(sys.path)
|
||||
92 |
|
||||
93 | "shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
| ^^^^^^^^^^^^ RUF100
|
||||
|
|
||||
= help: Remove unused `noqa` directive
|
||||
|
||||
ℹ Fix
|
||||
90 90 |
|
||||
91 91 | print(sys.path)
|
||||
92 92 |
|
||||
93 |-"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]" # noqa: F401
|
||||
93 |+"shape: (6,)\nSeries: '' [duration[μs]]\n[\n\t0µs\n\t1µs\n\t2µs\n\t3µs\n\t4µs\n\t5µs\n]"
|
||||
94 94 |
|
||||
95 95 |
|
||||
96 96 | def f():
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//! command-line options. Structure is optimized for internal usage, as opposed
|
||||
//! to external visibility or parsing.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -15,6 +14,7 @@ use rustc_hash::FxHashSet;
|
||||
use crate::codes::RuleCodePrefix;
|
||||
use ruff_macros::CacheKey;
|
||||
|
||||
use crate::line_width::LineLength;
|
||||
use crate::registry::{Linter, Rule, RuleSet};
|
||||
use crate::rules::{
|
||||
flake8_annotations, flake8_bandit, flake8_bugbear, flake8_builtins, flake8_comprehensions,
|
||||
@@ -54,12 +54,13 @@ pub struct LinterSettings {
|
||||
pub allowed_confusables: FxHashSet<char>,
|
||||
pub builtins: Vec<String>,
|
||||
pub dummy_variable_rgx: Regex,
|
||||
pub external: FxHashSet<String>,
|
||||
pub external: Vec<String>,
|
||||
pub ignore_init_module_imports: bool,
|
||||
pub logger_objects: Vec<String>,
|
||||
pub namespace_packages: Vec<PathBuf>,
|
||||
pub src: Vec<PathBuf>,
|
||||
pub tab_size: IndentWidth,
|
||||
pub line_length: LineLength,
|
||||
pub task_tags: Vec<String>,
|
||||
pub typing_modules: Vec<String>,
|
||||
|
||||
@@ -144,7 +145,7 @@ impl LinterSettings {
|
||||
builtins: vec![],
|
||||
dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone(),
|
||||
|
||||
external: HashSet::default(),
|
||||
external: vec![],
|
||||
ignore_init_module_imports: false,
|
||||
logger_objects: vec![],
|
||||
namespace_packages: vec![],
|
||||
@@ -156,6 +157,7 @@ impl LinterSettings {
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
// Needs duplicating
|
||||
tab_size: IndentWidth::default(),
|
||||
line_length: LineLength::default(),
|
||||
|
||||
task_tags: TASK_TAGS.iter().map(ToString::to_string).collect(),
|
||||
typing_modules: vec![],
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use similar::TextDiff;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -88,7 +88,12 @@ impl SourceKind {
|
||||
}
|
||||
|
||||
/// Write a diff of the transformed source file to `stdout`.
|
||||
pub fn diff(&self, other: &Self, path: Option<&Path>, writer: &mut dyn Write) -> Result<()> {
|
||||
pub fn diff(
|
||||
&self,
|
||||
other: &Self,
|
||||
path: Option<&Path>,
|
||||
writer: &mut dyn Write,
|
||||
) -> io::Result<()> {
|
||||
match (self, other) {
|
||||
(SourceKind::Python(src), SourceKind::Python(dst)) => {
|
||||
let text_diff = TextDiff::from_lines(src, dst);
|
||||
@@ -154,7 +159,7 @@ impl SourceKind {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => bail!("cannot diff Python source code with Jupyter notebook source code"),
|
||||
_ => panic!("cannot diff Python source code with Jupyter notebook source code"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,28 +537,6 @@ def update_emission_strength():
|
||||
value = self.emission_strength * 2
|
||||
```
|
||||
|
||||
#### Type annotations may be parenthesized when expanded ([#7315](https://github.com/astral-sh/ruff/issues/7315))
|
||||
|
||||
Black will avoid parenthesizing type annotations in an annotated assignment, while Ruff will insert
|
||||
parentheses in some cases.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
# Black
|
||||
StartElementHandler: Callable[[str, dict[str, str]], Any] | Callable[[str, list[str]], Any] | Callable[
|
||||
[str, dict[str, str], list[str]], Any
|
||||
] | None
|
||||
|
||||
# Ruff
|
||||
StartElementHandler: (
|
||||
Callable[[str, dict[str, str]], Any]
|
||||
| Callable[[str, list[str]], Any]
|
||||
| Callable[[str, dict[str, str], list[str]], Any]
|
||||
| None
|
||||
)
|
||||
```
|
||||
|
||||
#### Call chain calls break differently ([#7051](https://github.com/astral-sh/ruff/issues/7051))
|
||||
|
||||
Black occasionally breaks call chains differently than Ruff; in particular, Black occasionally
|
||||
|
||||
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_pow_spacing.py
vendored
Normal file
10
crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_pow_spacing.py
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# No spacing
|
||||
5 ** 5
|
||||
5.0 ** 5.0
|
||||
1e5 ** 2e5
|
||||
True ** True
|
||||
False ** False
|
||||
None ** None
|
||||
|
||||
# Space
|
||||
"a" ** "b"
|
||||
@@ -180,3 +180,16 @@ if "root" not in (
|
||||
):
|
||||
msg = "Could not find root. Please try a different forest."
|
||||
raise ValueError(msg)
|
||||
|
||||
# Regression for https://github.com/astral-sh/ruff/issues/8183
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x: int # Optional[int]
|
||||
@@ -0,0 +1,8 @@
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
x: int # Optional[int]
|
||||
@@ -1,7 +1,39 @@
|
||||
# Below is black stable style
|
||||
# In preview style, black always breaks the right side first
|
||||
"""
|
||||
Black's `Preview.module_docstring_newlines`
|
||||
"""
|
||||
first_stmt_after_module_level_docstring = 1
|
||||
|
||||
if True:
|
||||
|
||||
class CachedRepository:
|
||||
# Black's `Preview.dummy_implementations`
|
||||
def get_release_info(self): ...
|
||||
|
||||
|
||||
def raw_docstring():
|
||||
|
||||
r"""Black's `Preview.accept_raw_docstrings`
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def reference_docstring_newlines():
|
||||
|
||||
"""A regular docstring for comparison
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RemoveNewlineBeforeClassDocstring:
|
||||
|
||||
"""Black's `Preview.no_blank_line_before_class_docstring`"""
|
||||
|
||||
|
||||
def f():
|
||||
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
] = cccccccc.ccccccccccccc.cccccccc
|
||||
@@ -1,6 +1,21 @@
|
||||
# Regression test: Don't forget the parentheses in the value when breaking
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: int = a + 1 * a
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
)
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
)= Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
|
||||
JSONSerializable: TypeAlias = (
|
||||
"str | int | float | bool | None | list | tuple | JSONMapping"
|
||||
)
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = {1, 2, 3, 4}
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = aaaaaaaaaaaaaaaa
|
||||
|
||||
# Regression test: Don't forget the parentheses in the annotation when breaking
|
||||
class DefaultRunner:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# comment
|
||||
|
||||
class Test(
|
||||
Aaaaaaaaaaaaaaaaa,
|
||||
Bbbbbbbbbbbbbbbb,
|
||||
|
||||
@@ -6,12 +6,13 @@ use anyhow::{format_err, Context, Result};
|
||||
use clap::{command, Parser, ValueEnum};
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
use ruff_python_parser::{parse_ok_tokens, AsMode};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::comments::collect_comments;
|
||||
use crate::{format_module_ast, PyFormatOptions};
|
||||
use crate::{format_module_ast, PreviewMode, PyFormatOptions};
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
pub enum Emit {
|
||||
@@ -23,6 +24,7 @@ pub enum Emit {
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[allow(clippy::struct_excessive_bools)] // It's only the dev cli anyways
|
||||
pub struct Cli {
|
||||
/// Python files to format. If there are none, stdin will be used. `-` as stdin is not supported
|
||||
pub files: Vec<PathBuf>,
|
||||
@@ -33,20 +35,27 @@ pub struct Cli {
|
||||
#[clap(long)]
|
||||
pub check: bool,
|
||||
#[clap(long)]
|
||||
pub preview: bool,
|
||||
#[clap(long)]
|
||||
pub print_ir: bool,
|
||||
#[clap(long)]
|
||||
pub print_comments: bool,
|
||||
}
|
||||
|
||||
pub fn format_and_debug_print(source: &str, cli: &Cli, source_type: &Path) -> Result<String> {
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source)
|
||||
pub fn format_and_debug_print(source: &str, cli: &Cli, source_path: &Path) -> Result<String> {
|
||||
let source_type = PySourceType::from(source_path);
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source, source_type)
|
||||
.map_err(|err| format_err!("Source contains syntax errors {err:?}"))?;
|
||||
|
||||
// Parse the AST.
|
||||
let module = parse_ok_tokens(tokens, source, Mode::Module, "<filename>")
|
||||
let module = parse_ok_tokens(tokens, source, source_type.as_mode(), "<filename>")
|
||||
.context("Syntax error in input")?;
|
||||
|
||||
let options = PyFormatOptions::from_extension(source_type);
|
||||
let options = PyFormatOptions::from_extension(source_path).with_preview(if cli.preview {
|
||||
PreviewMode::Enabled
|
||||
} else {
|
||||
PreviewMode::Disabled
|
||||
});
|
||||
|
||||
let source_code = SourceCode::new(source);
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options)
|
||||
|
||||
@@ -507,13 +507,12 @@ fn strip_comment_prefix(comment_text: &str) -> FormatResult<&str> {
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// def func():
|
||||
/// class Class:
|
||||
/// ...
|
||||
/// # comment
|
||||
/// ```
|
||||
///
|
||||
/// This builder will insert two empty lines before the comment.
|
||||
/// ```
|
||||
pub(crate) fn empty_lines_before_trailing_comments<'a>(
|
||||
f: &PyFormatter,
|
||||
comments: &'a [SourceComment],
|
||||
@@ -555,3 +554,69 @@ impl Format<PyFormatContext<'_>> for FormatEmptyLinesBeforeTrailingComments<'_>
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the empty lines between a node and its leading comments.
|
||||
///
|
||||
/// For example, given:
|
||||
/// ```python
|
||||
/// # comment
|
||||
///
|
||||
/// class Class:
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// While `leading_comments` will preserve the existing empty line, this builder will insert an
|
||||
/// additional empty line before the comment.
|
||||
pub(crate) fn empty_lines_after_leading_comments<'a>(
|
||||
f: &PyFormatter,
|
||||
comments: &'a [SourceComment],
|
||||
) -> FormatEmptyLinesAfterLeadingComments<'a> {
|
||||
// Black has different rules for stub vs. non-stub and top level vs. indented
|
||||
let empty_lines = match (f.options().source_type(), f.context().node_level()) {
|
||||
(PySourceType::Stub, NodeLevel::TopLevel) => 1,
|
||||
(PySourceType::Stub, _) => 0,
|
||||
(_, NodeLevel::TopLevel) => 2,
|
||||
(_, _) => 1,
|
||||
};
|
||||
|
||||
FormatEmptyLinesAfterLeadingComments {
|
||||
comments,
|
||||
empty_lines,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct FormatEmptyLinesAfterLeadingComments<'a> {
|
||||
/// The leading comments of the node.
|
||||
comments: &'a [SourceComment],
|
||||
/// The expected number of empty lines after the leading comments.
|
||||
empty_lines: u32,
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatEmptyLinesAfterLeadingComments<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||
if let Some(comment) = self
|
||||
.comments
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|comment| comment.line_position().is_own_line())
|
||||
{
|
||||
let actual = lines_after(comment.end(), f.context().source()).saturating_sub(1);
|
||||
// If there are no empty lines, keep the comment tight to the node.
|
||||
if actual == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// If there are more than enough empty lines already, `leading_comments` will
|
||||
// trim them as necessary.
|
||||
if actual >= self.empty_lines {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for _ in actual..self.empty_lines {
|
||||
write!(f, [empty_line()])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,10 +548,10 @@ mod tests {
|
||||
use insta::assert_debug_snapshot;
|
||||
|
||||
use ruff_formatter::SourceCode;
|
||||
use ruff_python_ast::Mod;
|
||||
use ruff_python_ast::{Mod, PySourceType};
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
use ruff_python_parser::{parse_ok_tokens, AsMode};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
|
||||
use crate::comments::Comments;
|
||||
@@ -565,9 +565,10 @@ mod tests {
|
||||
impl<'a> CommentsTestCase<'a> {
|
||||
fn from_code(source: &'a str) -> Self {
|
||||
let source_code = SourceCode::new(source);
|
||||
let source_type = PySourceType::Python;
|
||||
let (tokens, comment_ranges) =
|
||||
tokens_and_ranges(source).expect("Expect source to be valid Python");
|
||||
let parsed = parse_ok_tokens(tokens, source, Mode::Module, "test.py")
|
||||
tokens_and_ranges(source, source_type).expect("Expect source to be valid Python");
|
||||
let parsed = parse_ok_tokens(tokens, source, source_type.as_mode(), "test.py")
|
||||
.expect("Expect source to be valid Python");
|
||||
|
||||
CommentsTestCase {
|
||||
|
||||
@@ -506,7 +506,12 @@ const fn is_simple_power_operand(expr: &Expr) -> bool {
|
||||
op: UnaryOp::Not, ..
|
||||
}) => false,
|
||||
Expr::Constant(ExprConstant {
|
||||
value: Constant::Complex { .. } | Constant::Float(_) | Constant::Int(_),
|
||||
value:
|
||||
Constant::Complex { .. }
|
||||
| Constant::Float(_)
|
||||
| Constant::Int(_)
|
||||
| Constant::None
|
||||
| Constant::Bool(_),
|
||||
..
|
||||
}) => true,
|
||||
Expr::Name(_) => true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use ruff_python_ast::ExprIpyEscapeCommand;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::expression::parentheses::{NeedsParentheses, OptionalParentheses};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -11,3 +12,13 @@ impl FormatNodeRule<ExprIpyEscapeCommand> for FormatExprIpyEscapeCommand {
|
||||
source_text_slice(item.range()).fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl NeedsParentheses for ExprIpyEscapeCommand {
|
||||
fn needs_parentheses(
|
||||
&self,
|
||||
_parent: ruff_python_ast::AnyNodeRef,
|
||||
_context: &PyFormatContext,
|
||||
) -> OptionalParentheses {
|
||||
OptionalParentheses::Never
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ impl NeedsParentheses for Expr {
|
||||
Expr::List(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::Tuple(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::Slice(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
Expr::IpyEscapeCommand(expr) => expr.needs_parentheses(parent, context),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -526,16 +526,20 @@ fn can_omit_optional_parentheses(expr: &Expr, context: &PyFormatContext) -> bool
|
||||
&& has_parentheses(expr, context).is_some_and(OwnParentheses::is_non_empty)
|
||||
}
|
||||
|
||||
// Only use the layout if the first or last expression has parentheses of some sort, and
|
||||
// Only use the layout if the first expression starts with parentheses
|
||||
// or the last expression ends with parentheses of some sort, and
|
||||
// those parentheses are non-empty.
|
||||
let first_parenthesized = visitor
|
||||
.first
|
||||
.is_some_and(|first| is_parenthesized(first, context));
|
||||
let last_parenthesized = visitor
|
||||
if visitor
|
||||
.last
|
||||
.is_some_and(|last| is_parenthesized(last, context));
|
||||
|
||||
first_parenthesized || last_parenthesized
|
||||
.is_some_and(|last| is_parenthesized(last, context))
|
||||
{
|
||||
true
|
||||
} else {
|
||||
visitor
|
||||
.first
|
||||
.expression()
|
||||
.is_some_and(|first| is_parenthesized(first, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -545,7 +549,7 @@ struct CanOmitOptionalParenthesesVisitor<'input> {
|
||||
max_precedence_count: u32,
|
||||
any_parenthesized_expressions: bool,
|
||||
last: Option<&'input Expr>,
|
||||
first: Option<&'input Expr>,
|
||||
first: First<'input>,
|
||||
context: &'input PyFormatContext<'input>,
|
||||
}
|
||||
|
||||
@@ -557,7 +561,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
max_precedence_count: 0,
|
||||
any_parenthesized_expressions: false,
|
||||
last: None,
|
||||
first: None,
|
||||
first: First::None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,6 +674,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
if op.is_invert() {
|
||||
self.update_max_precedence(OperatorPrecedence::BitwiseInversion);
|
||||
}
|
||||
self.first.set_if_none(First::Token);
|
||||
}
|
||||
|
||||
// `[a, b].test.test[300].dot`
|
||||
@@ -706,20 +711,25 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
self.update_max_precedence(OperatorPrecedence::String);
|
||||
}
|
||||
|
||||
Expr::Tuple(_)
|
||||
| Expr::NamedExpr(_)
|
||||
| Expr::GeneratorExp(_)
|
||||
| Expr::Lambda(_)
|
||||
// Expressions with sub expressions but a preceding token
|
||||
// Mark this expression as first expression and not the sub expression.
|
||||
Expr::Lambda(_)
|
||||
| Expr::Await(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::YieldFrom(_)
|
||||
| Expr::Starred(_) => {
|
||||
self.first.set_if_none(First::Token);
|
||||
}
|
||||
|
||||
Expr::Tuple(_)
|
||||
| Expr::NamedExpr(_)
|
||||
| Expr::GeneratorExp(_)
|
||||
| Expr::FormattedValue(_)
|
||||
| Expr::FString(_)
|
||||
| Expr::Constant(_)
|
||||
| Expr::Starred(_)
|
||||
| Expr::Name(_)
|
||||
| Expr::Slice(_) => {}
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
| Expr::Slice(_)
|
||||
| Expr::IpyEscapeCommand(_) => {}
|
||||
};
|
||||
|
||||
walk_expr(self, expr);
|
||||
@@ -741,8 +751,32 @@ impl<'input> PreorderVisitor<'input> for CanOmitOptionalParenthesesVisitor<'inpu
|
||||
self.visit_subexpression(expr);
|
||||
}
|
||||
|
||||
if self.first.is_none() {
|
||||
self.first = Some(expr);
|
||||
self.first.set_if_none(First::Expression(expr));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum First<'a> {
|
||||
None,
|
||||
|
||||
/// Expression starts with a non-parentheses token. E.g. `not a`
|
||||
Token,
|
||||
|
||||
Expression(&'a Expr),
|
||||
}
|
||||
|
||||
impl<'a> First<'a> {
|
||||
#[inline]
|
||||
fn set_if_none(&mut self, first: First<'a>) {
|
||||
if matches!(self, First::None) {
|
||||
*self = first;
|
||||
}
|
||||
}
|
||||
|
||||
fn expression(self) -> Option<&'a Expr> {
|
||||
match self {
|
||||
First::None | First::Token => None,
|
||||
First::Expression(expr) => Some(expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use ruff_python_ast::AstNode;
|
||||
use ruff_python_ast::Mod;
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
use ruff_python_parser::lexer::LexicalError;
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode, ParseError};
|
||||
use ruff_python_parser::{parse_ok_tokens, AsMode, ParseError};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_source_file::Locator;
|
||||
|
||||
@@ -130,8 +130,9 @@ pub fn format_module_source(
|
||||
source: &str,
|
||||
options: PyFormatOptions,
|
||||
) -> Result<Printed, FormatModuleError> {
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source)?;
|
||||
let module = parse_ok_tokens(tokens, source, Mode::Module, "<filename>")?;
|
||||
let source_type = options.source_type();
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source, source_type)?;
|
||||
let module = parse_ok_tokens(tokens, source, source_type.as_mode(), "<filename>")?;
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options)?;
|
||||
Ok(formatted.print()?)
|
||||
}
|
||||
@@ -172,9 +173,10 @@ mod tests {
|
||||
use anyhow::Result;
|
||||
use insta::assert_snapshot;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_index::tokens_and_ranges;
|
||||
|
||||
use ruff_python_parser::{parse_ok_tokens, Mode};
|
||||
use ruff_python_parser::{parse_ok_tokens, AsMode};
|
||||
|
||||
use crate::{format_module_ast, format_module_source, PyFormatOptions};
|
||||
|
||||
@@ -213,11 +215,12 @@ def main() -> None:
|
||||
]
|
||||
|
||||
"#;
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source).unwrap();
|
||||
let source_type = PySourceType::Python;
|
||||
let (tokens, comment_ranges) = tokens_and_ranges(source, source_type).unwrap();
|
||||
|
||||
// Parse the AST.
|
||||
let source_path = "code_inline.py";
|
||||
let module = parse_ok_tokens(tokens, source, Mode::Module, source_path).unwrap();
|
||||
let module = parse_ok_tokens(tokens, source, source_type.as_mode(), source_path).unwrap();
|
||||
let options = PyFormatOptions::from_extension(Path::new(source_path));
|
||||
let formatted = format_module_ast(&module, &comment_ranges, source, options).unwrap();
|
||||
|
||||
|
||||
@@ -250,6 +250,10 @@ impl MagicTrailingComma {
|
||||
pub const fn is_respect(self) -> bool {
|
||||
matches!(self, Self::Respect)
|
||||
}
|
||||
|
||||
pub const fn is_ignore(self) -> bool {
|
||||
matches!(self, Self::Ignore)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MagicTrailingComma {
|
||||
|
||||
@@ -391,7 +391,9 @@ pub(crate) fn clause_body<'a>(
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatClauseBody<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
|
||||
if f.options().source_type().is_stub()
|
||||
// In stable, stubs are only collapsed in stub files, in preview this is consistently
|
||||
// applied everywhere
|
||||
if (f.options().source_type().is_stub() || f.options().preview().is_enabled())
|
||||
&& contains_only_an_ellipsis(self.body, f.context().comments())
|
||||
&& self.trailing_comments.is_empty()
|
||||
{
|
||||
|
||||
@@ -21,12 +21,7 @@ impl FormatNodeRule<StmtAnnAssign> for FormatStmtAnnAssign {
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
target.format(),
|
||||
token(":"),
|
||||
space(),
|
||||
maybe_parenthesize_expression(annotation, item, Parenthesize::IfBreaks)
|
||||
]
|
||||
[target.format(), token(":"), space(), annotation.format(),]
|
||||
)?;
|
||||
|
||||
if let Some(value) = value {
|
||||
|
||||
@@ -3,7 +3,9 @@ use ruff_python_ast::{Decorator, StmtClassDef};
|
||||
use ruff_python_trivia::lines_after_ignoring_end_of_line_trivia;
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::comments::format::empty_lines_before_trailing_comments;
|
||||
use crate::comments::format::{
|
||||
empty_lines_after_leading_comments, empty_lines_before_trailing_comments,
|
||||
};
|
||||
use crate::comments::{leading_comments, trailing_comments, SourceComment};
|
||||
use crate::prelude::*;
|
||||
use crate::statement::clause::{clause_body, clause_header, ClauseHeader};
|
||||
@@ -32,6 +34,29 @@ impl FormatNodeRule<StmtClassDef> for FormatStmtClassDef {
|
||||
let (leading_definition_comments, trailing_definition_comments) =
|
||||
dangling_comments.split_at(trailing_definition_comments_start);
|
||||
|
||||
// If the class contains leading comments, insert newlines before them.
|
||||
// For example, given:
|
||||
// ```python
|
||||
// # comment
|
||||
//
|
||||
// class Class:
|
||||
// ...
|
||||
// ```
|
||||
//
|
||||
// At the top-level in a non-stub file, reformat as:
|
||||
// ```python
|
||||
// # comment
|
||||
//
|
||||
//
|
||||
// class Class:
|
||||
// ...
|
||||
// ```
|
||||
// Note that this is only really relevant for the specific case in which there's a single
|
||||
// newline between the comment and the node, but we _require_ two newlines. If there are
|
||||
// _no_ newlines between the comment and the node, we don't insert _any_ newlines; if there
|
||||
// are more than two, then `leading_comments` will preserve the correct number of newlines.
|
||||
empty_lines_after_leading_comments(f, comments.leading(item)).fmt(f)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use ruff_formatter::write;
|
||||
use ruff_python_ast::StmtFunctionDef;
|
||||
|
||||
use crate::comments::format::empty_lines_before_trailing_comments;
|
||||
use crate::comments::format::{
|
||||
empty_lines_after_leading_comments, empty_lines_before_trailing_comments,
|
||||
};
|
||||
use crate::comments::SourceComment;
|
||||
use crate::expression::maybe_parenthesize_expression;
|
||||
use crate::expression::parentheses::{Parentheses, Parenthesize};
|
||||
@@ -30,6 +32,29 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||
let (leading_definition_comments, trailing_definition_comments) =
|
||||
dangling_comments.split_at(trailing_definition_comments_start);
|
||||
|
||||
// If the class contains leading comments, insert newlines before them.
|
||||
// For example, given:
|
||||
// ```python
|
||||
// # comment
|
||||
//
|
||||
// def func():
|
||||
// ...
|
||||
// ```
|
||||
//
|
||||
// At the top-level in a non-stub file, reformat as:
|
||||
// ```python
|
||||
// # comment
|
||||
//
|
||||
//
|
||||
// def func():
|
||||
// ...
|
||||
// ```
|
||||
// Note that this is only really relevant for the specific case in which there's a single
|
||||
// newline between the comment and the node, but we _require_ two newlines. If there are
|
||||
// _no_ newlines between the comment and the node, we don't insert _any_ newlines; if there
|
||||
// are more than two, then `leading_comments` will preserve the correct number of newlines.
|
||||
empty_lines_after_leading_comments(f, comments.leading(item)).fmt(f)?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
|
||||
@@ -536,7 +536,7 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Suite {
|
||||
}
|
||||
|
||||
/// A statement representing a docstring.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) struct DocstringStmt<'a>(&'a Stmt);
|
||||
|
||||
impl<'a> DocstringStmt<'a> {
|
||||
@@ -589,7 +589,7 @@ impl Format<PyFormatContext<'_>> for DocstringStmt<'_> {
|
||||
}
|
||||
|
||||
/// A Child of a suite.
|
||||
#[derive(Copy, Clone)]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub(crate) enum SuiteChildStatement<'a> {
|
||||
/// A docstring documenting a class or function definition.
|
||||
Docstring(DocstringStmt<'a>),
|
||||
|
||||
@@ -327,7 +327,7 @@ fn write_suppressed_statements<'a>(
|
||||
|
||||
for range in CommentRangeIter::in_suppression(comments.trailing(statement), source) {
|
||||
match range {
|
||||
// All leading comments are suppressed
|
||||
// All trailing comments are suppressed
|
||||
// ```python
|
||||
// statement
|
||||
// # suppressed
|
||||
@@ -394,10 +394,14 @@ fn write_suppressed_statements<'a>(
|
||||
statement = SuiteChildStatement::Other(next_statement);
|
||||
leading_node_comments = comments.leading(next_statement);
|
||||
} else {
|
||||
let end = comments
|
||||
.trailing(statement)
|
||||
.last()
|
||||
.map_or(statement.end(), Ranged::end);
|
||||
let mut nodes =
|
||||
std::iter::successors(Some(AnyNodeRef::from(statement.statement())), |statement| {
|
||||
statement.last_child_in_body()
|
||||
});
|
||||
|
||||
let end = nodes
|
||||
.find_map(|statement| comments.trailing(statement).last().map(Ranged::end))
|
||||
.unwrap_or(statement.end());
|
||||
|
||||
FormatVerbatimStatementRange {
|
||||
verbatim_range: TextRange::new(format_off_comment.end(), end),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use ruff_formatter::FormatOptions;
|
||||
use ruff_python_formatter::{format_module_source, PyFormatOptions};
|
||||
use ruff_python_formatter::{format_module_source, PreviewMode, PyFormatOptions};
|
||||
use similar::TextDiff;
|
||||
use std::fmt::{Formatter, Write};
|
||||
use std::io::BufReader;
|
||||
@@ -142,16 +142,40 @@ fn format() {
|
||||
} else {
|
||||
let printed =
|
||||
format_module_source(&content, options.clone()).expect("Formatting to succeed");
|
||||
let formatted_code = printed.as_code();
|
||||
let formatted = printed.as_code();
|
||||
|
||||
ensure_stability_when_formatting_twice(formatted_code, options, input_path);
|
||||
ensure_stability_when_formatting_twice(formatted, options.clone(), input_path);
|
||||
|
||||
writeln!(
|
||||
snapshot,
|
||||
"## Output\n{}",
|
||||
CodeFrame::new("py", &formatted_code)
|
||||
)
|
||||
.unwrap();
|
||||
// We want to capture the differences in the preview style in our fixtures
|
||||
let options_preview = options.with_preview(PreviewMode::Enabled);
|
||||
let printed_preview = format_module_source(&content, options_preview.clone())
|
||||
.expect("Formatting to succeed");
|
||||
let formatted_preview = printed_preview.as_code();
|
||||
|
||||
ensure_stability_when_formatting_twice(
|
||||
formatted_preview,
|
||||
options_preview.clone(),
|
||||
input_path,
|
||||
);
|
||||
|
||||
if formatted == formatted_preview {
|
||||
writeln!(snapshot, "## Output\n{}", CodeFrame::new("py", &formatted)).unwrap();
|
||||
} else {
|
||||
// Having both snapshots makes it hard to see the difference, so we're keeping only
|
||||
// diff.
|
||||
writeln!(
|
||||
snapshot,
|
||||
"## Output\n{}\n## Preview changes\n{}",
|
||||
CodeFrame::new("py", &formatted),
|
||||
CodeFrame::new(
|
||||
"diff",
|
||||
TextDiff::from_lines(formatted, formatted_preview)
|
||||
.unified_diff()
|
||||
.header("Stable", "Preview")
|
||||
)
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
insta::with_settings!({
|
||||
|
||||
@@ -162,7 +162,7 @@ def f():
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,29 +1,205 @@
|
||||
@@ -1,29 +1,206 @@
|
||||
+# This file doesn't use the standard decomposition.
|
||||
+# Decorator syntax test cases are separated by double # comments.
|
||||
+# Those before the 'output' comment are valid under the old syntax.
|
||||
@@ -172,6 +172,7 @@ def f():
|
||||
+
|
||||
+##
|
||||
+
|
||||
+
|
||||
+@decorator
|
||||
+def f():
|
||||
+ ...
|
||||
@@ -209,43 +210,48 @@ def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
+
|
||||
##
|
||||
|
||||
-@decorator()()
|
||||
+
|
||||
+@decorator(**kwargs)
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
def f():
|
||||
...
|
||||
|
||||
+
|
||||
##
|
||||
|
||||
-@(decorator)
|
||||
+
|
||||
+@decorator(*args, **kwargs)
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
def f():
|
||||
...
|
||||
|
||||
+
|
||||
##
|
||||
|
||||
-@sequence["decorator"]
|
||||
+
|
||||
+@decorator(
|
||||
+ *args,
|
||||
+ **kwargs,
|
||||
+)
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
def f():
|
||||
...
|
||||
|
||||
+
|
||||
##
|
||||
|
||||
-@decorator[List[str]]
|
||||
+
|
||||
+@dotted.decorator
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
def f():
|
||||
...
|
||||
|
||||
+
|
||||
##
|
||||
|
||||
-@var := decorator
|
||||
+
|
||||
+@dotted.decorator(arg)
|
||||
+def f():
|
||||
@@ -260,48 +266,43 @@ def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
##
|
||||
|
||||
-@decorator()()
|
||||
+##
|
||||
+
|
||||
+
|
||||
+@dotted.decorator(*args)
|
||||
def f():
|
||||
...
|
||||
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
+
|
||||
##
|
||||
|
||||
-@(decorator)
|
||||
+
|
||||
+@dotted.decorator(**kwargs)
|
||||
def f():
|
||||
...
|
||||
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
+
|
||||
##
|
||||
|
||||
-@sequence["decorator"]
|
||||
+
|
||||
+@dotted.decorator(*args, **kwargs)
|
||||
def f():
|
||||
...
|
||||
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
+
|
||||
##
|
||||
|
||||
-@decorator[List[str]]
|
||||
+
|
||||
+@dotted.decorator(
|
||||
+ *args,
|
||||
+ **kwargs,
|
||||
+)
|
||||
def f():
|
||||
...
|
||||
|
||||
+def f():
|
||||
+ ...
|
||||
+
|
||||
+
|
||||
+##
|
||||
+
|
||||
##
|
||||
|
||||
-@var := decorator
|
||||
+
|
||||
+@double.dotted.decorator
|
||||
+def f():
|
||||
@@ -387,6 +388,7 @@ def f():
|
||||
|
||||
##
|
||||
|
||||
|
||||
@decorator
|
||||
def f():
|
||||
...
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/py_39/python39.py
|
||||
---
|
||||
## Input
|
||||
|
||||
```py
|
||||
#!/usr/bin/env python3.9
|
||||
|
||||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
|
||||
@relaxed_decorator[extremely_long_name_that_definitely_will_not_fit_on_one_line_of_standard_length]
|
||||
def f():
|
||||
...
|
||||
|
||||
@extremely_long_variable_name_that_doesnt_fit := complex.expression(with_long="arguments_value_that_wont_fit_at_the_end_of_the_line")
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
||||
## Black Differences
|
||||
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python3.9
|
||||
|
||||
-
|
||||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
||||
## Ruff Output
|
||||
|
||||
```py
|
||||
#!/usr/bin/env python3.9
|
||||
|
||||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
|
||||
|
||||
@relaxed_decorator[
|
||||
extremely_long_name_that_definitely_will_not_fit_on_one_line_of_standard_length
|
||||
]
|
||||
def f():
|
||||
...
|
||||
|
||||
|
||||
@extremely_long_variable_name_that_doesnt_fit := complex.expression(
|
||||
with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
|
||||
)
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
||||
## Black Output
|
||||
|
||||
```py
|
||||
#!/usr/bin/env python3.9
|
||||
|
||||
|
||||
@relaxed_decorator[0]
|
||||
def f():
|
||||
...
|
||||
|
||||
|
||||
@relaxed_decorator[
|
||||
extremely_long_name_that_definitely_will_not_fit_on_one_line_of_standard_length
|
||||
]
|
||||
def f():
|
||||
...
|
||||
|
||||
|
||||
@extremely_long_variable_name_that_doesnt_fit := complex.expression(
|
||||
with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
|
||||
)
|
||||
def f():
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/binary_pow_spacing.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# No spacing
|
||||
5 ** 5
|
||||
5.0 ** 5.0
|
||||
1e5 ** 2e5
|
||||
True ** True
|
||||
False ** False
|
||||
None ** None
|
||||
|
||||
# Space
|
||||
"a" ** "b"
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
# No spacing
|
||||
5**5
|
||||
5.0**5.0
|
||||
1e5**2e5
|
||||
True**True
|
||||
False**False
|
||||
None**None
|
||||
|
||||
# Space
|
||||
"a" ** "b"
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -186,6 +186,19 @@ if "root" not in (
|
||||
):
|
||||
msg = "Could not find root. Please try a different forest."
|
||||
raise ValueError(msg)
|
||||
|
||||
# Regression for https://github.com/astral-sh/ruff/issues/8183
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -292,10 +305,13 @@ if aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa & (
|
||||
):
|
||||
pass
|
||||
|
||||
if not (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) & aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
if (
|
||||
not (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
)
|
||||
& aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
@@ -383,6 +399,21 @@ if "root" not in (
|
||||
):
|
||||
msg = "Could not find root. Please try a different forest."
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
# Regression for https://github.com/astral-sh/ruff/issues/8183
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa(bbbbbbbb, ccccccc)) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def foo():
|
||||
while (
|
||||
not (aaaaaaaaaaaaaaaaaaaaa[bbbbbbbb, ccccccc]) and dddddddddd < eeeeeeeeeeeeeee
|
||||
):
|
||||
pass
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_on_off/fmt_off_unclosed_deep_nested_trailing_comment.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x: int # Optional[int]
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
if True:
|
||||
if False:
|
||||
x: int # Optional[int]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_on_off/fmt_off_unclosed_trailing_comment.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
x: int # Optional[int]
|
||||
```
|
||||
|
||||
## Output
|
||||
```py
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/8211
|
||||
|
||||
# fmt: off
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class A:
|
||||
x: int # Optional[int]
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ def test():
|
||||
|
||||
# fmt: on
|
||||
|
||||
|
||||
def test():
|
||||
pass
|
||||
```
|
||||
|
||||
@@ -93,4 +93,21 @@ def test3 ():
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -21,8 +21,7 @@
|
||||
|
||||
|
||||
# formatted
|
||||
-def test2():
|
||||
- ...
|
||||
+def test2(): ...
|
||||
|
||||
|
||||
a = 10
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -549,4 +549,27 @@ if True:
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -245,13 +245,11 @@
|
||||
class Path:
|
||||
if sys.version_info >= (3, 11):
|
||||
|
||||
- def joinpath(self):
|
||||
- ...
|
||||
+ def joinpath(self): ...
|
||||
|
||||
# The .open method comes from pathlib.pyi and should be kept in sync.
|
||||
@overload
|
||||
- def open(self):
|
||||
- ...
|
||||
+ def open(self): ...
|
||||
|
||||
|
||||
def fakehttp():
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,45 @@
|
||||
---
|
||||
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/assign_breaking.py
|
||||
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/preview.py
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# Below is black stable style
|
||||
# In preview style, black always breaks the right side first
|
||||
"""
|
||||
Black's `Preview.module_docstring_newlines`
|
||||
"""
|
||||
first_stmt_after_module_level_docstring = 1
|
||||
|
||||
if True:
|
||||
|
||||
class CachedRepository:
|
||||
# Black's `Preview.dummy_implementations`
|
||||
def get_release_info(self): ...
|
||||
|
||||
|
||||
def raw_docstring():
|
||||
|
||||
r"""Black's `Preview.accept_raw_docstrings`
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def reference_docstring_newlines():
|
||||
|
||||
"""A regular docstring for comparison
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RemoveNewlineBeforeClassDocstring:
|
||||
|
||||
"""Black's `Preview.no_blank_line_before_class_docstring`"""
|
||||
|
||||
|
||||
def f():
|
||||
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
] = cccccccc.ccccccccccccc.cccccccc
|
||||
@@ -52,10 +84,41 @@ preview = Disabled
|
||||
```
|
||||
|
||||
```py
|
||||
# Below is black stable style
|
||||
# In preview style, black always breaks the right side first
|
||||
"""
|
||||
Black's `Preview.module_docstring_newlines`
|
||||
"""
|
||||
first_stmt_after_module_level_docstring = 1
|
||||
|
||||
if True:
|
||||
|
||||
class CachedRepository:
|
||||
# Black's `Preview.dummy_implementations`
|
||||
def get_release_info(self):
|
||||
...
|
||||
|
||||
|
||||
def raw_docstring():
|
||||
r"""Black's `Preview.accept_raw_docstrings`
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def reference_docstring_newlines():
|
||||
"""A regular docstring for comparison
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RemoveNewlineBeforeClassDocstring:
|
||||
|
||||
"""Black's `Preview.no_blank_line_before_class_docstring`"""
|
||||
|
||||
|
||||
def f():
|
||||
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
] = cccccccc.ccccccccccccc.cccccccc
|
||||
@@ -100,10 +163,40 @@ preview = Enabled
|
||||
```
|
||||
|
||||
```py
|
||||
# Below is black stable style
|
||||
# In preview style, black always breaks the right side first
|
||||
"""
|
||||
Black's `Preview.module_docstring_newlines`
|
||||
"""
|
||||
first_stmt_after_module_level_docstring = 1
|
||||
|
||||
if True:
|
||||
|
||||
class CachedRepository:
|
||||
# Black's `Preview.dummy_implementations`
|
||||
def get_release_info(self): ...
|
||||
|
||||
|
||||
def raw_docstring():
|
||||
r"""Black's `Preview.accept_raw_docstrings`
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def reference_docstring_newlines():
|
||||
"""A regular docstring for comparison
|
||||
a
|
||||
b
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class RemoveNewlineBeforeClassDocstring:
|
||||
|
||||
"""Black's `Preview.no_blank_line_before_class_docstring`"""
|
||||
|
||||
|
||||
def f():
|
||||
"""Black's `Preview.prefer_splitting_right_hand_side_of_assignments`"""
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
] = cccccccc.ccccccccccccc.cccccccc
|
||||
@@ -7,6 +7,21 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/
|
||||
# Regression test: Don't forget the parentheses in the value when breaking
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: int = a + 1 * a
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
)
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
)= Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
|
||||
JSONSerializable: TypeAlias = (
|
||||
"str | int | float | bool | None | list | tuple | JSONMapping"
|
||||
)
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = {1, 2, 3, 4}
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = aaaaaaaaaaaaaaaa
|
||||
|
||||
# Regression test: Don't forget the parentheses in the annotation when breaking
|
||||
class DefaultRunner:
|
||||
@@ -20,12 +35,35 @@ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: int =
|
||||
a + 1 * a
|
||||
)
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb = (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
)
|
||||
|
||||
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: (
|
||||
Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
|
||||
) = Bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb()
|
||||
|
||||
JSONSerializable: TypeAlias = (
|
||||
"str | int | float | bool | None | list | tuple | JSONMapping"
|
||||
)
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = {
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
}
|
||||
|
||||
JSONSerializable: str | int | float | bool | None | list | tuple | JSONMapping = (
|
||||
aaaaaaaaaaaaaaaa
|
||||
)
|
||||
|
||||
|
||||
# Regression test: Don't forget the parentheses in the annotation when breaking
|
||||
class DefaultRunner:
|
||||
task_runner_cls: (
|
||||
TaskRunnerProtocol | typing.Callable[[], typing.Any]
|
||||
) = DefaultTaskRunner
|
||||
task_runner_cls: TaskRunnerProtocol | typing.Callable[
|
||||
[], typing.Any
|
||||
] = DefaultTaskRunner
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/
|
||||
---
|
||||
## Input
|
||||
```py
|
||||
# comment
|
||||
|
||||
class Test(
|
||||
Aaaaaaaaaaaaaaaaa,
|
||||
Bbbbbbbbbbbbbbbb,
|
||||
@@ -232,6 +234,9 @@ class QuerySet(AltersData):
|
||||
|
||||
## Output
|
||||
```py
|
||||
# comment
|
||||
|
||||
|
||||
class Test(
|
||||
Aaaaaaaaaaaaaaaaa,
|
||||
Bbbbbbbbbbbbbbbb,
|
||||
@@ -494,4 +499,45 @@ class QuerySet(AltersData):
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -28,8 +28,7 @@
|
||||
pass
|
||||
|
||||
|
||||
-class Test((Aaaa)):
|
||||
- ...
|
||||
+class Test((Aaaa)): ...
|
||||
|
||||
|
||||
class Test(
|
||||
@@ -159,20 +158,17 @@
|
||||
|
||||
@dataclass
|
||||
# Copied from transformers.models.clip.modeling_clip.CLIPOutput with CLIP->AltCLIP
|
||||
-class AltCLIPOutput(ModelOutput):
|
||||
- ...
|
||||
+class AltCLIPOutput(ModelOutput): ...
|
||||
|
||||
|
||||
@dataclass
|
||||
-class AltCLIPOutput: # Copied from transformers.models.clip.modeling_clip.CLIPOutput with CLIP->AltCLIP
|
||||
- ...
|
||||
+class AltCLIPOutput: ... # Copied from transformers.models.clip.modeling_clip.CLIPOutput with CLIP->AltCLIP
|
||||
|
||||
|
||||
@dataclass
|
||||
class AltCLIPOutput(
|
||||
# Copied from transformers.models.clip.modeling_clip.CLIPOutput with CLIP->AltCLIP
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
class TestTypeParams[
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -996,4 +996,167 @@ def default_arg_comments2( #
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -2,8 +2,7 @@
|
||||
def test(
|
||||
# comment
|
||||
# another
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
# Argument empty line spacing
|
||||
@@ -12,8 +11,7 @@
|
||||
a,
|
||||
# another
|
||||
b,
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
### Different function argument wrappings
|
||||
@@ -57,8 +55,7 @@
|
||||
b,
|
||||
# comment
|
||||
*args,
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def kwarg_with_leading_comments(
|
||||
@@ -66,8 +63,7 @@
|
||||
b,
|
||||
# comment
|
||||
**kwargs,
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def argument_with_long_default(
|
||||
@@ -75,8 +71,7 @@
|
||||
b=ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
|
||||
+ [dddddddddddddddddddd, eeeeeeeeeeeeeeeeeeee, ffffffffffffffffffffffff],
|
||||
h=[],
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def argument_with_long_type_annotation(
|
||||
@@ -85,12 +80,10 @@
|
||||
| yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
| zzzzzzzzzzzzzzzzzzz = [0, 1, 2, 3],
|
||||
h=[],
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
-def test():
|
||||
- ...
|
||||
+def test(): ...
|
||||
|
||||
|
||||
# Type parameter empty line spacing
|
||||
@@ -99,8 +92,7 @@
|
||||
A,
|
||||
# another
|
||||
B,
|
||||
-]():
|
||||
- ...
|
||||
+](): ...
|
||||
|
||||
|
||||
# Type parameter comments
|
||||
@@ -159,8 +151,7 @@
|
||||
|
||||
|
||||
# Comment
|
||||
-def with_leading_comment():
|
||||
- ...
|
||||
+def with_leading_comment(): ...
|
||||
|
||||
|
||||
# Comment that could be mistaken for a trailing comment of the function declaration when
|
||||
@@ -192,8 +183,7 @@
|
||||
# Regression test for https://github.com/astral-sh/ruff/issues/5176#issuecomment-1598171989
|
||||
def foo(
|
||||
b=3 + 2, # comment
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
# Comments on the slash or the star, both of which don't have a node
|
||||
@@ -454,8 +444,7 @@
|
||||
def f(
|
||||
# first
|
||||
# second
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def f( # first
|
||||
@@ -475,8 +464,7 @@
|
||||
# first
|
||||
b,
|
||||
# second
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def f( # first
|
||||
@@ -484,8 +472,7 @@
|
||||
# second
|
||||
b,
|
||||
# third
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def f( # first
|
||||
@@ -494,8 +481,7 @@
|
||||
# third
|
||||
b,
|
||||
# fourth
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def f( # first
|
||||
@@ -522,17 +508,14 @@
|
||||
a,
|
||||
# third
|
||||
/, # second
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
# Walrus operator in return type.
|
||||
-def this_is_unusual() -> (please := no):
|
||||
- ...
|
||||
+def this_is_unusual() -> (please := no): ...
|
||||
|
||||
|
||||
-def this_is_unusual(x) -> (please := no):
|
||||
- ...
|
||||
+def this_is_unusual(x) -> (please := no): ...
|
||||
|
||||
|
||||
# Regression test for: https://github.com/astral-sh/ruff/issues/7465
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -544,4 +544,298 @@ def process_board_action(
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -7,8 +7,7 @@
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
@@ -20,8 +19,7 @@
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
# comment
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
@@ -33,8 +31,7 @@
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
1
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
@@ -47,8 +44,7 @@
|
||||
) -> ( # type: ignore[override]
|
||||
1,
|
||||
2,
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
@@ -60,14 +56,12 @@
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
(1, 2)
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197
|
||||
self, m: Match[str], data: str
|
||||
-) -> Union[Tuple[None, None, None], Tuple[Element, int, int]]:
|
||||
- ...
|
||||
+) -> Union[Tuple[None, None, None], Tuple[Element, int, int]]: ...
|
||||
|
||||
|
||||
def double(
|
||||
@@ -95,50 +89,44 @@
|
||||
# function arguments break here with a single argument; we do not.)
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
- ...
|
||||
+) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ...
|
||||
|
||||
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, a
|
||||
-) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
- ...
|
||||
+) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ...
|
||||
|
||||
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-) -> a:
|
||||
- ...
|
||||
+) -> a: ...
|
||||
|
||||
|
||||
def f(
|
||||
a
|
||||
-) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
- ...
|
||||
+) -> (
|
||||
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
+): ...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def f[
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-]() -> a:
|
||||
- ...
|
||||
+]() -> a: ...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
- ...
|
||||
+) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: ...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
-) -> a:
|
||||
- ...
|
||||
+) -> a: ...
|
||||
|
||||
|
||||
# Breaking return type annotations. Black adds parentheses if the parameters are
|
||||
@@ -147,137 +135,126 @@
|
||||
Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
]
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
) -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
-]:
|
||||
- ...
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
) -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
-]:
|
||||
- ...
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
*args
|
||||
) -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
-]:
|
||||
- ...
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx( # foo
|
||||
) -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
-]:
|
||||
- ...
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
# bar
|
||||
) -> Set[
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
-]:
|
||||
- ...
|
||||
+]: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
-) -> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:
|
||||
- ...
|
||||
+) -> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
-) -> xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:
|
||||
- ...
|
||||
+) -> (
|
||||
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
+): ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> X + Y + foooooooooooooooooooooooooooooooooooo():
|
||||
- ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
+ X + Y + foooooooooooooooooooooooooooooooooooo()
|
||||
+): ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(x) -> X + Y + foooooooooooooooooooooooooooooooooooo():
|
||||
- ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
+ x
|
||||
+) -> X + Y + foooooooooooooooooooooooooooooooooooo(): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
X and Y and foooooooooooooooooooooooooooooooooooo()
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
-) -> X and Y and foooooooooooooooooooooooooooooooooooo():
|
||||
- ...
|
||||
+) -> X and Y and foooooooooooooooooooooooooooooooooooo(): ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> X | Y | foooooooooooooooooooooooooooooooooooo():
|
||||
- ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
+ X | Y | foooooooooooooooooooooooooooooooooooo()
|
||||
+): ...
|
||||
|
||||
|
||||
-def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(x) -> X | Y | foooooooooooooooooooooooooooooooooooo():
|
||||
- ...
|
||||
+def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
+ x
|
||||
+) -> X | Y | foooooooooooooooooooooooooooooooooooo(): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx() -> (
|
||||
X | Y | foooooooooooooooooooooooooooooooooooo() # comment
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def xxxxxxxxxxxxxxxxxxxxxxxxxxxx(
|
||||
x
|
||||
) -> (
|
||||
X | Y | foooooooooooooooooooooooooooooooooooo() # comment
|
||||
-):
|
||||
- ...
|
||||
+): ...
|
||||
|
||||
|
||||
def double() -> (
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -115,4 +115,57 @@ def quuz():
|
||||
```
|
||||
|
||||
|
||||
## Preview changes
|
||||
```diff
|
||||
--- Stable
|
||||
+++ Preview
|
||||
@@ -12,25 +12,20 @@
|
||||
pass
|
||||
|
||||
|
||||
-class Del(expr_context):
|
||||
- ...
|
||||
+class Del(expr_context): ...
|
||||
|
||||
|
||||
-class Load(expr_context):
|
||||
- ...
|
||||
+class Load(expr_context): ...
|
||||
|
||||
|
||||
# Some comment.
|
||||
-class Other(expr_context):
|
||||
- ...
|
||||
+class Other(expr_context): ...
|
||||
|
||||
|
||||
-class Store(expr_context):
|
||||
- ...
|
||||
+class Store(expr_context): ...
|
||||
|
||||
|
||||
-class Foo(Bar):
|
||||
- ...
|
||||
+class Foo(Bar): ...
|
||||
|
||||
|
||||
class Baz(Qux):
|
||||
@@ -49,12 +44,10 @@
|
||||
pass
|
||||
|
||||
|
||||
-def bar():
|
||||
- ...
|
||||
+def bar(): ...
|
||||
|
||||
|
||||
-def baz():
|
||||
- ...
|
||||
+def baz(): ...
|
||||
|
||||
|
||||
def quux():
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_parser::lexer::{lex, LexicalError};
|
||||
use ruff_python_parser::{Mode, Tok};
|
||||
use ruff_python_parser::{AsMode, Tok};
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
@@ -25,11 +26,12 @@ impl CommentRangesBuilder {
|
||||
/// Helper method to lex and extract comment ranges
|
||||
pub fn tokens_and_ranges(
|
||||
source: &str,
|
||||
source_type: PySourceType,
|
||||
) -> Result<(Vec<(Tok, TextRange)>, CommentRanges), LexicalError> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut comment_ranges = CommentRangesBuilder::default();
|
||||
|
||||
for result in lex(source, Mode::Module) {
|
||||
for result in lex(source, source_type.as_mode()) {
|
||||
let (token, range) = result?;
|
||||
|
||||
comment_ranges.visit_token(&token, range);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_shrinking"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -226,8 +226,9 @@ impl Configuration {
|
||||
dummy_variable_rgx: lint
|
||||
.dummy_variable_rgx
|
||||
.unwrap_or_else(|| DUMMY_VARIABLE_RGX.clone()),
|
||||
external: FxHashSet::from_iter(lint.external.unwrap_or_default()),
|
||||
external: lint.external.unwrap_or_default(),
|
||||
ignore_init_module_imports: lint.ignore_init_module_imports.unwrap_or_default(),
|
||||
line_length,
|
||||
tab_size: self.indent_width.unwrap_or_default(),
|
||||
namespace_packages: self.namespace_packages.unwrap_or_default(),
|
||||
per_file_ignores: resolve_per_file_ignores(
|
||||
|
||||
@@ -352,7 +352,7 @@ pub struct Options {
|
||||
|
||||
// Global Formatting options
|
||||
/// The line length to use when enforcing long-lines violations (like `E501`)
|
||||
/// and at which the formatter prefers to wrap lines.
|
||||
/// and at which `isort` and the formatter prefers to wrap lines.
|
||||
///
|
||||
/// The length is determined by the number of characters per line, except for lines containing East Asian characters or emojis.
|
||||
/// For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.
|
||||
@@ -548,7 +548,7 @@ pub struct LintCommonOptions {
|
||||
)]
|
||||
pub extend_unfixable: Option<Vec<RuleSelector>>,
|
||||
|
||||
/// A list of rule codes that are unsupported by Ruff, but should be
|
||||
/// A list of rule codes or prefixes that are unsupported by Ruff, but should be
|
||||
/// preserved when (e.g.) validating `# noqa` directives. Useful for
|
||||
/// retaining `# noqa` directives that cover plugins not yet implemented
|
||||
/// by Ruff.
|
||||
@@ -556,9 +556,9 @@ pub struct LintCommonOptions {
|
||||
default = "[]",
|
||||
value_type = "list[str]",
|
||||
example = r#"
|
||||
# Avoiding flagging (and removing) `V101` from any `# noqa`
|
||||
# directives, despite Ruff's lack of support for `vulture`.
|
||||
external = ["V101"]
|
||||
# Avoiding flagging (and removing) any codes starting with `V` from any
|
||||
# `# noqa` directives, despite Ruff's lack of support for `vulture`.
|
||||
external = ["V"]
|
||||
"#
|
||||
)]
|
||||
pub external: Option<Vec<String>>,
|
||||
@@ -1412,6 +1412,9 @@ impl Flake8PytestStyleOptions {
|
||||
pub struct Flake8QuotesOptions {
|
||||
/// Quote style to prefer for inline strings (either "single" or
|
||||
/// "double").
|
||||
///
|
||||
/// When using the formatter, ensure that `format.quote-style` is set to
|
||||
/// the same preferred quote style.
|
||||
#[option(
|
||||
default = r#""double""#,
|
||||
value_type = r#""single" | "double""#,
|
||||
@@ -1423,6 +1426,9 @@ pub struct Flake8QuotesOptions {
|
||||
|
||||
/// Quote style to prefer for multiline strings (either "single" or
|
||||
/// "double").
|
||||
///
|
||||
/// When using the formatter, only "double" is compatible, as the formatter
|
||||
/// enforces double quotes for multiline strings.
|
||||
#[option(
|
||||
default = r#""double""#,
|
||||
value_type = r#""single" | "double""#,
|
||||
@@ -1433,6 +1439,9 @@ pub struct Flake8QuotesOptions {
|
||||
pub multiline_quotes: Option<Quote>,
|
||||
|
||||
/// Quote style to prefer for docstrings (either "single" or "double").
|
||||
///
|
||||
/// When using the formatter, only "double" is compatible, as the formatter
|
||||
/// enforces double quotes for docstrings strings.
|
||||
#[option(
|
||||
default = r#""double""#,
|
||||
value_type = r#""single" | "double""#,
|
||||
@@ -1683,6 +1692,9 @@ pub struct IsortOptions {
|
||||
/// `combine-as-imports = true`. When `combine-as-imports` isn't
|
||||
/// enabled, every aliased `import from` will be given its own line, in
|
||||
/// which case, wrapping is not necessary.
|
||||
///
|
||||
/// When using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default)
|
||||
/// when enabling `force-wrap-aliases` to avoid that the formatter collapses members if they all fit on a single line.
|
||||
#[option(
|
||||
default = r#"false"#,
|
||||
value_type = "bool",
|
||||
@@ -1726,6 +1738,9 @@ pub struct IsortOptions {
|
||||
/// the imports will never be folded into one line.
|
||||
///
|
||||
/// See isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option.
|
||||
///
|
||||
/// When using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `split-on-trailing-comma`
|
||||
/// to avoid that the formatter removes the trailing commas.
|
||||
#[option(
|
||||
default = r#"true"#,
|
||||
value_type = "bool",
|
||||
@@ -1907,6 +1922,9 @@ pub struct IsortOptions {
|
||||
|
||||
/// The number of blank lines to place after imports.
|
||||
/// Use `-1` for automatic determination.
|
||||
///
|
||||
/// When using the formatter, only the values `-1`, `1`, and `2` are compatible because
|
||||
/// it enforces at least one empty and at most two empty lines after imports.
|
||||
#[option(
|
||||
default = r#"-1"#,
|
||||
value_type = "int",
|
||||
@@ -1918,6 +1936,9 @@ pub struct IsortOptions {
|
||||
pub lines_after_imports: Option<isize>,
|
||||
|
||||
/// The number of lines to place between "direct" and `import from` imports.
|
||||
///
|
||||
/// When using the formatter, only the values `0` and `1` are compatible because
|
||||
/// it preserves up to one empty line after imports in nested blocks.
|
||||
#[option(
|
||||
default = r#"0"#,
|
||||
value_type = "int",
|
||||
@@ -2301,7 +2322,7 @@ pub struct PycodestyleOptions {
|
||||
/// documentation (`W505`), including standalone comments. By default,
|
||||
/// this is set to null which disables reporting violations.
|
||||
///
|
||||
/// The length is determined by the number of characters per line, except for lines containinAsian characters or emojis.
|
||||
/// The length is determined by the number of characters per line, except for lines containing Asian characters or emojis.
|
||||
/// For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.
|
||||
///
|
||||
/// See the [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) rule for more information.
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::sync::RwLock;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, bail};
|
||||
use globset::{Candidate, GlobSet};
|
||||
use ignore::{WalkBuilder, WalkState};
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
@@ -333,12 +334,18 @@ pub fn python_files_in_path(
|
||||
let resolver = resolver.read().unwrap();
|
||||
let settings = resolver.resolve(path, pyproject_config);
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if match_exclusion(path, file_name, &settings.file_resolver.exclude) {
|
||||
let file_path = Candidate::new(path);
|
||||
let file_basename = Candidate::new(file_name);
|
||||
if match_candidate_exclusion(
|
||||
&file_path,
|
||||
&file_basename,
|
||||
&settings.file_resolver.exclude,
|
||||
) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return WalkState::Skip;
|
||||
} else if match_exclusion(
|
||||
path,
|
||||
file_name,
|
||||
} else if match_candidate_exclusion(
|
||||
&file_path,
|
||||
&file_basename,
|
||||
&settings.file_resolver.extend_exclude,
|
||||
) {
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
@@ -509,10 +516,20 @@ fn is_file_excluded(
|
||||
for path in path.ancestors() {
|
||||
let settings = resolver.resolve(path, pyproject_strategy);
|
||||
if let Some(file_name) = path.file_name() {
|
||||
if match_exclusion(path, file_name, &settings.file_resolver.exclude) {
|
||||
let file_path = Candidate::new(path);
|
||||
let file_basename = Candidate::new(file_name);
|
||||
if match_candidate_exclusion(
|
||||
&file_path,
|
||||
&file_basename,
|
||||
&settings.file_resolver.exclude,
|
||||
) {
|
||||
debug!("Ignored path via `exclude`: {:?}", path);
|
||||
return true;
|
||||
} else if match_exclusion(path, file_name, &settings.file_resolver.extend_exclude) {
|
||||
} else if match_candidate_exclusion(
|
||||
&file_path,
|
||||
&file_basename,
|
||||
&settings.file_resolver.extend_exclude,
|
||||
) {
|
||||
debug!("Ignored path via `extend-exclude`: {:?}", path);
|
||||
return true;
|
||||
}
|
||||
@@ -533,11 +550,27 @@ fn is_file_excluded(
|
||||
pub fn match_exclusion<P: AsRef<Path>, R: AsRef<Path>>(
|
||||
file_path: P,
|
||||
file_basename: R,
|
||||
exclusion: &globset::GlobSet,
|
||||
exclusion: &GlobSet,
|
||||
) -> bool {
|
||||
if exclusion.is_empty() {
|
||||
return false;
|
||||
}
|
||||
exclusion.is_match(file_path) || exclusion.is_match(file_basename)
|
||||
}
|
||||
|
||||
/// Return `true` if the given candidates should be ignored based on the exclusion
|
||||
/// criteria.
|
||||
pub fn match_candidate_exclusion(
|
||||
file_path: &Candidate,
|
||||
file_basename: &Candidate,
|
||||
exclusion: &GlobSet,
|
||||
) -> bool {
|
||||
if exclusion.is_empty() {
|
||||
return false;
|
||||
}
|
||||
exclusion.is_match_candidate(file_path) || exclusion.is_match_candidate(file_basename)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::{create_dir, File};
|
||||
|
||||
@@ -67,7 +67,7 @@ quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
||||
# Like Black, respect magic trailing commas.
|
||||
magic-trailing-comma = "respect"
|
||||
skip-magic-trailing-comma = false
|
||||
|
||||
# Like Black, automatically detect the appropriate line ending.
|
||||
line-ending = "auto"
|
||||
@@ -78,7 +78,7 @@ As an example, the following would configure Ruff to:
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# 1. Enable flake8-bugbear (`B`) rules, in addition to the defaults.
|
||||
select = ["E", "F", "B"]
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
|
||||
# 2. Avoid enforcing line-length violations (`E501`)
|
||||
ignore = ["E501"]
|
||||
@@ -101,7 +101,7 @@ Linter plugin configurations are expressed as subsections, e.g.:
|
||||
```toml
|
||||
[tool.ruff.lint]
|
||||
# Add "Q" to the list of enabled codes.
|
||||
select = ["E", "F", "Q"]
|
||||
select = ["E4", "E7", "E9", "F", "Q"]
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
docstring-quotes = "double"
|
||||
@@ -121,7 +121,7 @@ For example, the `pyproject.toml` described above would be represented via the f
|
||||
```toml
|
||||
[lint]
|
||||
# Enable flake8-bugbear (`B`) rules.
|
||||
select = ["E", "F", "B"]
|
||||
select = ["E4", "E7", "E9", "F", "B"]
|
||||
|
||||
# Never enforce `E501` (line length violations).
|
||||
ignore = ["E501"]
|
||||
|
||||
@@ -164,10 +164,10 @@ elif False: # fmt: skip
|
||||
pass
|
||||
|
||||
@Test
|
||||
@Test2 # fmt: off
|
||||
@Test2 # fmt: skip
|
||||
def test(): ...
|
||||
|
||||
a = [1, 2, 3, 4, 5] # fmt: off
|
||||
a = [1, 2, 3, 4, 5] # fmt: skip
|
||||
|
||||
def test(a, b, c, d, e, f) -> int: # fmt: skip
|
||||
pass
|
||||
@@ -180,9 +180,10 @@ comments, which are treated equivalently to `# fmt: off` and `# fmt: on`, respec
|
||||
|
||||
Ruff's formatter is designed to be used alongside the linter. However, the linter includes
|
||||
some rules that, when enabled, can cause conflicts with the formatter, leading to unexpected
|
||||
behavior.
|
||||
behavior. When configured appropriately, the goal of Ruff's formatter-linter compatibility is
|
||||
such that running the formatter should never introduce new lint errors.
|
||||
|
||||
When using Ruff as a formatter, we recommend disabling the following rules:
|
||||
As such, when using Ruff as a formatter, we recommend avoiding the following lint rules:
|
||||
|
||||
- [`tab-indentation`](rules/tab-indentation.md) (`W191`)
|
||||
- [`indentation-with-invalid-multiple`](rules/indentation-with-invalid-multiple.md) (`E111`)
|
||||
@@ -199,7 +200,11 @@ When using Ruff as a formatter, we recommend disabling the following rules:
|
||||
- [`single-line-implicit-string-concatenation`](rules/single-line-implicit-string-concatenation.md) (`ISC001`)
|
||||
- [`multi-line-implicit-string-concatenation`](rules/multi-line-implicit-string-concatenation.md) (`ISC002`)
|
||||
|
||||
Similarly, we recommend disabling the following isort settings, which are incompatible with the
|
||||
None of the above are included in Ruff's default configuration. However, if you've enabled
|
||||
any of these rules or their parent categories (like `Q`), we recommend disabling them via the
|
||||
linter's [`ignore`](settings.md#ignore) setting.
|
||||
|
||||
Similarly, we recommend avoiding the following isort settings, which are incompatible with the
|
||||
formatter's treatment of import statements when set to non-default values:
|
||||
|
||||
- [`force-single-line`](settings.md#isort-force-single-line)
|
||||
@@ -208,6 +213,12 @@ formatter's treatment of import statements when set to non-default values:
|
||||
- [`lines-between-types`](settings.md#isort-lines-between-types)
|
||||
- [`split-on-trailing-comma`](settings.md#isort-split-on-trailing-comma)
|
||||
|
||||
If you've configured any of these settings to take on non-default values, we recommend removing
|
||||
them from your Ruff configuration.
|
||||
|
||||
When an incompatible lint rule or setting is enabled, `ruff format` will emit a warning. If your
|
||||
`ruff format` is free of warnings, you're good to go!
|
||||
|
||||
## Exit codes
|
||||
|
||||
`ruff format` exits with the following status codes:
|
||||
|
||||
@@ -399,28 +399,6 @@ def update_emission_strength():
|
||||
value = self.emission_strength * 2
|
||||
```
|
||||
|
||||
### Type annotations may be parenthesized when expanded
|
||||
|
||||
Black will avoid parenthesizing type annotations in an annotated assignment, while Ruff will insert
|
||||
parentheses in some cases.
|
||||
|
||||
For example:
|
||||
|
||||
```python
|
||||
# Black
|
||||
StartElementHandler: Callable[[str, dict[str, str]], Any] | Callable[[str, list[str]], Any] | Callable[
|
||||
[str, dict[str, str], list[str]], Any
|
||||
] | None
|
||||
|
||||
# Ruff
|
||||
StartElementHandler: (
|
||||
Callable[[str, dict[str, str]], Any]
|
||||
| Callable[[str, list[str]], Any]
|
||||
| Callable[[str, dict[str, str], list[str]], Any]
|
||||
| None
|
||||
)
|
||||
```
|
||||
|
||||
### Call chain calls break differently
|
||||
|
||||
Black occasionally breaks call chains differently than Ruff; in particular, Black occasionally
|
||||
|
||||
@@ -55,6 +55,10 @@ or isort, _unless_ you enable autofix, in which case, Ruff's pre-commit hook sho
|
||||
Black, isort, and other formatting tools, as Ruff's autofix behavior can output code changes that
|
||||
require reformatting.
|
||||
|
||||
As long as your Ruff configuration avoids any [linter-formatter incompatibilities](formatter.md#conflicting-lint-rules),
|
||||
`ruff format` should never introduce new lint errors, so it's safe to run Ruff's format hook _after_
|
||||
`ruff check --fix`.
|
||||
|
||||
## Language Server Protocol (Official)
|
||||
|
||||
Ruff supports the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PyYAML==6.0
|
||||
black==23.3.0
|
||||
black==23.10.0
|
||||
mkdocs==1.5.0
|
||||
git+ssh://git@github.com/astral-sh/mkdocs-material-insiders.git@38c0b8187325c3bab386b666daf3518ac036f2f4
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PyYAML==6.0
|
||||
black==23.3.0
|
||||
black==23.10.0
|
||||
mkdocs==1.5.0
|
||||
mkdocs-material==9.1.18
|
||||
|
||||
@@ -110,7 +110,7 @@ To configure Ruff, let's create a `pyproject.toml` file in our project's root di
|
||||
# Set the maximum line length to 79.
|
||||
line-length = 79
|
||||
|
||||
[tool.ruff.linter]
|
||||
[tool.ruff.lint]
|
||||
# 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.
|
||||
@@ -137,7 +137,7 @@ requires-python = ">=3.10"
|
||||
# Set the maximum line length to 79.
|
||||
line-length = 79
|
||||
|
||||
[tool.ruff.linter]
|
||||
[tool.ruff.lint]
|
||||
# Add the `line-too-long` rule to the enforced rule set.
|
||||
extend-select = ["E501"]
|
||||
```
|
||||
@@ -164,7 +164,7 @@ rules, we can set our `pyproject.toml` to the following:
|
||||
[project]
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[tool.ruff.linter]
|
||||
[tool.ruff.lint]
|
||||
extend-select = [
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
@@ -187,13 +187,13 @@ all functions have docstrings:
|
||||
[project]
|
||||
requires-python = ">=3.10"
|
||||
|
||||
[tool.ruff.linter]
|
||||
[tool.ruff.lint]
|
||||
extend-select = [
|
||||
"UP", # pyupgrade
|
||||
"D", # pydocstyle
|
||||
]
|
||||
|
||||
[tool.ruff.linter.pydocstyle]
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
```
|
||||
|
||||
@@ -284,13 +284,13 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
# Run the Ruff linter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.1.2
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
# Run the Ruff formatter.
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.291
|
||||
rev: v0.1.3
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
```
|
||||
|
||||
@@ -13,6 +13,8 @@ Ruff uses a custom versioning scheme that uses the **minor** version number for
|
||||
- Support for a new file type is promoted to stable
|
||||
- Support for an end-of-life Python version is dropped
|
||||
- The behavior of a stable rule is changed
|
||||
- The scope of a stable rule is significantly increased
|
||||
- The intent of the rule changes
|
||||
- Does not include bug fixes that follow the original intent of the rule
|
||||
- Stable rules are added to the default set
|
||||
- Stable rules are removed from the default set
|
||||
@@ -23,6 +25,7 @@ Ruff uses a custom versioning scheme that uses the **minor** version number for
|
||||
- Bugs are fixed, _including behavior changes that fix bugs_
|
||||
- An unsafe fix for a rule is added
|
||||
- A safe fix for a rule is added in preview
|
||||
- The scope of a rule is increased in preview
|
||||
- A fix’s applicability is demoted
|
||||
- A new configuration option is added
|
||||
- A rule is added in preview
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
|
||||
readme = "README.md"
|
||||
|
||||
22
ruff.schema.json
generated
22
ruff.schema.json
generated
@@ -159,7 +159,7 @@
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"description": "A list of rule codes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.",
|
||||
"description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
@@ -436,7 +436,7 @@
|
||||
]
|
||||
},
|
||||
"line-length": {
|
||||
"description": "The line length to use when enforcing long-lines violations (like `E501`) and at which the formatter prefers to wrap lines.\n\nThe length is determined by the number of characters per line, except for lines containing East Asian characters or emojis. For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.\n\nThe value must be greater than `0` and less than or equal to `320`.\n\nNote: While the formatter will attempt to format lines such that they remain within the `line-length`, it isn't a hard upper bound, and formatted lines may exceed the `line-length`.\n\nSee [`pycodestyle.max-line-length`](#pycodestyle-max-line-length) to configure different lengths for `E501` and the formatter.",
|
||||
"description": "The line length to use when enforcing long-lines violations (like `E501`) and at which `isort` and the formatter prefers to wrap lines.\n\nThe length is determined by the number of characters per line, except for lines containing East Asian characters or emojis. For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.\n\nThe value must be greater than `0` and less than or equal to `320`.\n\nNote: While the formatter will attempt to format lines such that they remain within the `line-length`, it isn't a hard upper bound, and formatted lines may exceed the `line-length`.\n\nSee [`pycodestyle.max-line-length`](#pycodestyle-max-line-length) to configure different lengths for `E501` and the formatter.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LineLength"
|
||||
@@ -1083,7 +1083,7 @@
|
||||
]
|
||||
},
|
||||
"docstring-quotes": {
|
||||
"description": "Quote style to prefer for docstrings (either \"single\" or \"double\").",
|
||||
"description": "Quote style to prefer for docstrings (either \"single\" or \"double\").\n\nWhen using the formatter, only \"double\" is compatible, as the formatter enforces double quotes for docstrings strings.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Quote"
|
||||
@@ -1094,7 +1094,7 @@
|
||||
]
|
||||
},
|
||||
"inline-quotes": {
|
||||
"description": "Quote style to prefer for inline strings (either \"single\" or \"double\").",
|
||||
"description": "Quote style to prefer for inline strings (either \"single\" or \"double\").\n\nWhen using the formatter, ensure that `format.quote-style` is set to the same preferred quote style.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Quote"
|
||||
@@ -1105,7 +1105,7 @@
|
||||
]
|
||||
},
|
||||
"multiline-quotes": {
|
||||
"description": "Quote style to prefer for multiline strings (either \"single\" or \"double\").",
|
||||
"description": "Quote style to prefer for multiline strings (either \"single\" or \"double\").\n\nWhen using the formatter, only \"double\" is compatible, as the formatter enforces double quotes for multiline strings.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Quote"
|
||||
@@ -1424,7 +1424,7 @@
|
||||
}
|
||||
},
|
||||
"force-wrap-aliases": {
|
||||
"description": "Force `import from` statements with multiple members and at least one alias (e.g., `import A as B`) to wrap such that every line contains exactly one member. For example, this formatting would be retained, rather than condensing to a single line:\n\n```python from .utils import ( test_directory as test_directory, test_id as test_id ) ```\n\nNote that this setting is only effective when combined with `combine-as-imports = true`. When `combine-as-imports` isn't enabled, every aliased `import from` will be given its own line, in which case, wrapping is not necessary.",
|
||||
"description": "Force `import from` statements with multiple members and at least one alias (e.g., `import A as B`) to wrap such that every line contains exactly one member. For example, this formatting would be retained, rather than condensing to a single line:\n\n```python from .utils import ( test_directory as test_directory, test_id as test_id ) ```\n\nNote that this setting is only effective when combined with `combine-as-imports = true`. When `combine-as-imports` isn't enabled, every aliased `import from` will be given its own line, in which case, wrapping is not necessary.\n\nWhen using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `force-wrap-aliases` to avoid that the formatter collapses members if they all fit on a single line.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
@@ -1471,7 +1471,7 @@
|
||||
}
|
||||
},
|
||||
"lines-after-imports": {
|
||||
"description": "The number of blank lines to place after imports. Use `-1` for automatic determination.",
|
||||
"description": "The number of blank lines to place after imports. Use `-1` for automatic determination.\n\nWhen using the formatter, only the values `-1`, `1`, and `2` are compatible because it enforces at least one empty and at most two empty lines after imports.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
@@ -1479,7 +1479,7 @@
|
||||
"format": "int"
|
||||
},
|
||||
"lines-between-types": {
|
||||
"description": "The number of lines to place between \"direct\" and `import from` imports.",
|
||||
"description": "The number of lines to place between \"direct\" and `import from` imports.\n\nWhen using the formatter, only the values `0` and `1` are compatible because it preserves up to one empty line after imports in nested blocks.",
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
@@ -1559,7 +1559,7 @@
|
||||
}
|
||||
},
|
||||
"split-on-trailing-comma": {
|
||||
"description": "If a comma is placed after the last member in a multi-line import, then the imports will never be folded into one line.\n\nSee isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option.",
|
||||
"description": "If a comma is placed after the last member in a multi-line import, then the imports will never be folded into one line.\n\nSee isort's [`split-on-trailing-comma`](https://pycqa.github.io/isort/docs/configuration/options.html#split-on-trailing-comma) option.\n\nWhen using the formatter, ensure that `format.skip-magic-trailing-comma` is set to `false` (default) when enabling `split-on-trailing-comma` to avoid that the formatter removes the trailing commas.",
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
@@ -1733,7 +1733,7 @@
|
||||
}
|
||||
},
|
||||
"external": {
|
||||
"description": "A list of rule codes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.",
|
||||
"description": "A list of rule codes or prefixes that are unsupported by Ruff, but should be preserved when (e.g.) validating `# noqa` directives. Useful for retaining `# noqa` directives that cover plugins not yet implemented by Ruff.",
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
@@ -2213,7 +2213,7 @@
|
||||
]
|
||||
},
|
||||
"max-doc-length": {
|
||||
"description": "The maximum line length to allow for [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) violations within documentation (`W505`), including standalone comments. By default, this is set to null which disables reporting violations.\n\nThe length is determined by the number of characters per line, except for lines containinAsian characters or emojis. For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.\n\nSee the [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) rule for more information.",
|
||||
"description": "The maximum line length to allow for [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) violations within documentation (`W505`), including standalone comments. By default, this is set to null which disables reporting violations.\n\nThe length is determined by the number of characters per line, except for lines containing Asian characters or emojis. For these lines, the [unicode width](https://unicode.org/reports/tr11/) of each character is added up to determine the length.\n\nSee the [`doc-line-too-long`](https://docs.astral.sh/ruff/rules/doc-line-too-long/) rule for more information.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LineLength"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "scripts"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
description = ""
|
||||
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]
|
||||
|
||||
|
||||
@@ -70,6 +70,12 @@ if [ ! -d "$dir/cpython/.git" ]; then
|
||||
fi
|
||||
git -C "$dir/cpython" checkout -q b75186f69edcf54615910a5cd707996144163ef7
|
||||
|
||||
# poetry itself
|
||||
if [ ! -d "$dir/poetry/.git" ]; then
|
||||
git clone --filter=tree:0 https://github.com/python-poetry/poetry "$dir/poetry"
|
||||
fi
|
||||
git -C "$dir/poetry" checkout -q 611033a7335f3c8e2b74dd58688fb9021cf84a5b
|
||||
|
||||
# Uncomment if you want to update the hashes
|
||||
#for i in "$dir"/*/; do git -C "$i" switch main && git -C "$i" pull; done
|
||||
#for i in "$dir"/*/; do echo "# $(basename "$i") $(git -C "$i" rev-parse HEAD)"; done
|
||||
|
||||
Reference in New Issue
Block a user