Compare commits

...

546 Commits

Author SHA1 Message Date
Charlie Marsh
d8c9500b37 Rename all of these structs 2023-07-30 19:56:11 -04:00
Charlie Marsh
ac25bd9596 Traits 2023-07-30 19:55:50 -04:00
Charlie Marsh
ac0f51ef4f Some abstractions 2023-07-30 19:55:50 -04:00
Charlie Marsh
3eec7de6e4 Clean up 2023-07-30 19:55:50 -04:00
Charlie Marsh
5dbe4e4653 Remove qualified name 2023-07-30 19:55:49 -04:00
Charlie Marsh
43eb51b15a Handle relative 2023-07-30 19:55:23 -04:00
Charlie Marsh
68a12f72a4 Tweaks 2023-07-30 19:55:23 -04:00
Charlie Marsh
15273c6d95 Store the call path 2023-07-30 19:55:23 -04:00
Charlie Marsh
de898c52eb Avoid falsely marking non-submodules as submodule aliases (#6182)
## Summary

We have some code to ensure that if an aliased import is used, any
submodules should be marked as used too. This comment says it best:

```rust
// If the name of a submodule import is the same as an alias of another import, and the
// alias is used, then the submodule import should be marked as used too.
//
// For example, mark `pyarrow.csv` as used in:
//
// ```python
// import pyarrow as pa
// import pyarrow.csv
// print(pa.csv.read_csv("test.csv"))
// ```
```

However, it looks like when we go to look up `pyarrow` (of `import
pyarrow as pa`), we aren't checking to ensure the resolved binding is
_actually_ an import. This was causing us to attribute `print(rm.ANY)`
to `def requests_mock` here:

```python
import requests_mock as rm

def requests_mock(requests_mock: rm.Mocker):
    print(rm.ANY)
```

Closes https://github.com/astral-sh/ruff/issues/6180.
2023-07-30 22:16:25 +00:00
Charlie Marsh
76741cac77 Add global and nonlocal formatting (#6170)
## Summary

Adds `global` and `nonlocal` formatting, without the "deviation from
black" outlined in the linked issue, which I'll do separately.

See: https://github.com/astral-sh/ruff/issues/4798.

## Test Plan

Added a fixture in the Ruff-specific directory since the Black fixtures
don't seem to cover this.
2023-07-29 14:39:42 +00:00
Charlie Marsh
5d9814d84d Remove parentheses around some walrus operators (#6173)
## Summary

Closes https://github.com/astral-sh/ruff/issues/5781

## Test Plan

Added cases to
`crates/ruff_python_formatter/resources/test/fixtures/ruff/expression/named_expr.py`
one-by-one and adjusted the condition as needed.
2023-07-29 10:06:26 -04:00
Micha Reiser
1d7ad30188 CI: Update formatter dependencies (#6168) 2023-07-29 15:24:24 +02:00
Charlie Marsh
4231ed2fc3 Skip partial duplicates when applying multi-edit fixes (#6144)
## Summary

Right now, if we have two fixes that have an overlapping edit, but not
an _identical_ set of edits, they'll conflict, causing us to do another
linter traversal. Here, I've enabled the fixer to support partially
overlapping edits, which (as an example) let's us greatly reduce the
number of iterations required in the test suite.

The most common case here is that in which a bunch of edits need to
import some symbol, and then use that symbol, but in different ways. In
that case, all edits will have a common fix (to import the symbol), but
deviate in some way. With this change, we can do all of those edits in
one pass.

Note that the simplest way to enable this was to store sorted edits on
`Fix`. We don't allow modifying the edits on `Fix` once it's
constructed, so this is an easy change, and allows us to avoid a bunch
of clones and traversals later on.

Closes #5800.
2023-07-29 12:11:57 +00:00
Charlie Marsh
badbfb2d3e Skip BOM when determining Locator's line starts (#6159)
## Summary

If a file has a BOM, the import sorter _always_ reports the imports as
unsorted. The acute issue is that we detect that the line has leading
content (before the imports), which we always consider a violation.
Rather than fixing that one site, this PR instead makes `.line_start`
BOM-aware.

Fixes https://github.com/astral-sh/ruff/issues/6155.
2023-07-29 11:47:13 +00:00
Dhruv Manilawala
44bdf20221 [pep8-naming]: New config option extend-ignore-names (#6169)
## Summary

This PR adds a new config option for `pep8-naming` plugin called
`extend-ignore-names` which is used to extend the default values in
`ignore-names` option.

resolves: #6050
2023-07-29 17:11:04 +05:30
Dhruv Manilawala
3c99fbf808 Implement --diff for Jupyter Notebooks (#6149)
## Summary

Implement `--diff` for Jupyter Notebooks

## Test Plan

1. Use `crates/ruff/resources/test/fixtures/jupyter/isort.ipynb` as a
test case
and add a markdown cell in between the code cells to check that the diff
   outputs the correct cell index.
2. Run the command:
`cargo run --bin ruff --package ruff_cli -- check --no-cache --isolated
--select=ALL crates/ruff/resources/test/fixtures/jupyter/isort.ipynb
--fix --diff`

<details><summary>Example output:</summary>
<p>

```diff
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 0
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 0
@@ -1,3 +0,0 @@
-from pathlib import Path
-import random
-import math
--- /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 4
+++ /Users/dhruv/playground/ruff/notebooks/test.ipynb:cell 4
@@ -1,5 +1,3 @@
-from typing import Any
-import collections
 # Newline should be added here
 def foo():
     pass

--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 8
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 8
@@ -1,8 +1,7 @@
 import pprint
 import tempfile
 
-from IPython import display
 import matplotlib.pyplot as plt
-
 import tensorflow as tf
-import tensorflow_datasets as tfds
+import tensorflow_datasets as tfds
+from IPython import display
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 10
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 10
@@ -1,5 +1,4 @@
 import tensorflow_models as tfm
 
 # These are not in the tfm public API for v2.9. They will be available in v2.10
-from official.vision.serving import export_saved_model_lib
-import official.core.train_lib
+from official.vision.serving import export_saved_model_lib
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 13
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 13
@@ -1,5 +1,5 @@
-exp_config = tfm.core.exp_factory.get_exp_config('resnet_imagenet')
-tfds_name = 'cifar10'
+exp_config = tfm.core.exp_factory.get_exp_config("resnet_imagenet")
+tfds_name = "cifar10"
 ds,ds_info = tfds.load(
 tfds_name,
 with_info=True)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 15
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 15
@@ -6,12 +6,12 @@
 # Configure training and testing data
 batch_size = 128
 
-exp_config.task.train_data.input_path = ''
+exp_config.task.train_data.input_path = ""
 exp_config.task.train_data.tfds_name = tfds_name
-exp_config.task.train_data.tfds_split = 'train'
+exp_config.task.train_data.tfds_split = "train"
 exp_config.task.train_data.global_batch_size = batch_size
 
-exp_config.task.validation_data.input_path = ''
+exp_config.task.validation_data.input_path = ""
 exp_config.task.validation_data.tfds_name = tfds_name
-exp_config.task.validation_data.tfds_split = 'test'
+exp_config.task.validation_data.tfds_split = "test"
 exp_config.task.validation_data.global_batch_size = batch_size
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 17
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 17
@@ -1,16 +1,16 @@
 logical_device_names = [logical_device.name for logical_device in tf.config.list_logical_devices()]
 
-if 'GPU' in ''.join(logical_device_names):
-  print('This may be broken in Colab.')
-  device = 'GPU'
-elif 'TPU' in ''.join(logical_device_names):
-  print('This may be broken in Colab.')
-  device = 'TPU'
+if "GPU" in "".join(logical_device_names):
+  print("This may be broken in Colab.")
+  device = "GPU"
+elif "TPU" in "".join(logical_device_names):
+  print("This may be broken in Colab.")
+  device = "TPU"
 else:
-  print('Running on CPU is slow, so only train for a few steps.')
-  device = 'CPU'
+  print("Running on CPU is slow, so only train for a few steps.")
+  device = "CPU"
 
-if device=='CPU':
+if device=="CPU":
   train_steps = 20
   exp_config.trainer.steps_per_loop = 5
 else:
@@ -20,9 +20,9 @@
 exp_config.trainer.summary_interval = 100
 exp_config.trainer.checkpoint_interval = train_steps
 exp_config.trainer.validation_interval = 1000
-exp_config.trainer.validation_steps =  ds_info.splits['test'].num_examples // batch_size
+exp_config.trainer.validation_steps =  ds_info.splits["test"].num_examples // batch_size
 exp_config.trainer.train_steps = train_steps
-exp_config.trainer.optimizer_config.learning_rate.type = 'cosine'
+exp_config.trainer.optimizer_config.learning_rate.type = "cosine"
 exp_config.trainer.optimizer_config.learning_rate.cosine.decay_steps = train_steps
 exp_config.trainer.optimizer_config.learning_rate.cosine.initial_learning_rate = 0.1
 exp_config.trainer.optimizer_config.warmup.linear.warmup_steps = 100
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 21
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 21
@@ -1,14 +1,14 @@
 logical_device_names = [logical_device.name for logical_device in tf.config.list_logical_devices()]
 
 if exp_config.runtime.mixed_precision_dtype == tf.float16:
-    tf.keras.mixed_precision.set_global_policy('mixed_float16')
+    tf.keras.mixed_precision.set_global_policy("mixed_float16")
 
-if 'GPU' in ''.join(logical_device_names):
+if "GPU" in "".join(logical_device_names):
   distribution_strategy = tf.distribute.MirroredStrategy()
-elif 'TPU' in ''.join(logical_device_names):
+elif "TPU" in "".join(logical_device_names):
   tf.tpu.experimental.initialize_tpu_system()
-  tpu = tf.distribute.cluster_resolver.TPUClusterResolver(tpu='/device:TPU_SYSTEM:0')
+  tpu = tf.distribute.cluster_resolver.TPUClusterResolver(tpu="/device:TPU_SYSTEM:0")
   distribution_strategy = tf.distribute.experimental.TPUStrategy(tpu)
 else:
-  print('Warning: this will be really slow.')
+  print("Warning: this will be really slow.")
   distribution_strategy = tf.distribute.OneDeviceStrategy(logical_device_names[0])
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 23
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 23
@@ -1,5 +1,3 @@
 with distribution_strategy.scope():
   model_dir = tempfile.mkdtemp()
   task = tfm.core.task_factory.get_task(exp_config.task, logging_dir=model_dir)
-
-#  tf.keras.utils.plot_model(task.build_model(), show_shapes=True)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 24
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 24
@@ -1,4 +1,4 @@
 for images, labels in task.build_inputs(exp_config.task.train_data).take(1):
   print()
-  print(f'images.shape: {str(images.shape):16}  images.dtype: {images.dtype!r}')
-  print(f'labels.shape: {str(labels.shape):16}  labels.dtype: {labels.dtype!r}')
+  print(f"images.shape: {images.shape!s:16}  images.dtype: {images.dtype!r}")
+  print(f"labels.shape: {labels.shape!s:16}  labels.dtype: {labels.dtype!r}")
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 27
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 27
@@ -1 +1 @@
-plt.hist(images.numpy().flatten());
+plt.hist(images.numpy().flatten())
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 29
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 29
@@ -1,2 +1,2 @@
-label_info = ds_info.features['label']
+label_info = ds_info.features["label"]
 label_info.int2str(1)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 31
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 31
@@ -10,9 +10,6 @@
     if predictions is None:
       plt.title(label_info.int2str(labels[i]))
     else:
-      if labels[i] == predictions[i]:
-        color = 'g'
-      else:
-        color = 'r'
+      color = "g" if labels[i] == predictions[i] else "r"
       plt.title(label_info.int2str(predictions[i]), color=color)
     plt.axis("off")
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 35
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 35
@@ -1,3 +1,3 @@
-plt.figure(figsize=(10, 10));
+plt.figure(figsize=(10, 10))
 for images, labels in task.build_inputs(exp_config.task.validation_data).take(1):
   show_batch(images, labels)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 37
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 37
@@ -1,7 +1,7 @@
 model, eval_logs = tfm.core.train_lib.run_experiment(
     distribution_strategy=distribution_strategy,
     task=task,
-    mode='train_and_eval',
+    mode="train_and_eval",
     params=exp_config,
     model_dir=model_dir,
     run_post_eval=True)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 38
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 38
@@ -1 +0,0 @@
-#  tf.keras.utils.plot_model(model, show_shapes=True)
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 40
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 40
@@ -1,4 +1,4 @@
 for key, value in eval_logs.items():
     if isinstance(value, tf.Tensor):
       value = value.numpy()
-    print(f'{key:20}: {value:.3f}')
+    print(f"{key:20}: {value:.3f}")
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 42
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 42
@@ -4,5 +4,5 @@
 
 show_batch(images, labels, tf.cast(predictions, tf.int32))
 
-if device=='CPU':
-  plt.suptitle('The model was only trained for a few steps, it is not expected to do well.')
+if device=="CPU":
+  plt.suptitle("The model was only trained for a few steps, it is not expected to do well.")
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 45
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 45
@@ -1,8 +1,8 @@
 # Saving and exporting the trained model
 export_saved_model_lib.export_inference_graph(
-    input_type='image_tensor',
+    input_type="image_tensor",
     batch_size=1,
     input_image_size=[32, 32],
     params=exp_config,
     checkpoint_path=tf.train.latest_checkpoint(model_dir),
-    export_dir='./export/')
+    export_dir="./export/")
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 47
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 47
@@ -1,3 +1,3 @@
 # Importing SavedModel
-imported = tf.saved_model.load('./export/')
-model_fn = imported.signatures['serving_default']
+imported = tf.saved_model.load("./export/")
+model_fn = imported.signatures["serving_default"]
--- /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 49
+++ /Users/dhruv/playground/ruff/notebooks/image_classification.ipynb:cell 49
@@ -1,10 +1,10 @@
 plt.figure(figsize=(10, 10))
-for data in tfds.load('cifar10', split='test').batch(12).take(1):
+for data in tfds.load("cifar10", split="test").batch(12).take(1):
   predictions = []
-  for image in data['image']:
-    index = tf.argmax(model_fn(image[tf.newaxis, ...])['logits'], axis=1)[0]
+  for image in data["image"]:
+    index = tf.argmax(model_fn(image[tf.newaxis, ...])["logits"], axis=1)[0]
     predictions.append(index)
-  show_batch(data['image'], data['label'], predictions)
+  show_batch(data["image"], data["label"], predictions)
 
-  if device=='CPU':
-    plt.suptitle('The model was only trained for a few steps, it is not expected to do better than random.')
+  if device=="CPU":
+    plt.suptitle("The model was only trained for a few steps, it is not expected to do better than random.")

Would fix 61 errors.
```

</p>
</details> 

resolves: #4727
2023-07-29 04:22:56 +00:00
Charlie Marsh
4802c7c7d8 Avoid key-in-dict violations for self accesses (#6165)
Closes https://github.com/astral-sh/ruff/issues/6163.
2023-07-29 03:35:26 +00:00
Charlie Marsh
646ff6497c Ignore end-of-line file exemption comments (#6160)
## Summary

This PR protects against code like:

```python
from typing import Optional

import bar  # ruff: noqa
import baz

class Foo:
    x: Optional[str] = None
```

In which the user wrote `# ruff: noqa` to ignore a specific error, not
realizing that it was a file-level exemption that thus turned off all
lint rules.

Specifically, if a `# ruff: noqa` directive is not at the start of a
line, we now ignore it and warn, since this is almost certainly a
mistake.
2023-07-29 00:40:32 +00:00
Victor Hugo Gomes
e0d5c7564f [flake8-pyi] Implement PYI049 (#6136)
## Summary

Checks for the presence of unused private `typing.TypedDict`
definitions.

ref #848 

## Test Plan

Snapshots and manual runs of flake8
2023-07-29 00:34:36 +00:00
Victor Hugo Gomes
7838d8c8af Implement PYI047 (#6134)
## Summary

Checks for the presence of unused private `typing.TypeAlias`
definitions.

ref #848 

## Test Plan

Snapshots and manual runs of flake8
2023-07-29 00:21:29 +00:00
Zanie Blue
047c211837 Add semantic analysis of type aliases and parameters (#6109)
Requires https://github.com/astral-sh/RustPython-Parser/pull/42
Related https://github.com/PyCQA/pyflakes/pull/778
[PEP-695](https://peps.python.org/pep-0695)
Part of #5062 

<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
Adds a scope for type parameters, a type parameter binding kind, and
checker visitation of type parameters in type alias statements, function
definitions, and class definitions.

A few changes were necessary to ensure correctness following the
insertion of a new scope between function and class scopes and their
parent.

## Test Plan

<!-- How was it tested? -->
Undefined name snapshots.

Unused type parameter rule will be added as follow-up.
2023-07-28 17:06:37 -05:00
Charlie Marsh
134d447d4c Avoid refactoring x[:1]-like slices in RUF015 (#6150)
## Summary

Right now, `RUF015` will try to rewrite `x[:1]` as `[next(x)]`. This
isn't equivalent if `x`, for example, is empty, where slicing like
`x[:1]` is forgiving, but `next` raises `StopIteration`. For me this is
a little too much of a deviation to be comfortable with, and most of the
value in this rule is the `x[0]` to `next(x)` conversion anyway.

Closes https://github.com/astral-sh/ruff/issues/6148.
2023-07-28 09:38:13 -04:00
Charlie Marsh
cd4147423c Skip PERF203 violations for multi-statement loops (#6145)
Closes https://github.com/astral-sh/ruff/issues/5858.
2023-07-28 04:55:55 +00:00
Charlie Marsh
d15436458f Only run unused private type rules over finalized bindings (#6142)
## Summary

In #6134 and #6136, we see some false positives for "shadowed" class
definitions. For example, here, the first definition is flagged as
unused, since from the perspective of the semantic model (which doesn't
understand branching), it appears to be immediately shadowed in the
`else`, and thus never used:

```python
if sys.version_info >= (3, 11):
    class _RootLoggerConfiguration(TypedDict, total=False):
        level: _Level
        filters: Sequence[str | _FilterType]
        handlers: Sequence[str]

else:
    class _RootLoggerConfiguration(TypedDict, total=False):
        level: _Level
        filters: Sequence[str]
        handlers: Sequence[str]
```

Instead of looking at _all_ bindings, we should instead look at the
"live" bindings, which is similar to how other rules (like unused
variables detection) is structured. We thus move the rule from
`bindings.rs` (which iterates over _all_ bindings, regardless of whether
they're shadowed) to `deferred_scopes.rs`, which iterates over all
"live" bindings once a scope has been fully analyzed.

## Test Plan

`cargo test`
2023-07-28 02:16:09 +00:00
Charlie Marsh
0bc3edf6c9 Add documentation and test cases for redefinition (#6135) 2023-07-28 00:01:42 +00:00
Aarni Koskela
3d54d31cd9 Implement E241 and E242 (tab/multiple ws after commas) (#6094)
## Summary

This PR implements pycodestyle's E241 (tab after comma) and E242
(multiple whitespace after comma) lints.

These are marked as nursery rules like many other pycodestyle rules.

Refs #2402

## Test Plan

E24.py copied from pycodestyle.
2023-07-27 18:58:41 +00:00
Tom Kuson
1418ee62f8 Add more documentation to the flake8-bandit rules (#6128)
## Summary

Completes the documentation for the ruleset, apart from four rules which
have contradictions, so need to be thought about more regarding how to
document that. Related to #2646.

## Test Plan

`python scripts/test_docs_formatted.py`
2023-07-27 18:57:45 +00:00
Harutaka Kawamura
bf987f80f4 Add PT017 and PT019 docs (#6115) 2023-07-27 18:56:34 +00:00
rembridge
bb08eea5cc missing-whitespace-around-operators comment (#6106)
**Summary**

Updated doc comments for `missing_whitespace_around_operator.rs`. Online
docs also benefit from this update.

**Test Plan**

Checked docs via
[mkdocs](389fe13c93/CONTRIBUTING.md (L267-L296))
2023-07-27 14:52:43 -04:00
Tom Kuson
d16216a2c2 Add documentation to the flynt rules (#6130)
## Summary

Completes the documentation for the one and only (current) rule in the
`flynt` ruleset. Related to #2646.

## Test Plan

`python scripts/test_docs_formatted.py`
2023-07-27 14:32:59 -04:00
Jelle van der Waa
0853004f41 [pylint] Implement eq-without-hash rule (PLW1641) (#5955)
Implement
https://pylint.pycqa.org/en/latest/user_guide/messages/warning/eq-without-hash.html
Issue https://github.com/astral-sh/ruff/issues/970

It's not enabled by default in pylint, so I guess it shouldn't in Ruff
either?
2023-07-27 18:28:44 +00:00
Harutaka Kawamura
fb5bbe30c7 Update SIM115 to cover pathlib.Path.open (#6118) 2023-07-27 14:20:52 -04:00
Charlie Marsh
dd706c7a35 Fix E211 documentation (#6133) 2023-07-27 17:19:33 +00:00
Charlie Marsh
e15b9c5572 Cache name resolutions in the semantic model (#6047)
## Summary

This PR stores the mapping from `ExprName` node to resolved `BindingId`,
which lets us skip scope lookups in `resolve_call_path`. It's enabled by
#6045, since that PR ensures that when we analyze a node (and thus call
`resolve_call_path`), we'll have already visited its `ExprName`
elements.

In more detail: imagine that we're traversing over `foo.bar()`. When we
read `foo`, it will be an `ExprName`, which we'll then resolve to a
binding via `handle_node_load`. With this change, we then store that
binding in a map. Later, if we call `collect_call_path` on `foo.bar`,
we'll identify `foo` (the "head" of the attribute) and grab the resolved
binding in that map. _Almost_ all names are now resolved in advance,
though it's not a strict requirement, and some rules break that pattern
(e.g., if we're analyzing arguments, and they need to inspect their
annotations, which are visited in a deferred manner).

This improves performance by 4-6% on the all-rules benchmark. It looks
like it hurts performance (1-2% drop) in the default-rules benchmark,
presumedly because those rules don't call `resolve_call_path` nearly as
much, and so we're paying for these extra writes.

Here's the benchmark data:

```
linter/default-rules/numpy/globals.py
                        time:   [67.270 µs 67.380 µs 67.489 µs]
                        thrpt:  [43.720 MiB/s 43.792 MiB/s 43.863 MiB/s]
                 change:
                        time:   [+0.4747% +0.7752% +1.0626%] (p = 0.00 < 0.05)
                        thrpt:  [-1.0514% -0.7693% -0.4724%]
                        Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe
linter/default-rules/pydantic/types.py
                        time:   [1.4067 ms 1.4105 ms 1.4146 ms]
                        thrpt:  [18.028 MiB/s 18.081 MiB/s 18.129 MiB/s]
                 change:
                        time:   [+1.3152% +1.6953% +2.0414%] (p = 0.00 < 0.05)
                        thrpt:  [-2.0006% -1.6671% -1.2981%]
                        Performance has regressed.
linter/default-rules/numpy/ctypeslib.py
                        time:   [637.67 µs 638.96 µs 640.28 µs]
                        thrpt:  [26.006 MiB/s 26.060 MiB/s 26.113 MiB/s]
                 change:
                        time:   [+1.5859% +1.8109% +2.0353%] (p = 0.00 < 0.05)
                        thrpt:  [-1.9947% -1.7787% -1.5611%]
                        Performance has regressed.
linter/default-rules/large/dataset.py
                        time:   [3.2289 ms 3.2336 ms 3.2383 ms]
                        thrpt:  [12.563 MiB/s 12.581 MiB/s 12.599 MiB/s]
                 change:
                        time:   [+0.8029% +0.9898% +1.1740%] (p = 0.00 < 0.05)
                        thrpt:  [-1.1604% -0.9801% -0.7965%]
                        Change within noise threshold.

linter/all-rules/numpy/globals.py
                        time:   [134.05 µs 134.15 µs 134.26 µs]
                        thrpt:  [21.977 MiB/s 21.995 MiB/s 22.012 MiB/s]
                 change:
                        time:   [-4.4571% -4.1175% -3.8268%] (p = 0.00 < 0.05)
                        thrpt:  [+3.9791% +4.2943% +4.6651%]
                        Performance has improved.
Found 8 outliers among 100 measurements (8.00%)
  2 (2.00%) low mild
  3 (3.00%) high mild
  3 (3.00%) high severe
linter/all-rules/pydantic/types.py
                        time:   [2.5627 ms 2.5669 ms 2.5720 ms]
                        thrpt:  [9.9158 MiB/s 9.9354 MiB/s 9.9516 MiB/s]
                 change:
                        time:   [-5.8304% -5.6374% -5.4452%] (p = 0.00 < 0.05)
                        thrpt:  [+5.7587% +5.9742% +6.1914%]
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  6 (6.00%) high mild
  1 (1.00%) high severe
linter/all-rules/numpy/ctypeslib.py
                        time:   [1.3949 ms 1.3956 ms 1.3964 ms]
                        thrpt:  [11.925 MiB/s 11.931 MiB/s 11.937 MiB/s]
                 change:
                        time:   [-6.2496% -6.0856% -5.9293%] (p = 0.00 < 0.05)
                        thrpt:  [+6.3030% +6.4799% +6.6662%]
                        Performance has improved.
Found 7 outliers among 100 measurements (7.00%)
  3 (3.00%) high mild
  4 (4.00%) high severe
linter/all-rules/large/dataset.py
                        time:   [5.5951 ms 5.6019 ms 5.6093 ms]
                        thrpt:  [7.2527 MiB/s 7.2623 MiB/s 7.2711 MiB/s]
                 change:
                        time:   [-5.1781% -4.9783% -4.8070%] (p = 0.00 < 0.05)
                        thrpt:  [+5.0497% +5.2391% +5.4608%]
                        Performance has improved.
```

Still playing with this (the concepts need better names, documentation,
etc.), but opening up for feedback.
2023-07-27 13:01:56 -04:00
qdegraaf
0638a26347 Add AnyExpressionYield to consolidate ExprYield and ExprYieldFrom (#6127)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-27 16:01:16 +00:00
konsti
2a65e6fc38 Explain check_docs_formatted.py error message (#6125)
## Summary

This is an error message only change to lead an implementor of a new
rule that has an unformatted or invalid bad example to the
right code.

## Test Plan

n/a
2023-07-27 10:22:13 -04:00
Charlie Marsh
13af91299d Avoid walking past root when resolving imports (#6126)
## Summary

Noticed in #5954: we walk _past_ the root rather than stopping _at_ the
root when attempting to traverse along the parent path. It's effectively
an off-by-one bug.
2023-07-27 10:22:13 -04:00
konsti
d317af442f Fix windows test warnings (#6124)
See
https://github.com/astral-sh/ruff/actions/runs/5679922286/job/15392998698.
These didn't fail CI because we run clippy on linux only.
2023-07-27 10:22:13 -04:00
Micha Reiser
6bf6646c5d Respect indent when measuring with MeasureMode::AllLines (#6120) 2023-07-27 10:22:13 -04:00
konsti
9574ff3dc7 Unbreak main (#6123)
This fixes main breaking due to two merges.
2023-07-27 10:22:13 -04:00
konsti
06d9ff9577 Don't format trailing comma for lambda arguments (#5946)
**Summary** lambda arguments don't have parentheses, so they shouldn't
get a magic trailing comma either. This fixes some unstable formatting

**Test Plan** Added a regression test.

89 (from previously 145) instances of unstable formatting remaining.

```
$ cargo run --bin ruff_dev --release -- format-dev --stability-check --error-file formatter-ecosystem-errors.txt --multi-project target/checkouts > formatter-ecosystem-progress.txt
$ rg "Unstable formatting" target/formatter-ecosystem-errors.txt | wc -l
89
```

Closes #5892
2023-07-27 10:22:13 -04:00
Micha Reiser
40f54375cb Pull in RustPython parser (#6099) 2023-07-27 09:29:11 +00:00
Victor Hugo Gomes
86539c1fc5 [flake8-pyi] Implement PYI046 (#6098)
## Summary
Checks for the presence of unused private `typing.Protocol` definitions.

ref #848 

## Test Plan

Snapshots and manual runs of flake8.
2023-07-27 02:34:56 +00:00
rembridge
d04367a042 call-datetime-without-tzinfo comment (#6105)
## Summary

Updated doc comment for `call_datetime_without_tzinfo.rs`. Online docs
also benefit from this update.

## Test Plan

Checked docs via
[mkdocs](389fe13c93/CONTRIBUTING.md (L267-L296))
2023-07-26 23:21:03 +00:00
Simon Brugman
ffdd653c54 [flake8-use-pathlib] Implement glob (PTH207) (#5939)
Discovered that the usage of `glob.glob` is
[widespread](https://grep.app/search?current=7&q=glob.glob%28&filter%5Blang%5D%5B0%5D=Python)
when working on the previous lints for `flake8-use-pathlib`.
2023-07-26 23:15:05 +00:00
rembridge
132f07c27b whitespace-before-parameters comment (#6103) 2023-07-26 23:01:47 +00:00
Victor Hugo Gomes
c0dbcb3434 [flake8-pyi] Implement PYI018 (#6018)
## Summary

Check for unused private `TypeVar`. See [original
implementation](2a86db8271/pyi.py (L1958)).

```
$ flake8 --select Y018 crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi

crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi:4:1: Y018 TypeVar "_T" is not used
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi:5:1: Y018 TypeVar "_P" is not used
```

```
$ ./target/debug/ruff --select PYI018 crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi --no-cache

crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi:4:1: PYI018 TypeVar `_T` is never used
crates/ruff/resources/test/fixtures/flake8_pyi/PYI018.pyi:5:1: PYI018 TypeVar `_P` is never used
Found 2 errors.
```
In the file `unused_private_type_declaration.rs`, I'm planning to add
other rules that are similar to `PYI018` like the `PYI046`, `PYI047` and
`PYI049`.

ref #848

## Test Plan

Snapshots and manual runs of flake8.
2023-07-26 22:56:15 +00:00
Victor Hugo Gomes
788643f718 Add "--select E402" to example snippet in CONTRIBUTING.md (#6108)
## Summary
In Ruff only a subset of rules are enabled by default. This change
change aims to clarify that when adding a new rule, you must explicitly
use the `--select name_of_rule` command to ensure the rule gets
executed.

This was talked about on Discord a while back.

## Test Plan
Checked docs via mkdocs
2023-07-26 22:48:53 +00:00
Charlie Marsh
64a186272f Move utf8-encoding-declaration to token-based rules (#6110)
Closes #5979.
2023-07-26 22:42:37 +00:00
Charlie Marsh
8113615534 Add some additional documentation around import categorization (#6107)
Closes https://github.com/astral-sh/ruff/issues/5529.
2023-07-26 22:39:01 +00:00
konsti
ecf4058e52 Fix cargo test -p ruff (#6104) 2023-07-26 22:44:53 +02:00
Zanie Blue
2d2673f613 Add comment regarding class scope short circuit (#6101) 2023-07-26 14:55:05 -05:00
Harutaka Kawamura
564304eba2 Add PT001 documentation (#6023) 2023-07-26 18:05:25 +00:00
Harutaka Kawamura
5b8fc753ec Add PT024 documentation (#6026) 2023-07-26 13:48:37 -04:00
konsti
13f9a16e33 Rewrite placement logic (#6040)
## Summary
This is a rewrite of the main comment placement logic. `place_comment`
now has three parts:

- place own line comments
  - between branches
  - after a branch
- place end-of-line comments
  - after colon
  - after a branch
- place comments for specific nodes (that include module level comments)

The rewrite fixed three bugs: `class A: # trailing comment` comments now
stay end-of-line, `try: # comment` remains end-of-line and deeply
indented try-else-finally comments remain with the right nested
statement.

It will be much easier to give more alternative branches nodes since
this is abstracted away by `is_node_with_body` and the first/last child
helpers. Adding new node types can now be done by adding an entry to the
`place_comment` match. The code went from 1526 lines before #6033 to
1213 lines now.

It thinks it easier to just read the new `placement.rs` rather than
reviewing the diff.

## Test Plan

The existing fixtures staying the same or improving plus new ones for
the bug fixes.
2023-07-26 16:21:23 +00:00
Micha Reiser
2cf00fee96 Remove parser dependency from ruff-python-ast (#6096) 2023-07-26 17:47:22 +02:00
Harutaka Kawamura
99127243f4 Raise PTH201 for Path("") (#6095) 2023-07-26 09:22:46 -04:00
Harutaka Kawamura
77396c6f92 Fix SIM102 to handle indented elif (#6072)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

The `SIM102` auto-fix fails if `elif` is indented like this:

## Example

```python
def f():
    # SIM102
    if a:
        pass
    elif b:
        if c:
            d
```

```
> cargo run -p ruff_cli -- check --select SIM102 --fix a.py
...
error: Failed to fix nested if: Failed to extract statement from source
a.py:5:5: SIM102 Use a single `if` statement instead of nested `if` statements
Found 1 error.
```

## Test Plan

<!-- How was it tested? -->

New test
2023-07-26 14:37:32 +02:00
Micha Reiser
16e1737d1b Use cursor based lexer (#6012) 2023-07-26 11:32:26 +02:00
Dhruv Manilawala
025fa4eba8 Integrate the new Jupyter AST nodes in Ruff (#6086)
## Summary

This PR adds the implementation for the new Jupyter AST nodes i.e.,
`ExprLineMagic` and `StmtLineMagic`.

## Test Plan

Add test cases for `unparse` containing magic commands

resolves: #6087
2023-07-26 08:20:30 +00:00
Micha Reiser
1fdadee59c playground: Persist source and panel (#6071) 2023-07-26 07:55:59 +02:00
Charlie Marsh
c8ee357613 Remove relative import handling from BindingKind::Import case (#6084)
## Summary

Only `ImportFrom` imports can be relative, this is just unused.
2023-07-26 00:17:41 -04:00
Harutaka Kawamura
96d2ca0bda Allow pytest.raises body to contain a single func or class definition (#6083) 2023-07-25 23:45:57 -04:00
Harutaka Kawamura
62f821daaa Avoid raising PT012 for simple with statements (#6081) 2023-07-26 01:43:31 +00:00
Noah Jenner
9dfe484472 Modify PyPA classifiers and Shields.io badge URLs (#6082)
## Summary

Updated `pyproject.toml` classifiers from `"Development Status :: 4 -
Beta"` to `"Development Status :: 5 - Production/Stable"` to reflect the
transition from Beta to Full Release.
Updated the `README.md` to use `.com/astral-sh/ruff/...` instead of
`.com/charliermarsh/ruff/...` in Shields.io badges to reflect the
transition to a company.

## Test Plan

Utilized the official PyPA classifiers list (located at:
https://pypi.org/classifiers/)
Previewed the markdown file in different browsers on Github to ensure
all badges and logos still render properly.
2023-07-26 01:25:46 +00:00
Tom Kuson
da33c26238 Ignore explicit-string-concatenation on single line (#6028)
## Summary

Ignore `explicit-string-concatenation` on single line.

Closes #5332.

## Test Plan

`cargo test`
2023-07-25 19:20:29 -04:00
rembridge
8c80bfa7da tab indentation comment (#6079)
## Summary

Updated doc comment for `tab_indentation.rs`. Online docs also benefit
from this update.

## Test Plan

Checked docs via
[mkdocs](389fe13c93/CONTRIBUTING.md (L267-L296))
2023-07-25 23:14:43 +00:00
Zanie Blue
389fe13c93 Implement visitation of type aliases and parameters (#5927)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Part of #5062 
Requires https://github.com/astral-sh/RustPython-Parser/pull/32

Adds visitation of type alias statements and type parameters in class
and function definitions.

Duplicates tests for `PreorderVisitor` into `Visitor` with new
snapshots. Testing required node implementations for the `TypeParam`
enum, which is a chunk of the diff and the reason we need `Ranged`
implementations in
https://github.com/astral-sh/RustPython-Parser/pull/32.

## Test Plan

<!-- How was it tested? -->

Adds unit tests with snapshots.
2023-07-25 17:11:26 +00:00
Zanie Blue
3000a47fe8 Include file permissions in key for cached files (#5901)
Reimplements https://github.com/astral-sh/ruff/pull/3104
Closes https://github.com/astral-sh/ruff/issues/5726

Note that we will generate the hash for a cache key twice in normal
operation. Once to check for the cached item and again to update the
cache. We could optimize this by generating the hash once in
`diagnostics::lint_file` and passing the `u64` into `get` and `update`.
We'd probably want to wrap it in a `CacheKeyHash` enum for type safety.

## Test plan

Unit tests for Windows and Unix.

Manual test with case from issue

```
❯ touch fake.py
❯ chmod +x fake.py
❯ ./target/debug/ruff --select EXE fake.py
fake.py:1:1: EXE002 The file is executable but no shebang is present
Found 1 error.
❯ chmod -x fake.py
❯ ./target/debug/ruff --select EXE fake.py
```
2023-07-25 17:06:47 +00:00
Charlie Marsh
cbf6085375 Fix example in D413 documentation (#6075)
See #6037.
2023-07-25 12:22:11 -04:00
Charlie Marsh
9171bd4c28 Avoid A003 violations for explicitly overridden methods (#6076)
## Summary

If a method is annotated with `@typing_extensions.override`, we should
avoid flagging A003 on it. This isn't part of the standard library yet,
but it's used to explicitly mark methods as overrides.
2023-07-25 16:21:23 +00:00
Chris Pryer
f5c69c1b34 Update ArgumentsParentheses usage (#6070) 2023-07-25 18:03:48 +02:00
Charlie Marsh
5f63b8bfb8 Ignore some common builtin overrides on standard library subclasses (#6074)
## Summary

If a user subclasses `threading.Event`, e.g. with:

```python
from threading import Event


class CustomEvent(Event):
    def set(self) -> None:
        ...
```

They no control over the method name (`set`). This PR allows
`threading.Event#set` and `logging.Filter#filter` overrides, and avoids
flagging A003 in such cases. Ideally, we'd avoid flagging all overridden
methods, but... that's a lot more difficult, and this is at least
_better_ than what we do now.

Closes https://github.com/astral-sh/ruff/issues/6057.

Closes https://github.com/astral-sh/ruff/issues/5956.
2023-07-25 15:54:34 +00:00
Charlie Marsh
c996b614fe Set default max-complexity to 10 for empty McCabe settings (#6073)
Closes https://github.com/astral-sh/ruff/issues/6058.
2023-07-25 15:38:19 +00:00
Ville Skyttä
670db1db4b pycodestyle.max-doc-length doc updates (#6052) 2023-07-25 15:34:26 +00:00
Charlie Marsh
242cbd966d Perform lint rule analysis after subtree traversal (#6045)
## Summary

This PR modifies the order of operations in our AST checker. Previously,
we ran our analysis rules first, then bound names and traversed over the
subtrees. Now, after a series of refactors, we can invert the order: do
the subtree traversal and model-building _first_, then run rules.

The nice thing about this change is that when we go to analyze, e.g., a
function call node, we'll already have traversed any of the constituent
`Expr::Name` nodes... So if we store the resolution of all names when do
the traversal, we can avoid having to do any expensive work in
`resolve_call_path`.

## Test Plan

Clean run of the snapshot tests, and hopefully the ecosystem checks too!
2023-07-25 09:05:44 -04:00
konsti
e7f228f781 Placement refactor (#6034)
## Summary

This PR is a refactoring of placement.rs. The code got more consistent,
some comments were updated and some dead code was removed or replaced
with debug assertions. It also contains a bugfix for the placement of
end-of-branch comments with nested bodies inside try statements that
occurred when refactoring the nested body loop.

## Test Plan

The existing test cases don't change. I added a couple of cases that i
think should be tested but weren't, and a regression test for the bugfix
2023-07-25 11:49:05 +02:00
Paul Mairo
51d8fc1f30 Update contributing.md with where to run ruff from (#6048)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

As of right now, the instructions don't specify where to run ruff from
after cloning the repository this is to address that. Super trivial
change, but helpful for real newbies I think.
2023-07-24 19:44:55 -04:00
Charlie Marsh
ed72c027a3 Replace NoHashHasher usages with FxHashMap (#6049)
## Summary

I had always assumed that `NoHashHasher` would be faster when using
integer keys, but benchmarking shows otherwise:

```
linter/default-rules/numpy/globals.py
                        time:   [66.544 µs 66.606 µs 66.678 µs]
                        thrpt:  [44.253 MiB/s 44.300 MiB/s 44.342 MiB/s]
                 change:
                        time:   [-0.1843% +0.1087% +0.3718%] (p = 0.46 > 0.05)
                        thrpt:  [-0.3704% -0.1086% +0.1847%]
                        No change in performance detected.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high mild
linter/default-rules/pydantic/types.py
                        time:   [1.3787 ms 1.3811 ms 1.3837 ms]
                        thrpt:  [18.431 MiB/s 18.466 MiB/s 18.498 MiB/s]
                 change:
                        time:   [-0.4827% -0.1074% +0.1927%] (p = 0.56 > 0.05)
                        thrpt:  [-0.1924% +0.1075% +0.4850%]
                        No change in performance detected.
linter/default-rules/numpy/ctypeslib.py
                        time:   [624.82 µs 625.96 µs 627.17 µs]
                        thrpt:  [26.550 MiB/s 26.601 MiB/s 26.650 MiB/s]
                 change:
                        time:   [-0.7071% -0.4908% -0.2736%] (p = 0.00 < 0.05)
                        thrpt:  [+0.2744% +0.4932% +0.7122%]
                        Change within noise threshold.
linter/default-rules/large/dataset.py
                        time:   [3.1585 ms 3.1634 ms 3.1685 ms]
                        thrpt:  [12.840 MiB/s 12.861 MiB/s 12.880 MiB/s]
                 change:
                        time:   [-1.5338% -1.3463% -1.1476%] (p = 0.00 < 0.05)
                        thrpt:  [+1.1610% +1.3647% +1.5577%]
                        Performance has improved.

linter/all-rules/numpy/globals.py
                        time:   [140.17 µs 140.37 µs 140.58 µs]
                        thrpt:  [20.989 MiB/s 21.020 MiB/s 21.051 MiB/s]
                 change:
                        time:   [-0.1066% +0.3140% +0.7479%] (p = 0.14 > 0.05)
                        thrpt:  [-0.7423% -0.3130% +0.1067%]
                        No change in performance detected.
Found 3 outliers among 100 measurements (3.00%)
  2 (2.00%) high mild
  1 (1.00%) high severe
linter/all-rules/pydantic/types.py
                        time:   [2.7030 ms 2.7069 ms 2.7112 ms]
                        thrpt:  [9.4064 MiB/s 9.4216 MiB/s 9.4351 MiB/s]
                 change:
                        time:   [-0.6721% -0.4874% -0.2974%] (p = 0.00 < 0.05)
                        thrpt:  [+0.2982% +0.4898% +0.6766%]
                        Change within noise threshold.
Found 14 outliers among 100 measurements (14.00%)
  12 (12.00%) high mild
  2 (2.00%) high severe
linter/all-rules/numpy/ctypeslib.py
                        time:   [1.4709 ms 1.4727 ms 1.4749 ms]
                        thrpt:  [11.290 MiB/s 11.306 MiB/s 11.320 MiB/s]
                 change:
                        time:   [-1.1617% -0.9766% -0.8094%] (p = 0.00 < 0.05)
                        thrpt:  [+0.8160% +0.9862% +1.1754%]
                        Change within noise threshold.
Found 12 outliers among 100 measurements (12.00%)
  9 (9.00%) high mild
  3 (3.00%) high severe
linter/all-rules/large/dataset.py
                        time:   [5.8086 ms 5.8163 ms 5.8240 ms]
                        thrpt:  [6.9854 MiB/s 6.9946 MiB/s 7.0038 MiB/s]
                 change:
                        time:   [-1.5651% -1.3536% -1.1584%] (p = 0.00 < 0.05)
                        thrpt:  [+1.1720% +1.3721% +1.5900%]
                        Performance has improved.
```

My guess is that `NoHashHasher` underperforms because the keys are not
randomly distributed...

Anyway, it's a ~1% (significant) performance gain on some of the above,
plus we get to remove a dependency.
2023-07-24 23:41:57 +00:00
Charlie Marsh
b7e7346081 Remove empty newline in deferred_for_loops (#6046)
Trivial change but none of the others have this empty newline.
2023-07-24 21:59:32 +00:00
Charlie Marsh
d35b5248ea Tweak lambda rule to use annotations rather than shadowing (#6044)
## Summary

This PR ensures that we can retain the current behavior even after we
reorder the visitor a bit, by looking for annotated lambdas rather than
"is the name bound to anything?", since if we visit the name before we
run this rule, it'll _always_ be bound. (This check is already a bit
flawed -- in truth, we should probably run this rule deferred so that we
can reliably detect shadowing.)
2023-07-24 21:39:02 +00:00
Charlie Marsh
c535e10fff Move comprehension rules into shared analyze method (#6042) 2023-07-24 21:18:45 +00:00
Charlie Marsh
c3ecdb8783 Fix Arg typo (#6041) 2023-07-24 21:16:28 +00:00
Charlie Marsh
242df67cbf Move lint rules out of checkers/ast/mod.rs (#5957)
## Summary

This PR attempts to draw some basic separation between the `Checker`'s
traversal responsibilities (traversing the AST, building the semantic
model) and its calling-out-to-lint-rule responsibilities. It doesn't try
to introduce any sophisticated API. Instead, it just moves all of the
lint rule calls out of `checkers/ast/mod.rs` and into methods in a new
`analyze` module. (There are four remaining lint rules in `Checker`, but
I'll remove those in future PRs.)

I'm not trying to "solve" our lint rule API here. Instead, I'm trying to
make two improvements:

1. `checkers/ast/mod.rs` has just gotten way too large, and people work
in it all the time. Prior to this PR, it was 5.5k lines, which led to
significant lags in my editor and made it really hard to reason about
the parts that are _actually_ important. (I like big files, but this one
crossed the line for me.) Now, it's < 2,000 lines, and the code is much
more focused.
2. I want to avoid accidentally adding lint rules in the "wrong" parts
of the traversal. By confining lint rule invocations to these "analyze"
calls, we'll avoid (e.g.) putting them in the binding phase.
2023-07-24 19:20:10 +00:00
Charlie Marsh
776d598738 Move flake8-executable rules out of physical lines checker (#6039)
## Summary

These only need the token stream, and we always prefer token-based to
physical line-based rules.

There are a few other changes snuck in here:

- Renaming the rule files to match the diagnostic names (likely an
error).
- The "leading whitespace before shebang" rule now works regardless of
where the comment occurs (i.e., if the shebang is on the second line,
and the first line is blank, we flag and remove that leading
whitespace).
2023-07-24 14:38:05 -04:00
konsti
7f3797185c Fix formatter with-statement after-as own line comment instability (#6033)
**Summary** Fix an instability in with statement formatter when there is
an own line comment as the `as`
```python
with (
    a as
    # bad comment
    b):
```

**Test Plan** Added the comment to the test cases.
2023-07-24 18:12:07 +00:00
konsti
a9f535997d Document formatter progress scripts (#6035)
## Summary

Add documentation to the formatter progress scripts

## Test Plan

n/a
2023-07-24 19:42:20 +02:00
Micha Reiser
fdb3c8852f Prefer breaking the implicit string concatenation over breaking before % (#5947) 2023-07-24 18:30:42 +02:00
Charlie Marsh
42d969f19f Add additional test cases for F823 (#6036)
Making some behavior explicit / codified. See:
https://github.com/astral-sh/ruff/issues/6029.
2023-07-24 15:49:48 +00:00
Charlie Marsh
62ffc773de Avoid treating Literal members as expressions with __future__ (#6032)
Closes https://github.com/astral-sh/ruff/issues/6030.
2023-07-24 15:09:37 +00:00
Charlie Marsh
6feb3fcc1b Ignore end-of-line comments when dirtying if-with-same-arms branches (#6031)
## Summary

Closes https://github.com/astral-sh/ruff/issues/6025 (which contains a
more thorough description of the issue). Previously, the `# noqa` here
was being marked as unused, but removing it raised `SIM114`:

```python
def foo():
    a = True
    b = False
    if a > b:  # noqa: SIM114
        return 3
    elif a == b:
        return 3
```
2023-07-24 10:59:58 -04:00
Chris Pryer
8eadacda33 Update TupleParentheses usage (#5810) 2023-07-24 14:44:36 +00:00
konsti
8a7dcb794b Add formatter progress tracking to CI (#5919)
**Summary** Add a formatter progress testing script to CI. This script
will 1) print the black compability on each run 2) catch regressions wrt
to formatter stability, emitting invalid syntax and other kinds of bugs
(e.g. #5917) before they land on main 3) have an additional layer of
real world tests when implementing new nodes or other new formatter
code.

This is currently a bash script, i'm not sure if we want to keep it that
way, or switch to e.g. the regular ecosystem scripts. The output
separation of `format_dev` could also use some polishing. We should also
consider pinning commits so we don't get spurious regression when they
change their code.

**Test Plan** The script extends CI.
2023-07-24 09:12:42 +00:00
Luc Khai Hai
dfa81b6fe0 Format numeric constants (#5972)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-24 07:04:40 +00:00
Charlie Marsh
33196f1859 Fix logging rules with whitespace around dot (#6022)
## Summary

Attempting to fix, e.g., `logging . warn("Hello World!")` was causing a
syntax error.
2023-07-24 05:14:48 +00:00
Charlie Marsh
0d94337b96 Avoid allocations in SimpleCallArgs (#6021)
## Summary

My intuition is that it's faster to do these checks as-needed rather
than allocation new hash maps and vectors for the arguments. (We
typically only query once anyway.)
2023-07-24 04:55:37 +00:00
Charlie Marsh
f9726af4ef Allow specification of logging.Logger re-exports via logger-objects (#5750)
## Summary

This PR adds a `logger-objects` setting that allows users to mark
specific symbols a `logging.Logger` objects. Currently, if a `logger` is
imported, we only flagged it as a `logging.Logger` if it comes exactly
from the `logging` module or is `flask.current_app.logger`.

This PR allows users to mark specific loggers, like
`logging_setup.logger`, to ensure that they're covered by the
`flake8-logging-format` rules and others.

For example, if you have a module `logging_setup.py` with the following
contents:

```python
import logging

logger = logging.getLogger(__name__)
```

Adding `"logging_setup.logger"` to `logger-objects` will ensure that
`logging_setup.logger` is treated as a `logging.Logger` object when
imported from other modules (e.g., `from logging_setup import logger`).

Closes https://github.com/astral-sh/ruff/issues/5694.
2023-07-24 00:38:20 -04:00
Tom Kuson
727153cf45 [pylint] Impement self-assigning-variable (W0127) (#6015)
## Summary

Implements Pylint rule [`self-assigning-variable`
(`W0127`)](https://pylint.pycqa.org/en/latest/user_guide/messages/warning/self-assigning-variable.html)
as `self-assigning-variable` (`PLW0127`). Includes documentation.
Related to #970.

## Test Plan

`cargo test`
2023-07-24 02:27:09 +00:00
Charlie Marsh
574c0e0105 Use match instead of phf for confusable lookup (#5953)
I don't know whether we want to make this change but here's some data...

Binary size:

- `main`: 30,384
- `charlie/match-phf`: 30,416

llvm-lines:

- `main`: 1,784,148
- `charlie/match-phf`: 1,789,877

llvm-lines and binary size are both unchanged (or, by < 5) when moving
from `u8` to `u32` return types, and even when moving to `char` keys and
values. I didn't expect this, but I'm not very knowledgable on this
topic.

Performance:

```
Confusables/match/src   time:   [4.9102 µs 4.9352 µs 4.9777 µs]
                        change: [+1.7469% +2.2421% +2.8710%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 12 outliers among 100 measurements (12.00%)
  2 (2.00%) low mild
  4 (4.00%) high mild
  6 (6.00%) high severe
Confusables/match-with-skip/src
                        time:   [2.0676 µs 2.0945 µs 2.1317 µs]
                        change: [+0.9384% +1.6000% +2.3920%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 8 outliers among 100 measurements (8.00%)
  3 (3.00%) high mild
  5 (5.00%) high severe
Confusables/phf/src     time:   [31.087 µs 31.188 µs 31.305 µs]
                        change: [+1.9262% +2.2188% +2.5496%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 15 outliers among 100 measurements (15.00%)
  3 (3.00%) low mild
  6 (6.00%) high mild
  6 (6.00%) high severe
Confusables/phf-with-skip/src
                        time:   [2.0470 µs 2.0486 µs 2.0502 µs]
                        change: [-0.3093% -0.1446% +0.0106%] (p = 0.08 > 0.05)
                        No change in performance detected.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) high mild
  2 (2.00%) high severe
```

The `-with-skip` variants add our optimization which first checks
whether the character is ASCII. So `match` is way, way faster than PHF,
but it tends not to matter since almost all source code is ASCII anyway.
2023-07-24 02:23:36 +00:00
Dhruv Manilawala
700c816fd5 Make TRY201 always autofixable (#6008)
## Summary

Make `TRY201` always autofiable.

## Test Plan

1. `cargo test`
2. `cargo insta review`

ref:
https://github.com/astral-sh/ruff/issues/4333#issuecomment-1646359788
2023-07-24 02:23:15 +00:00
Tom Kuson
3b56f6d616 [pylint] Implement subprocess-popen-preexec-fn (W1509) (#5978)
## Summary

Implements Pylint rule [`subprocess-popen-preexec-fn`
(`W1509`)](https://pylint.pycqa.org/en/latest/user_guide/messages/warning/subprocess-popen-preexec-fn.html)
as `subprocess-popen-preexec-fn` (`PLW1509`). Includes documentation.
Related to #970.

## Test Plan

`cargo test`
2023-07-24 02:06:19 +00:00
Harutaka Kawamura
110fa804ff Add PT016 documentation (#6005) 2023-07-23 21:52:48 -04:00
Harutaka Kawamura
2b9c22de0f Add a unit test for python-file-like directory exclusion (#5997) 2023-07-24 01:50:39 +00:00
Harutaka Kawamura
51ebff7e41 Add PT010 doc (#6010) 2023-07-24 01:43:18 +00:00
Dhruv Manilawala
742f615792 Add support for int, float, bool in UP018 (#6013)
## Summary

This pull request add supports for `int`, `float` and `bool` types in
`UP018`
rule to convert empty call to the default value of the type or remove
the call
if a value of the same type is provided as an argument.

## Test Plan

Added tests for `int`, `float` and `bool` types.

Partially resolves #5988
2023-07-23 21:39:43 -04:00
Harutaka Kawamura
95e6258d5d Add PT020 doc (#6011) 2023-07-23 21:37:03 -04:00
Dhruv Manilawala
5dbb4dd823 Update docs for ANN401 (#6009)
Part of #5803
2023-07-23 16:15:04 +00:00
konsti
46f8961292 Formatter: Add EmptyWithDanglingComments helper (#5951)
**Summary** Add a `EmptyWithDanglingComments` format helper that formats
comments inside empty parentheses, brackets or curly braces. Previously,
this was implemented separately, and partially incorrectly, for each use
case.

Empty `()`, `[]` and `{}` are special because there can be dangling
comments, and they can be in
two positions:
```python
x = [  # end-of-line
    # own line
]
```
These comments are dangling because they can't be assigned to any
element inside as they would
in all other cases.

**Test Plan** Added a regression test.

145 (from previously 149) instances of unstable formatting remaining.

```
$ cargo run --bin ruff_dev --release -- format-dev --stability-check --error-file formatter-ecosystem-errors.txt --multi-project target/checkouts > formatter-ecosystem-progress.txt
$ rg "Unstable formatting" target/formatter-ecosystem-errors.txt | wc -l
145
```
2023-07-23 14:32:16 +02:00
Simon Brugman
f886b58c92 [flake8-use-pathlib] Implement os-sep-split (PTH206) (#5936)
Implements
https://github.com/astral-sh/ruff/issues/5905#issuecomment-1644822548

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-07-23 12:22:26 +02:00
Charlie Marsh
057faabcdd Use Flags::intersects rather than Flags::contains (#6007)
## Summary

This is equivalent for a single flag, but I think it's more likely to be
correct when the bitflags are modified -- the primary reason being that
we sometimes define flags as the union of other flags, e.g.:

```rust
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_ANNOTATION.bits();
```

In this case, `flags.contains(Flag::ANNOTATION)` requires that _both_
flags in the union are set, whereas `flags.intersects(Flag::ANNOTATION)`
requires that _at least one_ flag is set.
2023-07-23 02:59:31 +00:00
Charlie Marsh
0bb175f7f6 Store flags rather than ExecutionContext on references (#6006) 2023-07-23 02:54:39 +00:00
Charlie Marsh
4b2ec7d562 Move runtime execution context into add_reference calls (#6003) 2023-07-23 02:37:51 +00:00
Charlie Marsh
4aac801277 Fix context-to-model references in SemanticModel documentation (#6004) 2023-07-23 02:32:23 +00:00
Charlie Marsh
45a24912a6 Remove extra error! call (#6002) 2023-07-23 02:29:06 +00:00
Simon Brugman
3914fcb7ca Extend SIM118 with not in (#5995)
Closes https://github.com/astral-sh/ruff/issues/5989

Tracking issue https://github.com/astral-sh/ruff/issues/1348
2023-07-23 01:46:21 +00:00
Charlie Marsh
6d58b773b1 Use simple text matching for type: ignore detection (#5999)
Closes #5980.
2023-07-23 01:45:28 +00:00
Tom Kuson
e7f5121922 Extends B002 to detect unary prefix decrement operators (#5998)
## Summary

Extends `B002` to detect unary decrement prefix operators.

Closes #5992.

## Test Plan

`cargo test`
2023-07-23 01:40:49 +00:00
Charlie Marsh
1776cbd2e2 Move blanket noqa and blanket type: ignore rules into token-based checker (#5996)
Closes https://github.com/astral-sh/ruff/issues/5981.
2023-07-22 21:22:48 -04:00
Charlie Marsh
71f1643eda Use memchr for invalid-escape-sequence (#5994) 2023-07-22 20:57:36 -04:00
Tom Kuson
74dc137b30 Use find_keyword helper function in more places (#5993)
## Summary

Use the `find_keyword` helper function instead of reimplementing it.

Follows on from #5983 by doing a different search.

## Test Plan

`cargo test`
2023-07-22 20:27:24 -04:00
Harutaka Kawamura
97e31cad2f Fix F507 false positive (#5986)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

F507 should not be raised when the right-hand side value is a non-tuple
object.

```python
'%s' % (1, 2, 3)  # throws
'%s' % [1, 2, 3]  # doesn't throw
'%s' % {1, 2, 3}  # doesn't throw
```
2023-07-22 18:42:44 +00:00
Simon Brugman
ed7d2b8a3d Do not raise SIM105 for non-exceptions (#5985)
Closes https://github.com/astral-sh/ruff/issues/5977

Added a test case from `refurb`
2023-07-22 18:36:46 +00:00
Tom Kuson
c7e4c58181 Use find_keyword helper function (#5983)
## Summary

Use `find_keyword` helper function instead of reimplementing it.

## Test Plan

`cargo test`
2023-07-22 14:09:30 -04:00
Charlie Marsh
6ff566f2c1 Flag [ as an invalid noqa suffix (#5982)
Closes https://github.com/astral-sh/ruff/issues/5960.
2023-07-22 10:16:28 -04:00
Charlie Marsh
32773e8309 Move locator, stylist, and friends better getters (#5968)
## Summary

Rather than exposing these as public fields, use getters, similar to
`semantic()`.
2023-07-22 09:37:24 -04:00
Harutaka Kawamura
050f5953f8 Avoid raising UP032 if format call arguments contain multiline expressions (#5971)
## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Fix a regression introduced by
https://github.com/astral-sh/ruff/pull/5638. A multiline expression
can't be safely inserted into a format field.

### Example

```
> cat a.py
"{}".format(
    [
        1,
        2,
        3,
    ]
)

> cargo run -p ruff_cli -- check a.py --no-cache --select UP032 --fix
    Finished dev [unoptimized + debuginfo] target(s) in 0.07s
     Running `target/debug/ruff check a.py --no-cache --select UP032 --fix`
error: Autofix introduced a syntax error in `a.py` with rule codes UP032: EOL while scanning string literal at byte offset 5
---
f"{[
        1,
        2,
        3,
    ]}"

---
a.py:1:1: UP032 Use f-string instead of `format` call
Found 1 error.
```


## Test Plan

New test cases
2023-07-22 09:37:08 -04:00
Alex Waygood
aba340a177 Fix typo in PYI056 docs (#5973)
The current "use instead" code would correctly be rejected by any type
checker worth its salt ;)
2023-07-22 09:10:38 -04:00
Victor Hugo Gomes
33657d3a1c [flake8-pyi] Implement PYI056 (#5959)
## Summary

Checks that `append`, `extend` and `remove` methods are not called on
`__all__`. See [original
implementation](2a86db8271/pyi.py (L1133-L1138)).

```
$ flake8 --select Y026 crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi

crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:3:1: Y056 Calling ".append()" on "__all__" may not be supported by all type checkers (use += instead)
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:4:1: Y056 Calling ".extend()" on "__all__" may not be supported by all type checkers (use += instead)
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:5:1: Y056 Calling ".remove()" on "__all__" may not be supported by all type checkers (use += instead)
```

```
$ ./target/debug/ruff --select PYI026 crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi --no-cache

crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:3:1: PYI056 Calling ".append()" on "__all__" may not be supported by all type checkers (use += instead)
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:4:1: PYI056 Calling ".extend()" on "__all__" may not be supported by all type checkers (use += instead)
crates/ruff/resources/test/fixtures/flake8_pyi/PYI056.pyi:5:1: PYI056 Calling ".remove()" on "__all__" may not be supported by all type checkers (use += instead)
Found 3 errors.
```

ref #848

## Test Plan

Snapshots and manual runs of flake8.
2023-07-22 04:25:54 +00:00
Charlie Marsh
45318d08b7 Always compute runtime annotations for flake8-type-checking rules (#5967)
## Summary

These are skipped as an optimization, but it feels kind of unnecessary
and makes the code a bit more confusing than is worthwhile.
(non-`strict` is also by far the more popular setting, and the default.)
2023-07-21 23:53:33 -04:00
Charlie Marsh
86b6a3e1ad Remove nested f-string flag (#5966)
## Summary

Not worth taking up a slot in the semantic model flags.
2023-07-21 22:51:37 -04:00
Charlie Marsh
f5a2fb5b5d Bump version to 0.0.280 (#5965) 2023-07-21 22:36:13 -04:00
Charlie Marsh
94a004ee9c Avoid collapsing elif and else branches during import sorting (#5964)
## Summary

I ran into this in the wild. It looks like Ruff will collapse the `else`
and `elif` branches here (i.e., it doesn't recognize that they're too
independent import blocks):

```python
if "sdist" in cmds:
    _sdist = cmds["sdist"]
elif "setuptools" in sys.modules:
    from setuptools.command.sdist import sdist as _sdist
else:
    from setuptools.command.sdist import sdist as _sdist
    from distutils.command.sdist import sdist as _sdist
```

Likely fallout from the `elif_else_branches` refactor.
2023-07-22 02:18:02 +00:00
Tom Kuson
aaf7f362a1 Create snake_case file if linter is Pylint (#5948)
## Summary

The `add_rule.py` script would create a test case that pointed to a file
that didn't exist when the linter is set to `"pylint"`. This PR fixes
that.

## Test Plan

`python scripts/add_rule.py --name DoTheThing --prefix PL --code C0999
--linter pylint`
2023-07-21 22:13:43 -04:00
Charlie Marsh
2dcd9e2e9c Remove unnecessary check_deferred_assignments (#5963)
## Summary

These rules can just be included in the `check_deferred_scopes`.
2023-07-22 02:08:44 +00:00
Charlie Marsh
40e9884353 Move nonlocal-without-binding out of binding step (#5962) 2023-07-22 01:39:27 +00:00
Tom Kuson
9bbb0a5151 Fix typo in documentation (#5961)
## Summary

Close unclosed inline code block that was causing the text not to render
properly.

## Test Plan

`mkdocs serve`
2023-07-22 01:23:30 +00:00
Charlie Marsh
f1f89f2a7e Bump version to 0.0.279 (#5949) 2023-07-21 15:46:53 -04:00
konsti
196cc9b655 Fix RustPython rev to main branch (#5950)
**Summary** I accidentally merged earlier while the RustPython parser
rev was still pointing to the feature branch instead of to the merged
main. This make the rev point to the RustPython parser repo main again
2023-07-21 15:53:14 +00:00
konsti
972f9a9c15 Fix formatting lambda with empty arguments (#5944)
**Summary** Fix implemented in
https://github.com/astral-sh/RustPython-Parser/pull/35: Previously,
empty lambda arguments (e.g. `lambda: 1`) would get the range of the
entire expression, which leads to incorrect comment placement. Now empty
lambda arguments get an empty range between the `lambda` and the `:`
tokens.

**Test Plan** Added a regression test.

149 instances of unstable formatting remaining.

```
$ cargo run --bin ruff_dev --release -- format-dev --stability-check --error-file formatter-ecosystem-errors.txt --multi-project target/checkouts > formatter-ecosystem-progress.txt
$ rg "Unstable formatting" target/formatter-ecosystem-errors.txt | wc -l
149
```
2023-07-21 15:48:45 +02:00
qdegraaf
519dbdffaa Format ExprYield/ExprYieldFrom (#5921)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-21 12:07:51 +00:00
konsti
c3b506fca6 Add script to shrink all formatter errors (#5943)
**Summary** Add script to shrink all formatter errors: This started as a
fun idea and turned out really useful: This script gives us a single
Python file with all formatter stability errors. I want to keep it
around to occasionally update #5828 so I added it to the git.

**Test Plan** None, this is a helper script
2023-07-21 11:32:35 +02:00
konsti
f6b40a021f Document shrinking script (#5942)
**Summary** Document shrinking script: I thinks it's both in a good
enough state and valuable enough to document it's usage.
2023-07-21 11:32:26 +02:00
konsti
b56e8ad696 Document formatter error shrinking (#5915)
## Summary

**Don't minimize files that don't match in the first place** This adds a
sanity check to the minimizer script that the
input matches the condition (e.g. unstable formatting). Otherwise we run
through all checks with the whole file, which is extremely slow. It's
more reasonable for downstream usage to write an empty string to the
output file instead.
2023-07-21 11:32:12 +02:00
Charlie Marsh
03018896de Port over some fixes from #3747 (#5940) 2023-07-21 03:55:01 +00:00
Charlie Marsh
b3d31025b1 Remove some unnecessary lifetime annotations (#5938) 2023-07-21 02:42:17 +00:00
Dhruv Manilawala
29e5e4e0b5 Allow respect_gitignore when not in a git repo (#5937)
## Summary

Allow `respect_gitignore` even when not in a git repo

## Test Plan

Within the Ruff repository:

1. Renamed `.git` to `.hello-world`
2. Added `test.py` in root folder
3. Added `test.py` to `.gitignore`
4. Ran `cargo run --bin ruff -- check --no-cache --isolated --show-files
.` with
   and without `--respect-gitignore` flag

fixes: #5930
2023-07-20 22:35:08 -04:00
Simon Brugman
f7b156523a [flake8-use-pathlib] extend PTH118 with os.sep (#5935)
Closes https://github.com/astral-sh/ruff/issues/5905

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-07-21 01:36:02 +00:00
Simon Brugman
d62183b07d Add documentation for the pathlib rules (#5815)
Reviving https://github.com/astral-sh/ruff/pull/2348 step by step

Pt 1: docs

Tracking issue: https://github.com/astral-sh/ruff/issues/2646.
2023-07-21 01:02:22 +00:00
Charlie Marsh
5f2014b0b8 Expand RUF015 to include all expression types (#5767)
## Summary

We now allow RUF015 to fix cases like:

```python
list(range(10))[0]
list(x.y)[0]
list(x["y"])[0]
```

Further, we fix generators like:

```python
[i + 1 for i in x][0]
```

By rewriting to `next(iter(i + 1 for i in x))`.

I've retained the special-case that rewrites `[i for i in x][0]` to
`next(iter(x))`.

Closes https://github.com/astral-sh/ruff/issues/5764.
2023-07-20 20:08:08 -04:00
Tom Kuson
4e681070dc Close unclosed code block in documentation (#5934)
## Summary

Closes an unclosed code block such that the rule documentation renders
properly.

## Test Plan

`mkdocs serve -f mkdocs.generated.yml`
2023-07-20 23:18:16 +00:00
Micha Reiser
4759ffc994 Merge changed steps using files_yaml (#5923) 2023-07-20 23:18:13 +02:00
Charlie Marsh
bcec2f0c4c Move undefined-local into a post-model-building pass (#5928)
## Summary

Similar to #5852 and a bunch of related PRs -- trying to move rules that
rely on point-in-time semantic analysis to _after_ the semantic model
building.
2023-07-20 15:34:22 -04:00
qdegraaf
2cde9b8aa6 [flake8-pyi] Implement PYI017 (#5895)
## Summary

Implements `PYI017` or `Y017` from `flake8-pyi` plug-in. Mirrors
[upstream
implementation](ceab86d16b/pyi.py (L1039-L1048)).
It checks for any assignment with more than 1 target or an assignment to
anything other than a name, and raises a violation for these in stub
files.

Couldn't find a clear and concise explanation for why this is to be
avoided and what is preferred for attribute cases like:

```python
a.b = int
```
So welcome some input there, to learn and to finish up the docs.

## Test Plan

Added test cases from upstream plug-in in a fixture (both `.py` and
`.pyi`). Added a few more.

## Issue link

Refers: https://github.com/astral-sh/ruff/issues/848
2023-07-20 16:35:38 +00:00
Charlie Marsh
c948dcc203 Restore redefined-while-unused violations in classes (#5926)
## Summary

This is a regression from a recent refactor whereby we moved these
checks to a deferred pass.

Closes https://github.com/astral-sh/ruff/issues/5918.
2023-07-20 12:10:26 -04:00
Luc Khai Hai
b866cbb33d Improve slice formatting (#5922)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

- Remove space when start of slice is empty
- Treat unary op except `not` as simple expression

## Test Plan

Add some simple tests for unary op expressions in slice

Closes #5673
2023-07-20 15:05:18 +00:00
Micha Reiser
d351761f5d SimpleTokenizer: Fix infinite loop when lexing empty quotes (#5917) 2023-07-20 15:18:35 +02:00
Tom Kuson
ccc6bd5df0 Fix typo in documentation (#5914) 2023-07-20 13:06:28 +02:00
Micha Reiser
eeb8a5fe0a Avoid line break before for in comprehension if outer expression expands (#5912) 2023-07-20 10:07:22 +00:00
konsti
c2b7b46717 Extend shrinking script to also remove tokens and characters (#5898)
This shrinks a good bit more than previously, which was helpful for all
the formatter bugs. fwiw i treat this as a very ad-hoc script since it's
mainly my ecosystem bug processing companion.
2023-07-20 12:02:00 +02:00
Micha Reiser
6fd8574a0b Only run jobs if relevant files changed (#5908) 2023-07-20 10:01:08 +00:00
Micha Reiser
76e9ce6dc0 Fix SimpleTokenizer's backward lexing of # (#5878) 2023-07-20 11:54:18 +02:00
konsti
8c5f8a8aef Formatter: Small RParen refactoring (#5885)
## Summary

A bit more consistency inspired by
https://github.com/astral-sh/ruff/pull/5882#discussion_r1268182403

## Test Plan

Existing tests (refactoring)
2023-07-20 11:30:39 +02:00
konsti
92f471a666 Handle io errors gracefully (#5611)
## Summary

It can happen that we can't read a file (a python file, a jupyter
notebook or pyproject.toml), which needs to be handled and handled
consistently for all file types. Instead of using `Err` or `error!`, we
emit E602 with the io error as message and continue. This PR makes sure
we handle all three cases consistently, emit E602.

I'm not convinced that it should be possible to disable io errors, but
we now handle the regular case consistently and at least print warning
consistently.

I went with `warn!` but i can change them all to `error!`, too.

It also checks the error case when a pyproject.toml is not readable. The
error message is not very helpful, but it's now a bit clearer that
actually ruff itself failed instead vs this being a diagnostic.

## Examples

This is how an Err of `run` looks now:


![image](https://github.com/astral-sh/ruff/assets/6826232/890f7ab2-2309-4b6f-a4b3-67161947cc83)

With an unreadable file and `IOError` disabled:


![image](https://github.com/astral-sh/ruff/assets/6826232/fd3d6959-fa23-4ddf-b2e5-8d6022df54b1)

(we lint zero files but count files before linting not during so we exit
0)

I'm not sure if it should (or if we should take a different path with
manual ExitStatus), but this currently also triggers when `files` is
empty:


![image](https://github.com/astral-sh/ruff/assets/6826232/f7ede301-41b5-4743-97fd-49149f750337)

## Test Plan

Unix only: Create a temporary directory with files with permissions
`000` (not readable by the owner) and run on that directory. Since this
breaks the assumptions of most of the test code (single file, `ruff`
instead of `ruff_cli`), the test code is rather cumbersome and looks a
bit misplaced; i'm happy about suggestions to fit it in closer with the
other tests or streamline it in other ways. I added another test for
when the entire directory is not readable.
2023-07-20 11:30:14 +02:00
Micha Reiser
029fe05a5f Playground: Fix escaped quotes handling (#5906)
Co-authored-by: konsti <konstin@mailbox.org>
2023-07-20 09:25:27 +00:00
Chris Pryer
9e32585cb1 Use dangling_node_comments in lambda formatting (#5903) 2023-07-20 08:52:32 +02:00
Charlie Marsh
fe7505b738 Move undefined deletions into post-model-building pass (#5904)
## Summary

Similar to #5902, but for undefined names in deletions (e.g., `del x`
where `x` is unbound).
2023-07-20 05:14:46 +00:00
Tom Kuson
266e684192 Add flake8-fixme documentation (#5868)
## Summary

Completes documentation for the `flake8-fixme` (`FIX`) ruleset. Related
to #2646.

Tweaks the violation message. For example,

```
FIX001 Line contains FIXME
```

becomes

```
FIX001 Line contains FIXME, consider resolving the issue
```

This is because the previous message was unclear if it was warning
against the use of FIXME tags per se, or the code the FIXME tag was
annotating.


## Test Plan

`cargo test && python scripts/check_docs_formatted.py`
2023-07-20 02:21:55 +00:00
Simon Brugman
4bba0bcab8 [flake8-use-pathlib] Implement os-path-getsize and os-path-get(a|m|c)-time (PTH202-205) (#5835)
Reviving https://github.com/astral-sh/ruff/pull/2348 step by step

Pt 3. implement detection for:
- `os.path.getsize`
- `os.path.getmtime`
- `os.path.getctime`
- `os.path.getatime`
2023-07-20 02:05:13 +00:00
Simon Brugman
d35cb6942f [flake8-use-pathlib] Implement path-constructor-default-argument (PTH201) (#5833)
Reviving https://github.com/astral-sh/ruff/pull/2348 step by step

Pt 2. PTH201: Path Constructor Default Argument

- rule originates from `refurb`:
https://github.com/charliermarsh/ruff/issues/1348
- Using PTH201 rather than FURBXXX to keep all pathlib logic together
2023-07-20 01:50:54 +00:00
Victor Hugo Gomes
a37d91529b [flake8-pyi] Implement PYI026 (#5844)
## Summary
Checks for `typehint.TypeAlias` annotation in type aliases. See
[original
source](https://github.com/PyCQA/flake8-pyi/blob/main/pyi.py#L1085).
```
$ flake8 --select Y026 crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:4:1: Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "NewAny: TypeAlias = Any"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:5:1: Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "OptinalStr: TypeAlias = typing.Optional[str]"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:6:1: Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "Foo: TypeAlias = Literal['foo']"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:7:1: Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "IntOrStr: TypeAlias = int | str"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:8:1: Y026 Use typing_extensions.TypeAlias for type aliases, e.g. "AliasNone: TypeAlias = None"
```

```
$ ./target/debug/ruff --select PYI026 crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi --no-cache
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:4:1: PYI026 Use `typing.TypeAlias` for type aliases in `NewAny`, e.g. "NewAny: typing.TypeAlias = Any"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:5:1: PYI026 Use `typing.TypeAlias` for type aliases in `OptinalStr`, e.g. "OptinalStr: typing.TypeAlias = typing.Optional[str]"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:6:1: PYI026 Use `typing.TypeAlias` for type aliases in `Foo`, e.g. "Foo: typing.TypeAlias = Literal["foo"]"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:7:1: PYI026 Use `typing.TypeAlias` for type aliases in `IntOrStr`, e.g. "IntOrStr: typing.TypeAlias = int | str"
crates/ruff/resources/test/fixtures/flake8_pyi/PYI026.pyi:8:1: PYI026 Use `typing.TypeAlias` for type aliases in `AliasNone`, e.g. "AliasNone: typing.TypeAlias = None"
Found 5 errors.
```

ref: #848 

## Test Plan

Snapshots, manual runs of flake8.
2023-07-20 01:39:55 +00:00
Charlie Marsh
963f240e46 Track unresolved references in the semantic model (#5902)
## Summary

As part of my continued quest to separate semantic model-building from
diagnostic emission, this PR moves our unresolved-reference rules to a
deferred pass. So, rather than emitting diagnostics as we encounter
unresolved references, we now track those unresolved references on the
semantic model (just like resolved references), and after traversal,
emit the relevant rules for any unresolved references.
2023-07-19 18:19:55 -04:00
Tom Kuson
23cde4d1f5 Add known problems to compare-to-empty-string documentation (#5879)
## Summary

Add known problems to `compare-to-empty-string` documentation. Related
to #5873.

Tweaked the example in the documentation to be a tad more concise and
correct (that the rule is most applicable when comparing to a `str`
variable).

## Test Plan

`python scripts/check_docs_formatted.py`
2023-07-19 18:12:27 -04:00
Charlie Marsh
9834c69c98 Remove __all__ enforcement rules out of binding phase (#5897)
## Summary

This PR moves two rules (`invalid-all-format` and `invalid-all-object`)
out of the name-binding phase, and into the dedicated pass over all
bindings that occurs at the end of the `Checker`. This is part of my
continued quest to separate the semantic model-building logic from the
actual rule enforcement.
2023-07-19 21:18:47 +00:00
Zanie Blue
b27f0fa433 Implement any_over_expr for type alias and type params (#5866)
Part of https://github.com/astral-sh/ruff/issues/5062
2023-07-19 16:17:06 -05:00
konsti
a459d8ffc7 Filter off-by-default RUF014 out of schema (#5832)
**Summary** Previously, `RUF014` would be part of ruff.schema.json
depending on whether or not the `unreachable-code` feature was active.
This caused problems for contributors who got unrelated RUF014 changes
when updating the schema without the feature active.

An alternative would be to always add `RUF014`.

**Test plan** `cargo dev generate-all` and `cargo run --bin ruff_dev
--features unreachable-code -- generate-all` now have the same effect.
2023-07-19 21:06:10 +00:00
Charlie Marsh
598549d24e Fix incorrect reference in extend-immutable-calls documentation (#5890) 2023-07-19 19:57:05 +00:00
David Cain
e1d76b60cc Add missing backtick to B034 documentation (#5889)
This is a great rule, but the documentation page shows some wonky
formatting due to a missing backtick. Fix a typo too.

Should fix display on
https://beta.ruff.rs/docs/rules/re-sub-positional-args/

<img width="1160" alt="image"
src="https://github.com/astral-sh/ruff/assets/901169/44bd76ec-9eb9-4290-ba7a-7691a7ea21d4">
2023-07-19 17:25:36 +00:00
Pedro
6f96acfd27 Rename Pynecone to Reflex (#5888)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

They just changed the name to `Reflex`

## Test Plan

Nothing
2023-07-19 18:46:49 +02:00
Micha Reiser
5a4317c688 Remove multithreading from check multiproject (#5884) 2023-07-19 16:18:30 +00:00
Charlie Marsh
5f3da9955a Rename ruff_python_whitespace to ruff_python_trivia (#5886)
## Summary

This crate now contains utilities for dealing with trivia more broadly:
whitespace, newlines, "simple" trivia lexing, etc. So renaming it to
reflect its increased responsibilities.

To avoid conflicts, I've also renamed `Token` and `TokenKind` to
`SimpleToken` and `SimpleTokenKind`.
2023-07-19 11:48:27 -04:00
Charlie Marsh
a75a6de577 Use a boxed slice for Export struct (#5887)
## Summary

The vector of names here is immutable -- we never push to it after
initialization. Boxing reduces the size of the variant from 32 bytes to
24 bytes. (See:
https://nnethercote.github.io/perf-book/type-sizes.html#boxed-slices.)
It doesn't make a difference here, since it's not the largest variant,
but it still seems like a prudent change (and I was considering adding
another field to this variant, though I may no longer do so).
2023-07-19 11:45:04 -04:00
konsti
a227775f62 Type alias stub for formatter (#5880)
**Summary** This replaces the `todo!()` with a type alias stub in the
formatter. I added the tests from
704eb40108/parser/src/parser.rs (L901-L936)
as ruff python formatter tests.

**Test Plan** None, testing is part of the actual implementation
2023-07-19 17:28:07 +02:00
konsti
a51606a10a Handle parentheses when formatting slice expressions (#5882)
**Summary** Fix the formatter crash with `x[(1) :: ]` and related code.

**Problem** For assigning comments in slices in subscripts, we need to
find the positions of the colons to assign comments before and after the
colon to the respective lower/upper/step node (or dangling in that
section). Formatting `x[(1) :: ]` was broken because we were looking for
a `:` after the `1` but didn't consider that there could be a `)`
outside the range of the lower node, which contains just the `1` and no
optional parentheses.

**Solution** Use the simple tokenizer directly and skip all closing
parentheses.

**Test Plan** I added regression tests.

Closes #5733
2023-07-19 15:25:25 +00:00
konsti
63ed7a31e8 Add message to formatter SyntaxError (#5881)
**Summary** Add a static string error message to the formatter syntax
error so we can disambiguate where the syntax error came from

**Test Plan** No fixed tests, we don't expect this to occur, but it
helped with transformers syntax error debugging:

```
Error: Failed to format node

Caused by:
    syntax error: slice first colon token was not a colon
```
2023-07-19 17:15:26 +02:00
Micha Reiser
46a17d11f3 playground: Add AST/Tokens/Formatter panels (#5859) 2023-07-19 14:46:08 +00:00
Micha Reiser
9ed7ceeb0a playground: Add left panel and use brand colors (#5838) 2023-07-19 16:33:32 +02:00
Chris Pryer
9fb8d6e999 Omit tuple parentheses inside comprehensions (#5790) 2023-07-19 12:05:38 +00:00
Chris Pryer
38678142ed Format lambda expression (#5806) 2023-07-19 11:47:56 +00:00
David Szotten
5d68ad9008 Format expr generator exp (#5804) 2023-07-19 13:01:58 +02:00
Micha Reiser
cda90d071c Upgrade cargo insta (#5872) 2023-07-19 12:56:32 +02:00
Dhruv Manilawala
7e6b472c5b Make lint_only aware of the source kind (#5876) 2023-07-19 09:29:35 +05:30
Charlie Marsh
1181d25e5a Move a few more candidate rules to the deferred Binding-only pass (#5853)
## Summary

No behavior change, but this is in theory more efficient, since we can
just iterate over the flat `Binding` vector rather than having to
iterate over binding chains via the `Scope`.
2023-07-19 00:59:02 +00:00
Charlie Marsh
626d8dc2cc Use .as_ref() in lieu of &** (#5874)
I find this less opaque (and often more succinct).
2023-07-19 00:49:13 +00:00
Charlie Marsh
7ffcd93afd Move unused deletion tracking to deferred analysis (#5852)
## Summary

This PR moves the "unused exception" rule out of the visitor and into a
deferred check. When we can base rules solely on the semantic model, we
probably should, as it greatly simplifies the `Checker` itself.
2023-07-18 20:43:12 -04:00
Charlie Marsh
2d505e2b04 Remove suite body tracking from SemanticModel (#5848)
## Summary

The `SemanticModel` currently stores the "body" of a given `Suite`,
along with the current statement index. This is used to support "next
sibling" queries, but we only use this in exactly one place -- the rule
that simplifies constructs like this to `any` or `all`:

```python
for x in y:
    if x == 0:
        return True
return False
```

Instead of tracking the state, we can just do a (slightly more
expensive) traversal, by finding the node within its parent and
returning the next node in the body.

Note that we'll only have to do this extremely rarely -- namely, for
functions that contain something like:

```python
for x in y:
    if x == 0:
        return True
```
2023-07-18 18:58:31 -04:00
Zanie Blue
a93254f026 Implement unparse for type aliases and parameters (#5869)
Part of https://github.com/astral-sh/ruff/issues/5062
2023-07-18 16:25:49 -05:00
Micha Reiser
c577045f2e perf(formatter): Use memchar for faster back tokenization (#5823) 2023-07-18 21:05:55 +00:00
Charlie Marsh
4204fc002d Remove exception-handler lexing from unused-bound-exception fix (#5851)
## Summary

The motivation here is that it will make this rule easier to rewrite as
a deferred check. Right now, we can't run this rule in the deferred
phase, because it depends on the `except_handler` to power its autofix.
Instead of lexing the `except_handler`, we can use the `SimpleTokenizer`
from the formatter, and just lex forwards and backwards.

For context, this rule detects the unused `e` in:

```python
try:
  pass
except ValueError as e:
  pass
```
2023-07-18 18:27:46 +00:00
Zanie Blue
41da52a61b Implement TokenKind for type aliases (#5870)
Part of https://github.com/astral-sh/ruff/issues/5062
2023-07-18 18:21:51 +00:00
Zanie Blue
d5c43a45b3 Implement Comparable for type aliases and parameters (#5865)
Part of https://github.com/astral-sh/ruff/issues/5062
2023-07-18 17:18:14 +00:00
Nikita Sobolev
cdfed3d50e Use relativize_path for noqa warnings (#5867)
Refs https://github.com/astral-sh/ruff/pull/5856
2023-07-18 12:44:32 -04:00
Harutaka Kawamura
68097e34e6 Update UP032 to autofix multi-line triple-quoted string (#5862)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Resolve #5854

## Test Plan

<!-- How was it tested? -->

New test cases

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-07-18 16:40:37 +00:00
Zanie Blue
f47443014e Remove tag information from RustPython-Parser dependency (#5861)
Following https://github.com/astral-sh/RustPython-Parser/pull/27 we now
cherry-pick commits onto our fork instead of rebasing our fork on top of
the upstream which means we do not overwrite history and a tag is not
necessary to preserve the pinned commit.

In the future, we may rewrite the history in our fork. If we do, we
should return to tagging the commits.
2023-07-18 10:48:51 -05:00
Zanie Blue
0eab4b3c22 Implement AnyNode and AnyNodRef for StmtTypeAlias (#5863)
Part of https://github.com/astral-sh/ruff/issues/5062
2023-07-18 10:44:55 -05:00
Charlie Marsh
c868def374 Unroll collect_call_path to speed up common cases (#5792)
## Summary

This PR just naively unrolls `collect_call_path` to handle attribute
resolutions of up to eight segments. In profiling via Instruments, it
seems to be about 4x faster for a very hot code path (4% of total
execution time on `main`, 1% here).

Profiling by running `RAYON_NUM_THREADS=1 cargo instruments -t time
--profile release-debug --time-limit 10000 -p ruff_cli -o
FromSlice.trace -- check crates/ruff/resources/test/cpython --silent -e
--no-cache --select ALL`, and modifying the linter to loop infinitely up
to the specified time (10 seconds) to increase sample size.

Before:

<img width="1792" alt="Screen Shot 2023-07-15 at 5 13 34 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/4a8b0b45-8b67-43e9-af5e-65b326928a8e">

After:

<img width="1792" alt="Screen Shot 2023-07-15 at 8 38 51 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/d8829159-2c79-4a49-ab3c-9e4e86f5b2b1">
2023-07-18 11:29:59 -04:00
konsti
5d41c832ad Formatter: Run generate.py for ElifElseClauses (#5864)
**Summary** This removes the diff for the next user of `generate.py`.
It's effectively a refactoring.

**Test Plan** No functional changes
2023-07-18 17:17:17 +02:00
Nikita Sobolev
0c7c81aa31 Add filename to noqa warnings (#5856)
## Summary

Before:

```
» ruff litestar tests --fix
warning: Invalid `# noqa` directive on line 19: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 65: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 74: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 22: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 66: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 75: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
```

After:

```
» cargo run --bin ruff ../litestar/litestar ../litestar/tests
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/ruff ../litestar/litestar ../litestar/tests`
warning: Detected debug build without --no-cache.
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_bigint.py:19: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_bigint.py:65: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_bigint.py:74: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_uuid.py:22: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_uuid.py:66: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on /Users/sobolev/Desktop/litestar/tests/unit/test_contrib/test_sqlalchemy/models_uuid.py:75: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
```

## Test Plan

I didn't find any existing tests with this warning.

Closes https://github.com/astral-sh/ruff/issues/5855
2023-07-18 14:08:22 +00:00
Micha Reiser
3b32e3a8fe perf(formatter): Improve is_expression_parenthesized performance (#5825) 2023-07-18 15:48:49 +02:00
Charlie Marsh
1aa851796e Add documentation to Checker (#5849)
## Summary

Documents the overall responsibilities along with the various steps in
the data flow.
2023-07-18 07:52:04 -04:00
konsti
730e6b2b4c Refactor StmtIf: Formatter and Linter (#5459)
## Summary

Previously, `StmtIf` was defined recursively as
```rust
pub struct StmtIf {
    pub range: TextRange,
    pub test: Box<Expr>,
    pub body: Vec<Stmt>,
    pub orelse: Vec<Stmt>,
}
```
Every `elif` was represented as an `orelse` with a single `StmtIf`. This
means that this representation couldn't differentiate between
```python
if cond1:
    x = 1
else:
    if cond2:
        x = 2
```
and 
```python
if cond1:
    x = 1
elif cond2:
    x = 2
```
It also makes many checks harder than they need to be because we have to
recurse just to iterate over an entire if-elif-else and because we're
lacking nodes and ranges on the `elif` and `else` branches.

We change the representation to a flat

```rust
pub struct StmtIf {
    pub range: TextRange,
    pub test: Box<Expr>,
    pub body: Vec<Stmt>,
    pub elif_else_clauses: Vec<ElifElseClause>,
}

pub struct ElifElseClause {
    pub range: TextRange,
    pub test: Option<Expr>,
    pub body: Vec<Stmt>,
}
```
where `test: Some(_)` represents an `elif` and `test: None` an else.

This representation is different tradeoff, e.g. we need to allocate the
`Vec<ElifElseClause>`, the `elif`s are now different than the `if`s
(which matters in rules where want to check both `if`s and `elif`s) and
the type system doesn't guarantee that the `test: None` else is actually
last. We're also now a bit more inconsistent since all other `else`,
those from `for`, `while` and `try`, still don't have nodes. With the
new representation some things became easier, e.g. finding the `elif`
token (we can use the start of the `ElifElseClause`) and formatting
comments for if-elif-else (no more dangling comments splitting, we only
have to insert the dangling comment after the colon manually and set
`leading_alternate_branch_comments`, everything else is taken of by
having nodes for each branch and the usual placement.rs fixups).

## Merge Plan

This PR requires coordination between the parser repo and the main ruff
repo. I've split the ruff part, into two stacked PRs which have to be
merged together (only the second one fixes all tests), the first for the
formatter to be reviewed by @michareiser and the second for the linter
to be reviewed by @charliermarsh.

* MH: Review and merge
https://github.com/astral-sh/RustPython-Parser/pull/20
* MH: Review and merge or move later in stack
https://github.com/astral-sh/RustPython-Parser/pull/21
* MH: Review and approve
https://github.com/astral-sh/RustPython-Parser/pull/22
* MH: Review and approve formatter PR
https://github.com/astral-sh/ruff/pull/5459
* CM: Review and approve linter PR
https://github.com/astral-sh/ruff/pull/5460
* Merge linter PR in formatter PR, fix ecosystem checks (ecosystem
checks can't run on the formatter PR and won't run on the linter PR, so
we need to merge them first)
 * Merge https://github.com/astral-sh/RustPython-Parser/pull/22
 * Create tag in the parser, update linter+formatter PR
 * Merge linter+formatter PR https://github.com/astral-sh/ruff/pull/5459

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-18 13:40:15 +02:00
Chris Pryer
167b9356fa Update from join_with example to join_comma_separated (#5843)
## Summary

Originally `join_with` was used in the formatters README.md. Now it uses

```rs
f.join_comma_separated(item.end())
    .nodes(elts.iter())
    .finish()
```

## Test Plan

None
2023-07-18 11:03:16 +02:00
konsti
d098256c96 Add a tool for shrinking failing examples (#5731)
## Summary

For formatter instabilities, the message we get look something like
this:
```text
Unstable formatting /home/konsti/ruff/target/checkouts/deepmodeling:dpdispatcher/dpdispatcher/slurm.py
@@ -47,9 +47,9 @@
-            script_header_dict["slurm_partition_line"] = (
-                NOT_YET_IMPLEMENTED_ExprJoinedStr
-            )
+            script_header_dict[
+                "slurm_partition_line"
+            ] = NOT_YET_IMPLEMENTED_ExprJoinedStr
Unstable formatting /home/konsti/ruff/target/checkouts/deepmodeling:dpdispatcher/dpdispatcher/pbs.py
@@ -26,9 +26,9 @@
-            pbs_script_header_dict["select_node_line"] += (
-                NOT_YET_IMPLEMENTED_ExprJoinedStr
-            )
+            pbs_script_header_dict[
+                "select_node_line"
+            ] += NOT_YET_IMPLEMENTED_ExprJoinedStr
``` 

For ruff crashes. you don't even get that but just the file that crashed
it. To extract the actual bug, you'd need to manually remove parts of
the file, rerun to see if the bug still occurs (and revert if it
doesn't) until you have a minimal example.

With this script, you run

```shell
cargo run --bin ruff_shrinking -- target/checkouts/deepmodeling:dpdispatcher/dpdispatcher/slurm.py target/minirepo/code.py "Unstable formatting" "target/debug/ruff_dev format-dev --stability-check target/minirepo"
```

and get

```python
class Slurm():
    def gen_script_header(self, job):
        if resources.queue_name != "":
            script_header_dict["slurm_partition_line"] = f"#SBATCH --partition {resources.queue_name}"
```

which is an nice minimal example.

I've been using this script and it would be easier for me if this were
part of main. The main disadvantage to merging is that it adds
additional dependencies.

## Test Plan

I've been using this for a number of minimization. This is an internal
helper script you only run manually. I could add a test that minimizes a
rule violation if required.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-18 08:03:35 +00:00
Micha Reiser
ef58287c16 playground: Merge Editor state variables (#5831)
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR removes state variables that can be derived, merges related variables into a single state, and generally avoids `null` states. 

## Test Plan

I clicked through the playground locally
<!-- How was it tested? -->
2023-07-18 08:08:24 +02:00
Micha Reiser
9ddf40455d Upgrade playground dependencies (#5830)
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR upgrades the playground's runtime and dev dependencies

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I tested the playground locally

<!-- How was it tested? -->
2023-07-18 08:00:54 +02:00
Harutaka Kawamura
a4e5e3205f Ignore directories when collecting files to lint (#5775)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->

Fixes #5739

## Test Plan

<!-- How was it tested? -->

Manually tested:

```sh
$ tree dir
dir
├── dir.py
│   └── file.py
└── file.py

1 directory, 2 files

$ cargo run -p ruff_cli -- check dir --no-cache
    Finished dev [unoptimized + debuginfo] target(s) in 0.08s
     Running `target/debug/ruff check dir --no-cache`
dir/dir.py/file.py:1:7: F821 Undefined name `a`
dir/file.py:1:7: F821 Undefined name `a`
Found 2 errors.
```

Is a unit test needed?
2023-07-17 20:25:43 -05:00
Simon Brugman
17ee80363a refactor: use find_keyword ast helper more (#5847)
Use the ast helper function `find_keyword` where applicable

(found these while working on another feature)
2023-07-17 19:37:23 -04:00
David Szotten
52aa2fc875 upgrade rustpython to remove tuple-constants (#5840)
c.f. https://github.com/astral-sh/RustPython-Parser/pull/28

Tests: No snapshots changed

---------

Co-authored-by: Zanie <contact@zanie.dev>
2023-07-17 22:50:31 +00:00
Charlie Marsh
e574a6a769 Add some "Phase" annotations to other visit methods (#5839)
## Summary

Follow-up from #5820.
2023-07-17 14:46:39 -04:00
Charlie Marsh
b9346a4fd6 Draw boundaries between various Checker visitation phases (#5820)
## Summary

This PR does some non-behavior-changing refactoring of the AST checker.
Specifically, it breaks the `Stmt`, `Expr`, and `ExceptHandler` visitors
into four distinct, consistent phases:

1. **Phase 1: Analysis**: Run any lint rules on the node.
2. **Phase 2: Binding**: Bind any symbols declared by the node.
3. **Phase 3: Recursion**: Visit all child nodes.
4. **Phase 4: Clean-up**: Pop scopes, etc.

There are some fuzzy boundaries in the last three phases, but the most
important divide is between the Phase 1 and all the others -- the goal
here is (as much as possible) to disentangle all of the vanilla
lint-rule calls from any other semantic analysis or model building.

Part of the motivation here is that I'm considering re-ordering some of
these phases, and it was just impossible to reason about that change as
long as we had miscellaneous binding-creation and scope-modification
code intermingled with lint rules. However, this could also enable us to
(e.g.) move the entire analysis phase elsewhere, and even with a more
limited API that has read-only access to `Checker` (but can push to a
diagnostics vector).
2023-07-17 13:02:21 -04:00
Charlie Marsh
8001a2f121 Expand convention documentation (#5819) 2023-07-17 14:12:46 +00:00
konsti
7dd30f0270 Read black options in format_dev script (#5827)
## Summary

Comparing repos with black requires that we use the settings as black,
notably line length and magic trailing comma behaviour. Excludes and
preserving quotes (vs. a preference for either quote style) is not yet
implemented because they weren't needed for the test projects.

In the other two commits i fixed the output when the progress bar is
hidden (this way is recommonded in the indicatif docs), added a
`scratch.pyi` file to gitignore because black formats stub files
differently and also updated the ecosystem readme with the projects json
without forks.

## Test Plan

I added a `line-length` vs `line_length` test. Otherwise only my
personal usage atm, a PR to integrate the script into the CI to check
some projects will follow.
2023-07-17 13:29:43 +00:00
Micha Reiser
21063544f7 Fix formatter generate.py (#5829) 2023-07-17 10:41:27 +00:00
Luc Khai Hai
fb336898a5 Format AsyncFor (#5808) 2023-07-17 10:38:59 +02:00
Tom Kuson
f5f8eb31ed Add documentation to the flake8-gettext (INT) rules (#5813)
## Summary

Completes documentation for the `flake8-gettext` (`INT`) ruleset.
Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-07-17 04:09:33 +00:00
Charlie Marsh
be6c744856 Include function name in undocumented-param message (#5818)
Closes #5814.
2023-07-16 22:51:34 -04:00
Charlie Marsh
94998aedef Reduce unnecessary allocations for keyword detection (#5817) 2023-07-17 02:22:30 +00:00
Tom Kuson
1c0376a72d Add documentation to the S5XX rules (#5805)
## Summary

Add documentation to the `S5XX` rules (the `flake8-bandit`
['cryptography'](https://bandit.readthedocs.io/en/latest/plugins/index.html#plugin-id-groupings)
rule group). Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-07-17 02:12:57 +00:00
Simon Brugman
de2a13fcd7 [pandas-vet] series constant series (#5802)
## Summary

Implementation for https://github.com/astral-sh/ruff/issues/5588

Q1: are there any additional semantic helpers that could be used to
guard this rule? Which existing rules should be similar in that respect?
Can we at least check if `pandas` is imported (any pointers welcome)?
Currently, the rule flags:
```python
data = {"a": "b"}
data.nunique() == 1
```

Q2: Any pointers on naming of the rule and selection of the code? It was
proposed, but not replied to/implemented in the upstream. `pandas` did
accept a PR to update their cookbook to reflect this rule though.

## Test Plan

TODO:
- [X] Checking for ecosystem CI results
- [x] Test on selected [real-world
cases](https://github.com/search?q=%22nunique%28%29+%3D%3D+1%22+language%3APython+&type=code)
  - [x] https://github.com/sdv-dev/SDMetrics
  - [x] https://github.com/google-research/robustness_metrics
  - [x] https://github.com/soft-matter/trackpy
  - [x] https://github.com/microsoft/FLAML/
- [ ] Add guarded test cases
2023-07-17 01:55:34 +00:00
Harutaka Kawamura
cfec636046 Do not fix NamedTuple calls containing both a list of fields and keywords (#5799)
## Summary

Fixes #5794

## Test Plan

Existing tests
2023-07-17 01:31:53 +00:00
Tom Kuson
ae431df146 Change pandas-use-of-dot-read-table rule to emit only when read_table is used on CSV data (#5807)
## Summary

Closes #5628 by only emitting if `sep=","`. Includes documentation
(completes the `pandas-vet` ruleset).

Related to #2646.

## Test Plan

`cargo test`
2023-07-17 01:25:13 +00:00
Charlie Marsh
2cd117ba81 Remove TryIdentifier trait (#5816)
## Summary

Last remaining usage here is for patterns, but we now have ranges on
identifiers so it's unnecessary.
2023-07-16 21:24:16 -04:00
Simon Brugman
a956226d95 perf: only compute start offset for overlong lines (#5811)
Moves the computation of the `start_offset` for overlong lines to just
before the result is returned. There is a slight overhead for overlong
lines (double the work for the first `limit` characters).

In practice this results in a speedup on the CPython codebase. Most
lines are not overlong, or are not enforced because the line ends with a
URL, or does not contain whitespace. Nonetheless, the 0.3% of overlong
lines are a lot compared to other violations.

### Before
![selected
before](https://github.com/astral-sh/ruff/assets/9756388/d32047df-7fd2-4ae8-8333-1a3679ce000f)
_Selected W505 and E501_

![all
before](https://github.com/astral-sh/ruff/assets/9756388/98495118-c474-46ff-873c-fb58a78cfe15)
_All rules_

### After
![selected
after](https://github.com/astral-sh/ruff/assets/9756388/e4bd7f10-ff7e-4d52-8267-27cace8c5471)
_Selected W505 and E501_

![all
after](https://github.com/astral-sh/ruff/assets/9756388/573bdbe2-c64f-4f22-9659-c68726ff52c0)
_All rules_

CPython line statistics:
- Number of Python lines: 867.696
- Number of overlong lines: 2.963 (0.3%)

<details>

Benchmark selected:
```shell
cargo build --release && hyperfine --warmup 10 --min-runs 50 \                                                  
  "./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select W505,E501"
```

Benchmark all:
```shell
cargo build --release && hyperfine --warmup 10 --min-runs 50 \                                                  
  "./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select ALL"
```

Overlong lines in CPython

```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/cpython/Lib --no-cache --select=E501,W505 --statistics
```

Total Python lines:
```shell
find crates/ruff/resources/test/cpython/ -name '*.py' | xargs wc -l
```

</details>

(Performance tested on Mac M1)
2023-07-16 21:05:44 -04:00
Chris Pryer
1dd52ad139 Update generate.py comment (#5809)
## Summary

The generated comment is different from the generate files current
comment.

## Test Plan

None
2023-07-16 11:51:30 -04:00
Charlie Marsh
d692ed0896 Use a match statement for builtin detection (#5798)
## Summary

We've seen speed-ups in the past by converting from slice iteration to
match statements; this just does the same for built-in checks.
2023-07-16 04:57:57 +00:00
Charlie Marsh
01b05fe247 Remove Identifier usages for isolating exception names (#5797)
## Summary

The motivating change here is to remove `let range =
except_handler.try_identifier().unwrap();` and instead just do
`name.range()`, since exception names now have ranges attached to them
by the parse. This also required some refactors (which are improvements)
to the built-in attribute shadowing rules, since at least one invocation
relied on passing in the exception handler and calling
`.try_identifier()`. Now that we have easy access to identifiers, we can
remove the whole `AnyShadowing` abstraction.
2023-07-16 04:49:48 +00:00
Charlie Marsh
59dfd0e793 Move except-handler flag into visit_except_handler (#5796)
## Summary

This is more similar to how these flags work in other contexts (e.g.,
`visit_annotation`), and also ensures that we unset it prior to visit
the `orelse` and `finalbody` (a subtle bug).
2023-07-16 00:35:02 -04:00
Charlie Marsh
c7ff743d30 Use semantic().global() to power global-statement rule (#5795)
## Summary

The intent of this rule is to always flag the `global` declaration, not
the usage. The current implementation does the wrong thing if a global
is assigned multiple times. Using `semantic().global()` is also more
efficient.
2023-07-16 00:34:42 -04:00
konsti
b01a4d8446 Update ruff crate descriptions (#5710)
## Summary

I updated all ruff crate descriptions in the contributing guide

## Test Plan

n/a
2023-07-16 02:41:47 +00:00
Justin Prieto
f012ed2d77 Add autofix for B004 (#5788)
## Summary

Adds autofix for `hasattr` case of B004. I don't think it's safe (or
simple) to implement it for the `getattr` case because, inter alia,
calling `getattr` may have side effects.

Fixes #3545

## Test Plan

Existing tests were sufficient. Updated snapshots
2023-07-16 01:32:21 +00:00
Charlie Marsh
06b5c6c06f Use SmallVec#extend_from_slice in lieu of SmallVec#extend (#5793)
## Summary

There's a note in the docs that suggests this can be faster, and in the
benchmarks it... seems like it is? Might just be noise but held up over
a few runs.

Before:

<img width="1792" alt="Screen Shot 2023-07-15 at 9 10 06 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/973cd955-d4e6-4ae3-898e-90b7eb52ecf2">

After:

<img width="1792" alt="Screen Shot 2023-07-15 at 9 10 09 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/1491b391-d219-48e9-aa47-110bc7dc7f90">
2023-07-15 21:25:12 -04:00
Charlie Marsh
4782675bf9 Remove lexer-based comment range detection (#5785)
## Summary

I'm doing some unrelated profiling, and I noticed that this method is
actually measurable on the CPython benchmark -- it's > 1% of execution
time. We don't need to lex here, we already know the ranges of all
comments, so we can just do a simple binary search for overlap, which
brings the method down to 0%.

## Test Plan

`cargo test`
2023-07-16 01:03:27 +00:00
Charlie Marsh
f2e995f78d Gate runtime-import-in-type-checking-block (TCH004) behind enabled flag (#5789)
Closes #5787.
2023-07-15 20:57:29 +00:00
guillaumeLepape
6824b67f44 Include alias when formatting import-from structs (#5786)
## Summary

When required-imports is set with the syntax from ... import ... as ...,
autofix I002 is failing

## Test Plan

Reuse the same python files as
`crates/ruff/src/rules/isort/mod.rs:required_import` test.
2023-07-15 15:53:21 -04:00
Charlie Marsh
8ccd697020 Expand scope of quoted-annotation rule (#5766)
## Summary

Previously, the `quoted-annotation` rule only removed quotes when `from
__future__ import annotations` was present. However, there are some
other cases in which this is also safe -- for example:

```python
def foo():
    x: "MyClass"
```

We already model these in the semantic model, so this PR just expands
the scope of the rule to handle those.
2023-07-15 15:37:34 -04:00
Charlie Marsh
2de6f30929 Lift Expr::Subscript value visit out of branches (#5783)
Like #5772, but for subscripts.
2023-07-15 15:12:15 -04:00
Micha Reiser
df2efe81c8 Respect magic trailing comma for set expression (#5782)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR uses the `join_comma_separated` builder for formatting set
expressions
to ensure the formatting preserves magic commas, if the setting is
enabled.
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan
See the fixed black tests

<!-- How was it tested? -->
2023-07-15 16:40:38 +00:00
Chris Pryer
fa4855e6fe Format DictComp expression (#5771)
## Summary

Format `DictComp` like `ListComp` from #5600. It's not 100%, but I
figured maybe it's worth starting to explore.

## Test Plan

Added ruff fixture based on `ListComp`'s.
2023-07-15 17:35:23 +01:00
Micha Reiser
3cda89ecaf Parenthesize with statements (#5758)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR improves the parentheses handling for with items to get closer
to black's formatting.

### Case 1:

```python
# Black / Input
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    aaaaaaaaaaaaaaaaaaaaaaaaaa
    + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
    + cccccccccccccccccccccccccccc
    + ddddddddddddddddd as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
):
    ...

# Before
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    (
        aaaaaaaaaaaaaaaaaaaaaaaaaa
        + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
        + cccccccccccccccccccccccccccc
        + ddddddddddddddddd
    ) as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
    CtxManager2() as example2,
):
    ...
```

Notice how Ruff wraps the binary expression in an extra set of
parentheses


### Case 2:
Black does not expand the with-items if the with has no parentheses:

```python
# Black / Input
with aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c:
    ...

# Before
with (
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb as c
):
    ...
```

Or 

```python
# Black / Input
with [
    "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "bbbbbbbbbb",
    "cccccccccccccccccccccccccccccccccccccccccc",
    dddddddddddddddddddddddddddddddd,
] as example1, aaaaaaaaaaaaaaaaaaaaaaaaaa * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb * cccccccccccccccccccccccccccc + ddddddddddddddddd as example2, CtxManager222222222222222() as example2:
    ...

# Before (Same as Case 1)
with (
    [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbb",
        "cccccccccccccccccccccccccccccccccccccccccc",
        dddddddddddddddddddddddddddddddd,
    ] as example1,
    (
        aaaaaaaaaaaaaaaaaaaaaaaaaa
        * bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
        * cccccccccccccccccccccccccccc
        + ddddddddddddddddd
    ) as example2,
    CtxManager222222222222222() as example2,
):
    ...

```
## Test Plan

I added new snapshot tests

Improves the django similarity index from 0.973 to 0.977
2023-07-15 16:03:09 +01:00
Luc Khai Hai
e1c119fde3 Format SetComp (#5774)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Format `SetComp` like `ListComp`.

## Test Plan

Derived from `ListComp`'s fixture.
2023-07-15 15:50:47 +01:00
Harutaka Kawamura
daa4b72d5f [B006] Add bytes to immutable types (#5776)
## Summary

`B006` should allow using `bytes(...)` as an argument defaule value.

## Test Plan

A new test case

---------

Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2023-07-15 13:04:33 +00:00
Charlie Marsh
f029f8b784 Move function visit out of Expr::Call branches (#5772)
## Summary

Non-behavioral change, but this is the same in each branch. Visiting the
`func` first also means we've visited the `func` by the time we try to
resolve it (via `resolve_call_path`), which should be helpful in a
future refactor.
2023-07-15 03:36:19 +00:00
Charlie Marsh
bf248ede93 Handle name nodes prior to running rules (#5770)
## Summary

This is more consistent with other patterns in the Checker. Shouldn't
change behavior at all.
2023-07-15 02:21:55 +00:00
Charlie Marsh
086f8a3c12 Move lambda visitation into recurse phase (#5769)
## Summary

Similar to #5768: when we analyze a lambda, we need to recurse in the
recurse phase, rather than the pre-visit phase.
2023-07-15 02:11:47 +00:00
Charlie Marsh
3dc73395ea Move Literal flag detection into recurse phase (#5768)
## Summary

The AST pass is broken up into three phases: pre-visit (which includes
analysis), recurse (visit all members), and post-visit (clean-up). We're
not supposed to edit semantic model flags in the pre-visit phase, but it
looks like we were for literal detection. This didn't matter in
practice, but I'm looking into some AST refactors for which this _does_
cause issues.

No behavior changes expected.

## Test Plan

Good test coverage on these.
2023-07-15 02:04:15 +00:00
Charlie Marsh
7c32e98d10 Use unused variable detection to power incorrect-dict-iterator (#5763)
## Summary

`PERF102` looks for unused keys or values in `dict.items()` calls, and
suggests instead using `dict.keys()` or `dict.values()`. Previously,
this check determined usage by looking for underscore-prefixed
variables. However, we can use the semantic model to actually detect
whether a variable is used. This has two nice effects:

1. We avoid odd false-positives whereby underscore-prefixed variables
are actually used.
2. We can catch more cases (fewer false-negatives) by detecting unused
loop variables that _aren't_ underscore-prefixed.

Closes #5692.
2023-07-14 15:42:47 -04:00
Charlie Marsh
81b88dcfb9 Misc. minor refactors to incorrect-dict-iterator (#5762)
## Summary

Mostly a no-op: use a single match for key-value, use identifier range
rather than re-lexing, respect our `dummy-variable-rgx` setting.
2023-07-14 17:29:25 +00:00
Micha Reiser
8187bf9f7e Cover Black's is_aritmetic_like formatting (#5738) 2023-07-14 17:54:58 +02:00
Charlie Marsh
513de13c46 Remove B904's lowercase exemption (#5751)
## Summary

It looks like bugbear, [from the
start](https://github.com/PyCQA/flake8-bugbear/pull/181#issuecomment-904314876),
has had an exemption here to exempt `raise lower_case_var`. I looked at
Hypothesis and Trio, which are mentioned in that issue, and Hypothesis
has exactly one case of this, and Trio has none, so IMO it doesn't seem
worth special-casing.

Closes https://github.com/astral-sh/ruff/issues/5664.
2023-07-14 11:46:21 -04:00
Justin Prieto
816f7644a9 Fix nested calls to sorted with differing arguments (#5761)
## Summary

Nested calls to `sorted` can only be collapsed if the calls are
identical (i.e., they have the exact same keyword arguments).
Update C414 to only flag such cases.

Fixes #5712

## Test Plan

Updated snapshots.
Tested against flake8-comprehensions. It incorrectly flags these cases.
2023-07-14 13:43:47 +00:00
konsti
fb46579d30 Add Regression test for #5605, where formatting x[:,] failed. (#5759)
#5605 has been fixed, i added the failing example from the issue as a
regression test.

Closes #5605
2023-07-14 11:55:05 +02:00
Chris Pryer
a961f75e13 Format assert statement (#5168) 2023-07-14 09:01:33 +02:00
Charlie Marsh
5a4516b812 Misc. stylistic changes from flipping through rules late at night (#5757)
## Summary

This is really bad PR hygiene, but a mix of: using `Locator`-based fixes
in a few places (in lieu of `Generator`-based fixes), using match syntax
to avoid `.len() == 1` checks, using common helpers in more places, etc.

## Test Plan

`cargo test`
2023-07-14 05:23:47 +00:00
Charlie Marsh
875e04e369 Avoid removing raw strings in comparison fixes (#5755)
## Summary

Use `Locator`-based verbatim fix rather than a `Generator`-based fix,
which loses trivia (and raw strings).

Closes https://github.com/astral-sh/ruff/issues/4130.
2023-07-14 04:27:46 +00:00
Charlie Marsh
12489d3305 Minor tweaks to playground color scheme (#5754)
## Summary

I kind of hate the light mode theme, but they now use colors from our
actual palette:

<img width="1792" alt="Screen Shot 2023-07-13 at 10 15 14 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/f1da0153-d6ed-4b65-9419-b824f2cad614">
<img width="1792" alt="Screen Shot 2023-07-13 at 10 15 12 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/d9452e10-796b-4b7f-bf3f-7af6e0b14fc0">
<img width="1792" alt="Screen Shot 2023-07-13 at 10 15 10 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/f75e7c1c-3b5a-4a78-8bb8-d8b4d40a337d">
<img width="1792" alt="Screen Shot 2023-07-13 at 10 15 07 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/52c23108-b9c2-4a1f-adf0-e11098dbdc5d">
2023-07-13 22:37:18 -04:00
Charlie Marsh
73228e914c Use Ruff favicon for playground (#5752) 2023-07-14 01:11:44 +00:00
Charlie Marsh
af2a087806 Ignore Enum-and-str subclasses for slots enforcement (#5749)
## Summary

Matches the behavior of the upstream plugin.

Closes #5748.
2023-07-13 20:12:16 +00:00
Charlie Marsh
51a313cca4 Avoid stack overflow for non-BitOr binary types (#5743)
## Summary

Closes #5742.
2023-07-13 14:23:40 -04:00
skykasko
48309cad08 Fix the example for blank-line-before-class (D211) (#5746)
The example for
[D211](https://beta.ruff.rs/docs/rules/blank-line-before-class/) is
currently identical to the example for
[D203](https://beta.ruff.rs/docs/rules/one-blank-line-before-class/). It
should be the opposite, with the incorrect case having a blank line
before the class docstring and the correct case having no blank line.
2023-07-13 17:47:01 +00:00
Charlie Marsh
2c2e5b2704 Add some additional Option links to the docs (#5745) 2023-07-13 13:46:17 -04:00
Dhruv Manilawala
5d135d4e0e Update table of content in CONTRIBUTING.md (#5744) 2023-07-13 17:42:28 +00:00
eggplants
06a04c10e2 Fix Options section of rule docs (#5741)
## Summary

Fix: #5740

A trailing line-break are needed for the anchor.

## Test Plan

http://127.0.0.1:8000/docs/rules/line-too-long/#options

|before|after|
|--|--|

|![image](https://github.com/astral-sh/ruff/assets/42153744/8cb9dcce-aeda-4255-b21e-ab11817ba9e1)|![image](https://github.com/astral-sh/ruff/assets/42153744/b68d4fd7-da5a-4494-bb95-f7792f1a42db)|
2023-07-13 17:25:54 +00:00
Charlie Marsh
fee0f43925 Add an overview of Ruff's compilation pipeline to the docs (#5719)
## Summary

I originally wrote this in Notion but it seems preferable to publish it
publicly in the documentation. Feedback welcome!
2023-07-13 16:50:41 +00:00
Justin Prieto
25e491ad6f [flake8-pyi] Implement PYI041 (#5722)
## Summary

Implements PYI041 from flake8-pyi. See [original
code](2a86db8271/pyi.py (L1283)).

This check only applies to function parameters in order to avoid issues
with mypy. See https://github.com/PyCQA/flake8-pyi/issues/299.

ref: #848

## Test Plan

Snapshots, manual runs of flake8.
2023-07-13 16:48:17 +00:00
Charlie Marsh
e7b059cc5c Fix nested lists in CONTRIBUTING.md (#5721)
## Summary

We have a lot of two-space-indented stuff, but apparently it needs to be
four-space indented to render as expected in MkDocs.
2023-07-13 16:32:59 +00:00
Micha Reiser
5dd5ee0c5b Properly group assignment targets (#5728) 2023-07-13 16:00:49 +02:00
konsti
f48ab2d621 Update scripts/ecosystem_all_check.sh (#5737)
## Summary

These changes make `scripts/ecosystem_all_check.sh --select ALL` work
again, i forgot to update this script to the new directory structure
from #5299 because it's only run manually


## Test Plan

n/a
2023-07-13 15:25:22 +02:00
Dhruv Manilawala
cf48ad7b21 Consider single element subscript expr for implicit optional (#5717)
## Summary

Consider single element subscript expr for implicit optional.

On `main`, the cases where there is only a single element in the
subscript
list was giving false positives such as for the following:

```python
typing.Union[None]
typing.Literal[None]
```

## Test Plan

`cargo test`

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-07-13 13:10:07 +00:00
Dhruv Manilawala
f44acc047a Check for Any in other types for ANN401 (#5601)
## Summary

Check for `Any` in other types for `ANN401`. This reuses the logic from
`implicit-optional` rule to resolve the type to `Any`.

Following types are supported:
* `Union[Any, ...]`
* `Any | ...`
* `Optional[Any]`
* `Annotated[<any of the above variant>, ...]`
* Forward references i.e., `"Any | ..."`

## Test Plan

Added test cases for various combinations.

fixes: #5458
2023-07-13 18:19:27 +05:30
Tom Kuson
8420008e79 Avoid checking EXE001 and EXE002 on WSL (#5735)
## Summary

Do not raise `EXE001` and `EXE002` if WSL is detected. Uses the
[`wsl`](https://crates.io/crates/wsl) crate.

Closes #5445.

## Test Plan

`cargo test`

I don't use Windows, so was unable to test on a WSL environment. It
would be good if someone who runs Windows could check the functionality.
2023-07-13 07:36:07 -04:00
Charlie Marsh
932c9a4789 Extend PEP 604 rewrites to support some quoted annotations (#5725)
## Summary

Python doesn't allow `"Foo" | None` if the annotation will be evaluated
at runtime (see the comments in the PR, or the semantic model
documentation for more on what this means and when it is true), but it
_does_ allow it if the annotation is typing-only.

This, for example, is invalid, as Python will evaluate `"Foo" | None` at
runtime in order to
populate the function's `__annotations__`:

```python
def f(x: "Foo" | None): ...
```

This, however, is valid:

```python
def f():
    x: "Foo" | None
```

As is this:

```python
from __future__ import annotations

def f(x: "Foo" | None): ...
```

Closes #5706.
2023-07-13 07:34:04 -04:00
konsti
549173b395 Fix StmtAnnAssign formatting by mirroring StmtAssign (#5732)
## Summary

`StmtAnnAssign` would not insert parentheses when breaking the same way
`StmtAssign` does, causing unstable formatting and likely some syntax
errors.

## Test Plan

I added a regression test.
2023-07-13 10:51:25 +00:00
konsti
b1781abffb Link issue tracker in contributing docs (#5688)
## Summary

This adds links to issue categories that are good for people looking to
implement something and a link to the contributing guide feedback issue
(https://github.com/astral-sh/ruff/issues/5684)

---------

Co-authored-by: Zanie <contact@zanie.dev>
2023-07-13 10:42:09 +00:00
konsti
68e0f97354 Formatter: Better f-string dummy (#5730)
## Summary

The previous dummy was causing instabilities since it turned a string
into a variable.

E.g.
```python
            script_header_dict[
                "slurm_partition_line"
            ] = f"#SBATCH --partition {resources.queue_name}"
```
has an instability as
```python
-            script_header_dict["slurm_partition_line"] = (
-                NOT_YET_IMPLEMENTED_ExprJoinedStr
-            )
+            script_header_dict[
+                "slurm_partition_line"
+            ] = NOT_YET_IMPLEMENTED_ExprJoinedStr
```

## Test Plan

The instability is gone, otherwise it's still a dummy
2023-07-13 09:27:25 +00:00
Dhruv Manilawala
e9771c9c63 Ignore Jupyter Notebooks for --add-noqa (#5727) 2023-07-13 13:26:47 +05:30
Micha Reiser
067b2a6ce6 Pass parent to NeedsParentheses (#5708) 2023-07-13 08:57:29 +02:00
Charlie Marsh
30702c2977 Flatten nested tuples when fixing UP007 violations (#5724)
## Summary

Also upgrading these to "Suggested" from "Manual" (they should've always
been "Suggested", I think), and adding some more test cases.
2023-07-13 04:11:32 +00:00
Charlie Marsh
34b79ead3d Use Locator-based replacement rather than Generator for UP007 (#5723)
## Summary

Locator-based replacement is generally preferable as we get verbatim
fixes.
2023-07-13 03:50:16 +00:00
Justin Prieto
19f475ae1f [flake8-pyi] Implement PYI036 (#5668)
## Summary

Implements PYI036 from `flake8-pyi`. See [original
code](https://github.com/PyCQA/flake8-pyi/blob/main/pyi.py#L1585)

## Test Plan

- Updated snapshots
- Checked against manual runs of flake8

ref: #848
2023-07-13 02:50:00 +00:00
Tom Kuson
2b03bd18f4 Implement Pylint consider-using-in (#5193)
## Summary

Implement Pylint rule [`consider-using-in`
(`R1714`)](https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/consider-using-in.html)
as `repeated-equality-comparison-target` (`PLR1714`). This rule checks
for expressions that can be re-written as a membership test for better
readability and performance.

For example,

```python
foo == "bar" or foo == "baz" or foo == "qux"
```

should be rewritten as

```python
foo in {"bar", "baz", "qux"}
```

Related to #970. Includes documentation.

### Implementation quirks

The implementation does not work with Yoda conditions (e.g., `"a" ==
foo` instead of `foo == "a"`). The Pylint version does. I couldn't find
a way of supporting Yoda-style conditions without it being inefficient,
so didn't (I don't think people write Yoda conditions any way).

## Test Plan

Added fixture.

`cargo test`
2023-07-13 01:32:34 +00:00
Charlie Marsh
c87faca884 Use Cursor for shebang parsing (#5716)
## Summary

Better to leverage the shared functionality we get from `Cursor`. It's
also a little bit faster, which is very cool.
2023-07-12 21:22:09 +00:00
Charlie Marsh
6dbc6d2e59 Use shared Cursor across crates (#5715)
## Summary

We have two `Cursor` implementations. This PR moves the implementation
from the formatter into `ruff_python_whitespace` (kind of a poorly-named
crate now) and uses it for both use-cases.
2023-07-12 21:09:27 +00:00
Charlie Marsh
6ce252f0ed Tweak hierarchy of benchmark docs (#5720)
## Summary

Before:

<img width="309" alt="Screen Shot 2023-07-12 at 4 33 23 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/b4a29dc5-183d-479f-8028-f47157b87e0e">

After:

<img width="281" alt="Screen Shot 2023-07-12 at 4 33 32 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/316859d3-db90-4595-8c07-b4bb6543ac4d">
2023-07-12 17:08:22 -04:00
Charlie Marsh
c029c8b37a Run release testing on PR, not push (#5718)
## Summary

This job runs whenever I put up a PR to bump the version, which is
really useful. But then it also runs again when I merge, and then _that_
job tends to get cancelled immediately, because I run the _actual_
release job, which triggers the cancel-concurrent-runs flow. (See, e.g.,
https://github.com/astral-sh/ruff/actions/runs/5534191373.)

I think it makes sense to run these on PR (when editing `pyproject.toml`
and friends), but not again on merge.
2023-07-12 14:22:29 -04:00
Charlie Marsh
0ead9a16ac Bump version to 0.0.278 (#5714) 2023-07-12 12:39:56 -04:00
Micha Reiser
653429bef9 Handle right parens in join comma builder (#5711) 2023-07-12 18:21:28 +02:00
konsti
f0aa6bd4d3 Document ruff_dev and format_dev (#5648)
## Summary

Document all `ruff_dev` subcommands and document the `format_dev` flags
in the formatter readme.

CC @zanieb please flag everything that isn't clear or missing

## Test Plan

n/a
2023-07-12 16:18:22 +02:00
Zanie
5665968b42 Bump static Python versions in CI from 3.7 to 3.11 (#5700)
Python 3.7 is EOL and we should use the latest stable version for
builds.

Related to https://github.com/astral-sh/ruff-lsp/pull/189

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-07-12 13:56:22 +00:00
Zanie
33a91773f7 Use permalinks in ecosystem diff references (#5704)
Closes https://github.com/astral-sh/ruff/issues/5702
2023-07-12 01:26:37 -05:00
Zanie
0666added9 Add RUF016: Detection of invalid index types (#5602)
Detects invalid types for tuple, list, bytes, string indices.

For example, the following will raise a `TypeError` at runtime and when
imported Python will display a `SyntaxWarning`

```python
var = [1, 2, 3]["x"]
```

```
example.py:1: SyntaxWarning: list indices must be integers or slices, not str; perhaps you missed a comma?
  var = [1, 2, 3]["x"]
Traceback (most recent call last):
  File "example.py", line 1, in <module>
    var = [1, 2, 3]["x"]
          ~~~~~~~~~^^^^^
TypeError: list indices must be integers or slices, not str
```

Previously, Ruff would not report the invalid syntax but now a violation
will be reported. This does not apply to cases where a variable, call,
or complex expression is used in the index — detection is roughly
limited to static definitions, which matches Python's warnings.

```
❯ ./target/debug/ruff example.py --select RUF015 --show-source --no-cache
example.py:1:17: RUF015 Indexed access to type `list` uses type `str` instead of an integer or slice.
  |
1 | var = [1, 2, 3]["x"]
  |                 ^^^ RUF015
  |
```

Closes https://github.com/astral-sh/ruff/issues/5082
xref
ffff1440d1
2023-07-12 00:23:06 -05:00
qdegraaf
7566ca8ff7 Refactor repeated_keys() to use ComparableExpr (#5696)
## Summary

Replaces `DictionaryKey` enum with the more general `ComparableExpr`
when checking for duplicate keys

## Test Plan

Added test fixture from issue. Can potentially be expanded further
depending on what exactly we want to flag (e.g. do we also want to check
for unhashable types?) and which `ComparableExpr::XYZ` types we consider
literals.

## Issue link

Closes: https://github.com/astral-sh/ruff/issues/5691
2023-07-12 03:46:53 +00:00
Charlie Marsh
5dd9e56748 Misc. tweaks to bandit documentation (#5701) 2023-07-11 23:32:15 -04:00
Tom Kuson
f8173daf4c Add documentation to the S3XX rules (#5592)
## Summary

Add documentation to the `S3XX` rules (the `flake8-bandit`
['blacklists'](https://bandit.readthedocs.io/en/latest/plugins/index.html#plugin-id-groupings)
rule group). Related to #2646 .

Changed the `lxml`-based message to reflect that [`defusedxml` doesn't
support `lxml`](https://github.com/tiran/defusedxml/issues/31).

## Test Plan

`python scripts/check_docs_formatted.py && mkdocs serve`
2023-07-11 18:56:51 -05:00
Charlie Marsh
511ec0d7bc Refactor shebang parsing to remove regex dependency (#5690)
## Summary

Similar to #5567, we can remove the use of regex, plus simplify the
representation (use `Option`), add snapshot tests, etc.

This is about 100x faster than using a regex for cases that match (2.5ns
vs. 250ns). It's obviously not a hot path, but I prefer the consistency
with other similar comment-parsing. I may DRY these up into some common
functionality later on.
2023-07-11 16:30:38 -04:00
Micha Reiser
30bec3fcfa Only omit optinal parens if the expression ends or starts with a parenthesized expression
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR matches Black' behavior where it only omits the optional parentheses if the expression starts or ends with a parenthesized expression:

```python
a + [aaa, bbb, cccc] * c # Don't omit
[aaa, bbb, cccc] + a * c # Split
a + c * [aaa, bbb, ccc] # Split 
```

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

This improves the Jaccard index from 0.945 to 0.946
2023-07-11 17:05:25 +02:00
Micha Reiser
8b9193ab1f Improve comprehension line break beheavior
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR improves the Black compatibility when it comes to breaking comprehensions. 

We want to avoid line breaks before the target and `in` whenever possible. Furthermore, `if X is not None` should be grouped together, similar to other binary like expressions

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

`cargo test`

<!-- How was it tested? -->
2023-07-11 16:51:24 +02:00
konsti
62a24e1028 Format ModExpression (#5689)
## Summary

We don't use `ModExpression` anywhere but it's part of the AST, removes
one `not_implemented_yet` and is a trivial 2-liner, so i implemented
formatting for `ModExpression`.

## Test Plan

None, this kind of node does not occur in file input. Otherwise all the
tests for expressions
2023-07-11 16:41:10 +02:00
Micha Reiser
f1d367655b Format target: annotation = value? expressions (#5661) 2023-07-11 16:40:28 +02:00
konsti
0c8ec80d7b Change lambda dummy to NOT_YET_IMPLEMENTED_lambda (#5687)
This only changes the dummy to be easier to identify.
2023-07-11 13:16:18 +00:00
Micha Reiser
df15ad9696 Print files that are slow to format (#5681)
Co-authored-by: konsti <konstin@mailbox.org>
2023-07-11 13:03:18 +00:00
Micha Reiser
8665a1a19d Pass FormatContext to NeedsParentheses
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

I started working on this because I assumed that I would need access to options inside of `NeedsParantheses` but it then turned out that I won't. 
Anyway, it kind of felt nice to pass fewer arguments. So I'm gonna put this out here to get your feedback if you prefer this over passing individual fiels. 

Oh, I sneeked in another change. I renamed `context.contents` to `source`. `contents` is too generic and doesn't tell you anything. 

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

It compiles
2023-07-11 14:28:50 +02:00
Micha Reiser
9a8ba58b4c Remove mode from BestFitting
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR removes the `mode` field from `BestFitting` because it is no longer used (we now use `conditional_group` and `fits_expanded).

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

`cargo test`

<!-- How was it tested? -->
2023-07-11 14:19:26 +02:00
Micha Reiser
715250a179 Prefer expanding parenthesized expressions before operands
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR implements Black's behavior where it first splits off parenthesized expressions before splitting before operands to avoid unnecessary parentheses:

```python
# We want 
if a + [ 
	b,
	c
]: 
	pass

# Rather than
if (
    a
    + [b, c]
): 
	pass
```

This is implemented by using the new IR elements introduced in #5596. 

* We give the group wrapping the optional parentheses an ID (`parentheses_id`)
* We use `conditional_group` for the lower priority groups  (all non-parenthesized expressions) with the condition that the `parentheses_id` group breaks (we want to split before operands only if the parentheses are necessary)
* We use `fits_expanded` to wrap all other parenthesized expressions (lists, dicts, sets), to prevent that expanding e.g. a list expands the `parentheses_id` group. We gate the `fits_expand` to only apply if the `parentheses_id` group fits (because we  prefer `a\n+[b, c]` over expanding `[b, c]` if the whole expression gets parenthesized).

We limit using `fits_expanded` and `conditional_group` only to expressions that themselves are not in parentheses (checking the conditions isn't free)

## Test Plan

It increases the Jaccard index for Django from 0.915 to 0.917

## Incompatibilites

There are two incompatibilities left that I'm aware of (there may be more, I didn't go through all snapshot differences). 

### Long string literals
I  commented on the regression. The issue is that a very long string (or any content without a split point) may not fit when only breaking the right side. The formatter than inserts the optional parentheses. But this is kind of useless because the overlong string will still not fit, because there are no new split points. 

I think we should ignore this incompatibility for now


### Expressions on statement level

I don't fully understand the logic behind this yet, but black doesn't break before the operators for the following example even though the expression exceeds the configured line width

```python
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa < bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb > ccccccccccccccccccccccccccccc == ddddddddddddddddddddd
```

But it would if the expression is used inside of a condition. 

What I understand so far is that Black doesn't insert optional parentheses on the expression statement level (and a few other places) and, therefore, only breaks after opening parentheses. I propose to keep this deviation for now to avoid overlong-lines and use the compatibility report to make a decision if we should implement the same behavior.
2023-07-11 14:07:39 +02:00
Micha Reiser
d30e9125eb Extend formatter IR to support Black's expression formatting (#5596) 2023-07-11 11:20:04 +00:00
konsti
212fd86bf0 Switch from jaccard index to similarity index (#5679)
## Summary

The similarity index, the fraction of unchanged lines, is easier to
understand than the jaccard index, the fraction between intersection and
union.

## Test Plan

I ran this on django and git a 0.945 index, meaning 5.5% of lines are
currently reformatted when compared to black
2023-07-11 13:03:44 +02:00
David Szotten
4b58a9c092 formatter: tidy: list_comp is an expression, not a statement (#5677) 2023-07-11 08:00:10 +00:00
konsti
b7794f855b Format StmtAugAssign (#5655)
## Summary

Format statements such as `tree_depth += 1`. This is a statement that
does not allow any line breaks, the only thing to be mindful of is to
parenthesize the assigned expression

Jaccard index on django: 0.915 -> 0.918

## Test Plan

black tests, and two new tests, a basic one and one that ensures that
the child gets parentheses. I ran the django stability check.
2023-07-11 09:06:23 +02:00
Chris Pryer
15c7b6bcf7 Format delete statement (#5169) 2023-07-11 08:36:26 +02:00
David Szotten
1782fb8c30 format ExprListComp (#5600)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-11 06:35:51 +00:00
Micha Reiser
987111f5fb Format ExpressionStarred nodes (#5654) 2023-07-11 06:08:08 +00:00
Charlie Marsh
9f486fa841 [flake8-bugbear] Implement re-sub-positional-args (B034) (#5669)
## Summary

Needed to do some coding to end the day.

Closes #5665.
2023-07-11 03:52:55 +00:00
Charlie Marsh
4dee49d6fa Run nightly Clippy over the Ruff repo (#5670)
## Summary

This is the result of running `cargo +nightly clippy --workspace
--all-targets --all-features -- -D warnings` and fixing all violations.
Just wanted to see if there were any interesting new checks on nightly
👀
2023-07-10 23:44:38 -04:00
Louis Dispa
e7e2f44440 Format raise statement (#5595)
## Summary

This PR implements the formatting of `raise` statements. I haven't
looked at the black implementation, this is inspired from from the
`return` statements formatting.

## Test Plan

The black differences with insta.

I also compared manually some edge cases with very long string and call
chaining and it seems to do the same formatting as black.

There is one issue:
```python
# input

raise OsError(
    "aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa"
) from a.aaaaa(aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa).a(aaaa)


# black

raise OsError(
    "aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa"
) from a.aaaaa(
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
).a(
    aaaa
)


# ruff

raise OsError(
    "aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa"
) from a.aaaaa(
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
).a(aaaa)
```

But I'm not sure this diff is the raise formatting implementation.

---------

Co-authored-by: Louis Dispa <ldispa@deezer.com>
2023-07-10 21:23:49 +02:00
Dhruv Manilawala
93bfa239b7 Add Jupyter Notebook usage with pre-commit in docs (#5666)
Similar to https://github.com/astral-sh/ruff-pre-commit/pull/45
2023-07-11 00:47:05 +05:30
monosans
14f2158e5d [flake8-self] Ignore _name_ and _value_ (#5663)
## Summary

`Enum._name_` and `Enum._value_` are so named to prevent conflicts. See
<https://docs.python.org/3/library/enum.html#supported-sunder-names>.

## Test Plan

Tests for `ignore-names` already exist.
2023-07-10 14:52:59 -04:00
Tom Kuson
b8a6ce43a2 Properly ignore bivariate types in type-name-incorrect-variance (#5660)
## Summary

#5658 didn't actually ignore bivariate types in some all cases (sorry
about that). This PR fixes that and adds bivariate types to the test
fixture.

## Test Plan

`cargo test`
2023-07-10 14:19:17 -04:00
Tom Kuson
5ab9538573 Improve type-name-incorrect-variance message (#5658)
## Summary

Change the `type-name-incorrect-variance` diagnostic message to include
the detected variance and a name change recommendation. For example,

```
`TypeVar` name "T_co" does not reflect its contravariance; consider renaming it to "T_contra"
```

Related to #5651.

## Test Plan

`cargo test`
2023-07-10 13:33:37 -04:00
Zanie
d19839fe0f Add support for Union declarations without | to PYI016 (#5598)
Previously, PYI016 only supported reporting violations for unions
defined with `|`. Now, union declarations with `typing.Union` are
supported.
2023-07-10 17:11:54 +00:00
dependabot[bot]
8dc06d1035 ci(deps): bump webfactory/ssh-agent from 0.7.0 to 0.8.0 (#5657) 2023-07-10 13:10:25 -04:00
Charlie Marsh
120e9d37f1 Audit some SemanticModel#is_builtin usages (#5659)
## Summary

Non-behavior-changing refactors to delay some `.is_builtin` calls in a
few older rules. Cheaper pre-conditions should always be checked first.
2023-07-10 13:10:08 -04:00
Evan Rittenhouse
28fe2d334a Implement UnnecessaryListAllocationForFirstElement (#5549)
## Summary

Fixes #5503. Ready for final review as the `mkdocs` issue involving SSH
keys is fixed.

Note that this will only throw on a `Name` - it will be refactorable
once we have a type-checker. This means that this is the only sort of
input that will throw.
```python
x = range(10)
list(x)[0]
```

I thought it'd be confusing if we supported direct function results.
Consider this example, assuming we support direct results:
```python
# throws
list(range(10))[0]

def createRange(bound):
    return range(bound)

# "why doesn't this throw, but a direct `range(10)` call does?"
list(createRange(10))[0]
```
If it's necessary, I can go through the list of built-ins and find those
which produce iterables, then add them to the throwing list.

## Test Plan

Added a new fixture, then ran `cargo t`
2023-07-10 16:32:41 +00:00
Tom Kuson
3562d809b2 [pylint] Implement Pylint typevar-name-incorrect-variance (C0105) (#5651)
## Summary

Implement Pylint `typevar-name-incorrect-variance` (`C0105`) as
`type-name-incorrect-variance` (`PLC0105`). Includes documentation.
Related to #970.

The Pylint implementation checks only `TypeVar`, but this PR checks
`ParamSpec` as well.

## Test Plan

Added test fixture.

`cargo test`
2023-07-10 12:28:44 -04:00
Tom Kuson
4cac75bc27 Add documentation to pandas-vet rules (#5629)
## Summary

Completes all the documentation for the `pandas-vet` rules, except for
`pandas-use-of-dot-read-table` as I am unclear of the rule's motivation
(see #5628).

Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py && mkdocs serve`
2023-07-10 15:45:36 +00:00
Charlie Marsh
ed872145fe Always allow PEP 585 and PEP 604 rewrites in stub files (#5653)
Closes https://github.com/astral-sh/ruff/issues/5640.
2023-07-10 14:51:38 +00:00
Charlie Marsh
35b04c2fab Skip flake8-future-annotations checks in stub files (#5652)
Closes https://github.com/astral-sh/ruff/issues/5649.
2023-07-10 10:49:17 -04:00
Evan Rittenhouse
ae4a7ef0ed Make TRY301 trigger only if a raise throws a caught exception (#5455)
## Summary

Fixes #5246. We generate a hash set of all exception IDs caught by the
`try` statement, then check that the inner `raise` actually raises a
caught exception.

## Test Plan

Added a new test, `cargo t`.
2023-07-10 10:00:43 -04:00
konsti
cab3a507bc Fix find_only_token_in_range with expression parentheses (#5645)
## Summary

Fix an oversight in `find_only_token_in_range` where the following code
would panic due do the closing and opening parentheses being in the
range we scan:
```python
d1 = [
    ("a") if # 1
    ("b") else # 2
    ("c")
]
```
Closing and opening parentheses respectively are now correctly skipped.

## Test Plan

I added a regression test
2023-07-10 15:55:19 +02:00
Harutaka Kawamura
82317ba1fd Support autofix for some multiline str.format calls (#5638)
## Summary

Fixes #5531

## Test Plan

New test cases
2023-07-10 09:49:13 -04:00
Aarni Koskela
24bcbb85a1 Rework upstream categories so we can all_rules() (#5591)
## Summary

This PR reworks the `upstream_categories` mechanism that is only used
for documentation purposes to make it easier to generate docs using
`all_rules()`. The new implementation also relies on "tribal knowledge"
about rule codes, so it's not the best implementation, but gets us
forward.

Another option would be to change the rule-defining proc macros to allow
configuring an optional `RuleCategory`, but that seems more heavy-handed
and possibly unnecessary in the long run...

Draft since this builds on #5439.

cc @charliermarsh :)
2023-07-10 09:41:26 -04:00
Micha Reiser
089a671adb Fix Black compatible snapshot deletion (#5646) 2023-07-10 15:00:18 +02:00
konsti
bd8f65814c Format named expressions (walrus operator) (#5642)
## Summary

Format named expressions (walrus operator) such a `value := f()`. 

Unlike tuples, named expression parentheses are not part of the range
even when mandatory, so mapping optional parentheses to always gives us
decent formatting without implementing all [PEP
572](https://peps.python.org/pep-0572/) rules on when we need
parentheses where other expressions wouldn't. We might want to revisit
this decision later and implement special cases, but for now this gives
us what we need.

## Test Plan

black fixtures, i added some fixtures and checked django and cpython for
stability.

Closes #5613
2023-07-10 12:32:15 +00:00
David Szotten
1e894f328c formatter: multi char tokens in SimpleTokenizer (#5610) 2023-07-10 09:00:59 +01:00
Dhruv Manilawala
52b22ceb6e Add links to ecosystem check result (#5631)
## Summary

Add links for ecosystem check result. This is useful for developers to
quickly check the added/removed violations with a single click.

There are a few downsides of this approach:
* Syntax highlighting is not available for the output
* Content length is increased because of the additional anchor tags

## Test Plan

`python scripts/check_ecosystem.py ./target/debug/ruff ../ruff-test/target/debug/ruff`

<details><summary>Example Output:</summary>

ℹ️ ecosystem check **detected changes**. (+6, -0, 0 error(s))

<details><summary>airflow (+1, -0)</summary>
<p>

<pre>
+ <a
href='https://github.com/apache/airflow/blob/main/dev/breeze/src/airflow_breeze/commands/release_management_commands.py#L654'>dev/breeze/src/airflow_breeze/commands/release_management_commands.py:654:25:</a>
PERF401 Use a list comprehension to create a transformed list
</pre>

</p>
</details>
<details><summary>bokeh (+3, -0)</summary>
<p>

<pre>
+ <a
href='https://github.com/bokeh/bokeh/blob/branch-3.2/src/bokeh/model/model.py#L315'>src/bokeh/model/model.py:315:17:</a>
PERF401 Use a list comprehension to create a transformed list
+ <a
href='https://github.com/bokeh/bokeh/blob/branch-3.2/src/bokeh/resources.py#L470'>src/bokeh/resources.py:470:25:</a>
PERF401 Use a list comprehension to create a transformed list
+ <a
href='https://github.com/bokeh/bokeh/blob/branch-3.2/src/bokeh/sphinxext/bokeh_sampledata_xref.py#L134'>src/bokeh/sphinxext/bokeh_sampledata_xref.py:134:17:</a>
PERF401 Use a list comprehension to create a transformed list
</pre>

</p>
</details>
<details><summary>zulip (+2, -0)</summary>
<p>

<pre>
+ <a
href='https://github.com/zulip/zulip/blob/main/zerver/actions/create_user.py#L197'>zerver/actions/create_user.py:197:17:</a>
PERF401 Use a list comprehension to create a transformed list
+ <a
href='https://github.com/zulip/zulip/blob/main/zerver/lib/markdown/__init__.py#L2412'>zerver/lib/markdown/__init__.py:2412:13:</a>
PERF401 Use a list comprehension to create a transformed list
</pre>

</p>
</details>

</details>

---------

Co-authored-by: konsti <konstin@mailbox.org>
2023-07-10 09:25:26 +05:30
Charlie Marsh
c9d7c0d7d5 Add a link to the nursery; tweak icons (#5637)
## Summary

We now always render the icons, but very faintly if inactive, and always
right-align. This ensures consistent alignment as you scroll down the
page:

<img width="1792" alt="Screen Shot 2023-07-09 at 10 45 50 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/da47ac0e-d646-49e1-bbe1-9f43adf94bb4">
2023-07-10 03:09:08 +00:00
Charlie Marsh
eb69fe37bf Render full-width tables in rules reference (#5636) 2023-07-10 02:39:07 +00:00
Charlie Marsh
27011448ea Fix typo in complex-if-statement-in-stub message (#5635) 2023-07-10 02:35:34 +00:00
Aarni Koskela
b4d6b7c230 docs: show nursery icon for nursery rules (#5439)
## Summary

This changes the docs to show a nursery icon (🌅) for rules in the
nursery.

It currently doesn't do that for the rules that are in sub-categories
(Pylint, Pycodestyle) because there is no `all_rules()` for the
`RuleCodePrefix` that's returned by `UpstreamCategory` iteration (and as
mentioned on Discord, I think `UpstreamCategory` maybe shouldn't be a
thing). (That would be enabled by #5591.)

## Test Plan

Generated docs to see new icons (with the caveat above).
2023-07-09 22:24:57 -04:00
Charlie Marsh
fa1341b0db Improve PERF203 example in docs (#5634)
Closes #5624.
2023-07-10 02:24:46 +00:00
Charlie Marsh
401d172e47 Use a simple match statement for case-insensitive noqa lookup (#5633)
## Summary

It turns out that just doing this match directly without `AhoCorasick`
is much faster, like 2x (and removes one dependency, though we likely
already rely on this transitively).
2023-07-09 22:15:23 -04:00
Dhruv Manilawala
6a4b216362 Avoid PERF401 if conditional depends on list var (#5603)
## Summary

Avoid `PERF401` if conditional depends on list var

## Test Plan

`cargo test`

fixes: #5581
2023-07-09 15:53:27 -04:00
Dhruv Manilawala
9dd05424c4 Update ecosystem script to account for 4 letter code (#5627)
E.g., `PERF`
2023-07-09 15:53:02 -04:00
Tom Kuson
ac2e374a5a Add tkinter import convention (#5626)
## Summary

Adds `import tkinter as tk` to the list of default import conventions.

Closes #5620.

## Test Plan

Added `tkinter` to test fixture.

`cargo test`
2023-07-09 16:26:31 +05:30
Charlie Marsh
38fa305f35 Refactor isort directive skips to use iterators (#5623)
## Summary

We're doing some unsafe accesses to advance these iterators. It's easier
to model these as actual iterators to ensure safety everywhere. Also
added some additional test cases.

Closes #5621.
2023-07-08 19:05:44 +00:00
Charlie Marsh
456273a92e Support individual codes on # flake8: noqa directives (#5618)
## Summary

We now treat `# flake8: noqa: F401` as turning off F401 for the entire
file. (Flake8 treats this as turning off _all rules_ for the entire
file).

This deviates from Flake8, but I think it's a much more user-friendly
deviation than what I introduced in #5571. See
https://github.com/astral-sh/ruff/issues/5617 for an explanation.

Closes https://github.com/astral-sh/ruff/issues/5617.
2023-07-08 16:51:37 +00:00
Charlie Marsh
507961f27d Emit warnings for invalid # noqa directives (#5571)
## Summary

This PR adds a `ParseError` type to the `noqa` parsing system to enable
us to render useful warnings instead of silently failing when parsing
`noqa` codes.

For example, given `foo.py`:

```python
# ruff: noqa: x

# ruff: noqa foo

# flake8: noqa: F401
import os  # noqa: foo-bar
```

We would now output:

```console
warning: Invalid `# noqa` directive on line 2: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 4: expected `:` followed by a comma-separated list of codes (e.g., `# noqa: F401, F841`).
warning: Invalid `# noqa` directive on line 6: Flake8's blanket exemption does not support exempting specific codes. To exempt specific codes, use, e.g., `# ruff: noqa: F401, F841` instead.
warning: Invalid `# noqa` directive on line 7: expected a comma-separated list of codes (e.g., `# noqa: F401, F841`).
```

There's one important behavior change here too. Right now, with Flake8,
if you do `# flake8: noqa: F401`, Flake8 treats that as equivalent to `#
flake8: noqa` -- it turns off _all_ diagnostics in the file, not just
`F401`. Historically, we respected this... but, I think it's confusing.
So we now raise a warning, and don't respect it at all. This will lead
to errors in some projects, but I'd argue that right now, those
directives are almost certainly behaving in an unintended way for users
anyway.

Closes https://github.com/astral-sh/ruff/issues/3339.
2023-07-08 16:37:55 +00:00
Charlie Marsh
a1c559eaa4 Only run pyproject.toml lint rules when enabled (#5578)
## Summary

I was testing some changes on Airflow, and I realized that we _always_
run the `pyproject.toml` validation rules, even if they're not enabled.
This PR gates them behind the appropriate enablement flags.

## Test Plan

- Ran: `cargo run -p ruff_cli -- check ../airflow -n`. Verified that no
RUF200 violations were raised.
- Run: `cargo run -p ruff_cli -- check ../airflow -n --select RUF200`.
Verified that two RUF200 violations were raised.
2023-07-08 11:05:05 -04:00
konsti
d0dae7e576 Fix CI by downgrading to cargo insta 1.29.0 (#5589)
Since the (implicit) update to cargo-insta 1.30, CI would pass even when
the tests failed. This downgrades to cargo insta 1.29.0 and CI fails
again when it should (which i can't show here, because CI needs to pass
to merge this PR). I've improved the unreferenced snapshot handling in
the process

See https://github.com/mitsuhiko/insta/issues/392
2023-07-08 14:54:49 +00:00
Dimitri Papadopoulos Orfanos
efe7c393d1 Fix typos found by codespell (#5607)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

Fix typos found by
[codespell](https://github.com/codespell-project/codespell).

I have left out `memoize` for now (see #5606).
<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

CI tests.
<!-- How was it tested? -->
2023-07-08 12:33:18 +02:00
konsti
0b9af031fb Format ExprIfExp (ternary operator) (#5597)
## Summary

Format `ExprIfExp`, also known as the ternary operator or inline `if`.
It can look like
```python
a1 = 1 if True else 2
```
but also
```python
b1 = (
    # We return "a" ...
    "a" # that's our True value
    # ... if this condition matches ...
    if True # that's our test
    # ... otherwise we return "b§
    else "b" # that's our False value
)
```

This also fixes a visitor order bug.

The jaccard index on django goes from 0.911 to 0.915.

## Test Plan

I added fixtures without and with comments in strange places.
2023-07-07 19:11:52 +00:00
konsti
0f9d7283e7 Add format-dev contributor docs (#5594)
## Summary

This adds markdown-level docs for #5492

## Test Plan

n/a

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-07 16:52:13 +00:00
Zanie
bb7303f867 Implement PYI030: Unnecessary literal union (#5570)
Implements PYI030 as part of
https://github.com/astral-sh/ruff/issues/848

> Union expressions should never have more than one Literal member, as
Literal[1] | Literal[2] is semantically identical to Literal[1, 2].

Note we differ slightly from the flake8-pyi implementation:

- We detect cases where there are parentheses or nested unions
- We detect cases with mixed `Union` and `|` syntax
- We use the same error message for all violations; flake8-pyi has two
different messages
- We retain the user's quoting style when displaying string literals;
flake8-pyi uses single quotes
- We warn on duplicates of the same literal `Literal[1] | Literal[1]`
2023-07-07 16:43:10 +00:00
konsti
60d318ddcf Check formatter stability on CI (#5446)
Check formatter stability on CI using CPython. This should be merged
into the ecosystem checks, but i think this is a good start.
2023-07-07 18:28:36 +02:00
Charlie Marsh
5640c310bb Move file-level rule exemption to lexer-based approach (#5567)
## Summary

In addition to `# noqa` codes, we also support file-level exemptions,
which look like:

- `# flake8: noqa` (ignore all rules in the file, for compatibility)
- `# ruff: noqa` (all rules in the file)
- `# ruff: noqa: F401` (ignore `F401` in the file, Flake8 doesn't
support this)

This PR moves that logic to something that looks a lot more like our `#
noqa` parser. Performance is actually quite a bit _worse_ than the
previous approach (lexing `# flake8: noqa` goes from 2ns to 11ns; lexing
`# ruff: noqa: F401, F841` is about the same`; lexing `# type: ignore #
noqa: E501` fgoes from 4ns to 6ns), but the numbers are very small so
it's... maybe worth it?

The primary benefit here is that we now properly support flexible
whitespace, like: `#flake8:noqa`. Previously, we required exact string
matching, and we also didn't support all case-insensitive variants of
`noqa`.
2023-07-07 15:41:20 +00:00
Charlie Marsh
072358e26b Use Instagram's LibCST rather than our fork (#5593)
## Summary

Historically, we only used a fork to enable building without pyo3. But
pyo3 is an optional feature. I may've just not understood how to
accomplish this way back when.
2023-07-07 10:00:44 -04:00
Peter Attia
aaab9f1597 Bugfix: Remove version numbers from pypi links (#5579)
## Summary

There are two pypi links in the documentation that link to specific
version numbers of other packages. Removing these versioned links allows
users to immediately view the latest version of the package and
maintains consistency with the other links.

## Test Plan

N/A
2023-07-07 09:35:50 -04:00
konsti
b22e6c3d38 Extend ruff_dev formatter script to compute statistics and format a project (#5492)
## Summary

This extends the `ruff_dev` formatter script util. Instead of only doing
stability checks, you can now choose different compatible options on the
CLI and get statistics.

* It adds an option the formats all files that ruff would check to allow
looking at an entire black-formatted repository with `git diff`
* It computes the [Jaccard
index](https://en.wikipedia.org/wiki/Jaccard_index) as a measure of
deviation between input and output, which is useful as single number
metric for assessing our current deviations from black.
* It adds progress bars to both the single projects as well as the
multi-project mode.
* It adds an option to write the multi-project output to a file

Sample usage:

```
$ cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython
$ cargo run --bin ruff_dev -- format-dev --stability-check /home/konsti/projects/django
Syntax error in /home/konsti/projects/django/tests/test_runner_apps/tagged/tests_syntax_error.py: source contains syntax errors (parser error): BaseError { error: UnrecognizedToken(Name { name: "syntax_error" }, None), offset: 131, source_path: "<filename>" }
Found 0 stability errors in 2755 files (jaccard index 0.911) in 9.75s
$ cargo run --bin ruff_dev -- format-dev --write /home/konsti/projects/django
```

Options:

```
Several utils related to the formatter which can be run on one or more repositories. The selected set of files in a repository is the same as for `ruff check`.

* Check formatter stability: Format a repository twice and ensure that it looks that the first and second formatting look the same. * Format: Format the files in a repository to be able to check them with `git diff` * Statistics: The subcommand the Jaccard index between the (assumed to be black formatted) input and the ruff formatted output

Usage: ruff_dev format-dev [OPTIONS] [FILES]...

Arguments:
  [FILES]...
          Like `ruff check`'s files. See `--multi-project` if you want to format an ecosystem checkout

Options:
      --stability-check
          Check stability
          
          We want to ensure that once formatted content stays the same when formatted again, which is known as formatter stability or formatter idempotency, and that the formatter prints syntactically valid code. As our test cases cover only a limited amount of code, this allows checking entire repositories.

      --write
          Format the files. Without this flag, the python files are not modified

      --format <FORMAT>
          Control the verbosity of the output
          
          [default: default]

          Possible values:
          - minimal: Filenames only
          - default: Filenames and reduced diff
          - full:    Full diff and invalid code

  -x, --exit-first-error
          Print only the first error and exit, `-x` is same as pytest

      --multi-project
          Checks each project inside a directory, useful e.g. if you want to check all of the ecosystem checkouts

      --error-file <ERROR_FILE>
          Write all errors to this file in addition to stdout. Only used in multi-project mode
```

## Test Plan

I ran this on django (2755 files, jaccard index 0.911) and discovered a
magic trailing comma problem and that we really needed to implement
import formatting. I ran the script on cpython to identify
https://github.com/astral-sh/ruff/pull/5558.
2023-07-07 11:30:12 +00:00
Micha Reiser
40ddc1604c Introduce parenthesized helper (#5565) 2023-07-07 11:28:25 +02:00
Charlie Marsh
bf4b96c5de Differentiate between runtime and typing-time annotations (#5575)
## Summary

In Python, the annotations on `x` and `y` here have very different
treatment:

```python
def foo(x: int):
  y: int
```

The `int` in `x: int` is a runtime-required annotation, because `x` gets
added to the function's `__annotations__`. You'll notice, for example,
that this fails:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
  from foo import Bar

def f(x: Bar):
  ...
```

Because `Bar` is required to be available at runtime, not just at typing
time. Meanwhile, this succeeds:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
  from foo import Bar

def f():
  x: Bar = 1

f()
```

(Both cases are fine if you use `from __future__ import annotations`.)

Historically, we've tracked those annotations that are _not_
runtime-required via the semantic model's `ANNOTATION` flag. But
annotations that _are_ runtime-required have been treated as "type
definitions" that aren't annotations.

This causes problems for the flake8-future-annotations rules, which try
to detect whether adding `from __future__ import annotations` would
_allow_ you to rewrite a type annotation. We need to know whether we're
in _any_ type annotation, runtime-required or not, since adding `from
__future__ import annotations` will convert any runtime-required
annotation to a typing-only annotation.

This PR adds separate state to track these runtime-required annotations.
The changes in the test fixtures are correct -- these were false
negatives before.

Closes https://github.com/astral-sh/ruff/issues/5574.
2023-07-07 00:21:44 -04:00
Charlie Marsh
b11492e940 Fix remaining Copyright rule references (#5577) 2023-07-07 02:49:19 +00:00
Charlie Marsh
cd4718988a Update JSON schema (#5576)
Confused as to how this got merged, but... oh well.
2023-07-06 22:38:39 -04:00
Tom Kuson
5908b39102 Support globbing in isort options (#5473)
## Summary

Support glob patterns in `isort` options.

Closes #5420.

## Test Plan

Added test.

`cargo test`
2023-07-06 20:37:41 -04:00
Charlie Marsh
edfe76d673 Remove checked-in scratch file (#5573) 2023-07-06 21:46:17 +00:00
konsti
5e5a96ca28 Fix formatter StmtTry test (#5568)
For some reason this didn't turn up on CI before

CC @michareiser this is the fix for the error you had
2023-07-06 18:23:53 +00:00
Tom Kuson
3650aaa8b3 Add documentation to the S1XX rules (#5479)
## Summary

Add documentation to the `S1XX` rules (the `flake8-bandit` ['misc
tests'](https://bandit.readthedocs.io/en/latest/plugins/index.html#plugin-id-groupings)
rule group).

## Test Plan

`python scripts/check_docs_formatted.py && mkdocs serve`
2023-07-06 17:46:16 +00:00
Charlie Marsh
cc822082a7 Refactor noqa directive parsing away from regex-based implementation (#5554)
## Summary

I'll write up a more detailed description tomorrow, but in short, this
PR removes our regex-based implementation in favor of "manual" parsing.

I tried a couple different implementations. In the benchmarks below:

- `Directive/Regex` is our implementation on `main`.
- `Directive/Find` just uses `text.find("noqa")`, which is insufficient,
since it doesn't cover case-insensitive variants like `NOQA`, and
doesn't handle multiple `noqa` matches in a single like, like ` # Here's
a noqa comment # noqa: F401`. But it's kind of a baseline.
- `Directive/Memchr` uses three `memchr` iterative finders (one for
`noqa`, `NOQA`, and `NoQA`).
- `Directive/AhoCorasick` is roughly the variant checked-in here.

The raw results:

```
Directive/Regex/# noqa: F401
                        time:   [273.69 ns 274.71 ns 276.03 ns]
                        change: [+1.4467% +1.8979% +2.4243%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 15 outliers among 100 measurements (15.00%)
  3 (3.00%) low mild
  8 (8.00%) high mild
  4 (4.00%) high severe
Directive/Find/# noqa: F401
                        time:   [66.972 ns 67.048 ns 67.132 ns]
                        change: [+2.8292% +2.9377% +3.0540%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 15 outliers among 100 measurements (15.00%)
  1 (1.00%) low severe
  3 (3.00%) low mild
  8 (8.00%) high mild
  3 (3.00%) high severe
Directive/AhoCorasick/# noqa: F401
                        time:   [76.922 ns 77.189 ns 77.536 ns]
                        change: [+0.4265% +0.6862% +0.9871%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 8 outliers among 100 measurements (8.00%)
  1 (1.00%) low mild
  3 (3.00%) high mild
  4 (4.00%) high severe
Directive/Memchr/# noqa: F401
                        time:   [62.627 ns 62.654 ns 62.679 ns]
                        change: [-0.1780% -0.0887% -0.0120%] (p = 0.03 < 0.05)
                        Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
  1 (1.00%) low severe
  5 (5.00%) low mild
  3 (3.00%) high mild
  2 (2.00%) high severe
Directive/Regex/# noqa: F401, F841
                        time:   [321.83 ns 322.39 ns 322.93 ns]
                        change: [+8602.4% +8623.5% +8644.5%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 5 outliers among 100 measurements (5.00%)
  1 (1.00%) low severe
  2 (2.00%) low mild
  1 (1.00%) high mild
  1 (1.00%) high severe
Directive/Find/# noqa: F401, F841
                        time:   [78.618 ns 78.758 ns 78.896 ns]
                        change: [+1.6909% +1.8771% +2.0628%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  3 (3.00%) high mild
Directive/AhoCorasick/# noqa: F401, F841
                        time:   [87.739 ns 88.057 ns 88.468 ns]
                        change: [+0.1843% +0.4685% +0.7854%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 11 outliers among 100 measurements (11.00%)
  5 (5.00%) low mild
  3 (3.00%) high mild
  3 (3.00%) high severe
Directive/Memchr/# noqa: F401, F841
                        time:   [80.674 ns 80.774 ns 80.860 ns]
                        change: [-0.7343% -0.5633% -0.4031%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 14 outliers among 100 measurements (14.00%)
  4 (4.00%) low severe
  9 (9.00%) low mild
  1 (1.00%) high mild
Directive/Regex/# noqa  time:   [194.86 ns 195.93 ns 196.97 ns]
                        change: [+11973% +12039% +12103%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
  5 (5.00%) low mild
  1 (1.00%) high mild
Directive/Find/# noqa   time:   [25.327 ns 25.354 ns 25.383 ns]
                        change: [+3.8524% +4.0267% +4.1845%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 9 outliers among 100 measurements (9.00%)
  6 (6.00%) high mild
  3 (3.00%) high severe
Directive/AhoCorasick/# noqa
                        time:   [34.267 ns 34.368 ns 34.481 ns]
                        change: [+0.5646% +0.8505% +1.1281%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 5 outliers among 100 measurements (5.00%)
  5 (5.00%) high mild
Directive/Memchr/# noqa time:   [21.770 ns 21.818 ns 21.874 ns]
                        change: [-0.0990% +0.1464% +0.4046%] (p = 0.26 > 0.05)
                        No change in performance detected.
Found 10 outliers among 100 measurements (10.00%)
  4 (4.00%) low mild
  4 (4.00%) high mild
  2 (2.00%) high severe
Directive/Regex/# type: ignore # noqa: E501
                        time:   [278.76 ns 279.69 ns 280.72 ns]
                        change: [+7449.4% +7469.8% +7490.5%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 3 outliers among 100 measurements (3.00%)
  1 (1.00%) low mild
  1 (1.00%) high mild
  1 (1.00%) high severe
Directive/Find/# type: ignore # noqa: E501
                        time:   [67.791 ns 67.976 ns 68.184 ns]
                        change: [+2.8321% +3.1735% +3.5418%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 6 outliers among 100 measurements (6.00%)
  5 (5.00%) high mild
  1 (1.00%) high severe
Directive/AhoCorasick/# type: ignore # noqa: E501
                        time:   [75.908 ns 76.055 ns 76.210 ns]
                        change: [+0.9269% +1.1427% +1.3955%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 1 outliers among 100 measurements (1.00%)
  1 (1.00%) high severe
Directive/Memchr/# type: ignore # noqa: E501
                        time:   [72.549 ns 72.723 ns 72.957 ns]
                        change: [+1.5881% +1.9660% +2.3974%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 15 outliers among 100 measurements (15.00%)
  10 (10.00%) high mild
  5 (5.00%) high severe
Directive/Regex/# type: ignore # nosec
                        time:   [66.967 ns 67.075 ns 67.207 ns]
                        change: [+1713.0% +1715.8% +1718.9%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 10 outliers among 100 measurements (10.00%)
  1 (1.00%) low severe
  3 (3.00%) low mild
  2 (2.00%) high mild
  4 (4.00%) high severe
Directive/Find/# type: ignore # nosec
                        time:   [18.505 ns 18.548 ns 18.597 ns]
                        change: [+1.3520% +1.6976% +2.0333%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
  4 (4.00%) high mild
Directive/AhoCorasick/# type: ignore # nosec
                        time:   [16.162 ns 16.206 ns 16.252 ns]
                        change: [+1.2919% +1.5587% +1.8430%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
  3 (3.00%) high mild
  1 (1.00%) high severe
Directive/Memchr/# type: ignore # nosec
                        time:   [39.192 ns 39.233 ns 39.276 ns]
                        change: [+0.5164% +0.7456% +0.9790%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 13 outliers among 100 measurements (13.00%)
  2 (2.00%) low severe
  4 (4.00%) low mild
  3 (3.00%) high mild
  4 (4.00%) high severe
Directive/Regex/# some very long comment that # is interspersed with characters but # no directive
                        time:   [81.460 ns 81.578 ns 81.703 ns]
                        change: [+2093.3% +2098.8% +2104.2%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 4 outliers among 100 measurements (4.00%)
  2 (2.00%) low mild
  2 (2.00%) high mild
Directive/Find/# some very long comment that # is interspersed with characters but # no directive
                        time:   [26.284 ns 26.331 ns 26.387 ns]
                        change: [+0.7554% +1.1027% +1.3832%] (p = 0.00 < 0.05)
                        Change within noise threshold.
Found 6 outliers among 100 measurements (6.00%)
  5 (5.00%) high mild
  1 (1.00%) high severe
Directive/AhoCorasick/# some very long comment that # is interspersed with characters but # no direc...
                        time:   [28.643 ns 28.714 ns 28.787 ns]
                        change: [+1.3774% +1.6780% +2.0028%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) high mild
Directive/Memchr/# some very long comment that # is interspersed with characters but # no directive
                        time:   [55.766 ns 55.831 ns 55.897 ns]
                        change: [+1.5802% +1.7476% +1.9021%] (p = 0.00 < 0.05)
                        Performance has regressed.
Found 2 outliers among 100 measurements (2.00%)
  2 (2.00%) low mild
```

While memchr is faster than aho-corasick in some of the common cases
(like `# noqa: F401`), the latter is way, way faster when there _isn't_
a match (like 2x faster -- see the last two cases). Since most comments
_aren't_ `noqa` comments, this felt like the right tradeoff. Note that
all implementations are significantly faster than the regex version.

(I know I originally reported a 10x speedup, but I ended up improving
the regex version a bit in some prior PRs, so it got unintentionally
faster via some refactors.)

There's also one behavior change in here, which is that we now allow
variable spaces, e.g., `#noqa` or `# noqa`. Previously, we required
exactly one space. This thus closes #5177.
2023-07-06 16:03:10 +00:00
Simon Brugman
87ca6171cf docs: add user (#5563)
## Summary

Adding two repositories at ING Bank using ruff. Demonstrates
corporate/industry adoption, e.g. similar to AstraZeneca.

## Test Plan

Note that the tests failing seems unrelated.
2023-07-06 15:55:27 +00:00
Charlie Marsh
9713ee4b80 Remove ParsedFileExemption::None (#5555)
## Summary

This is more aligned with the other enums in this module. Should've been
changed in a previous refactor, just an oversight.
2023-07-06 11:15:46 -04:00
Charlie Marsh
528bf2df3a Use non-Insiders MkDocs for building in forks (#5562) 2023-07-06 15:02:46 +00:00
konsti
8184235f93 Try statements have a body: Fix formatter instability (#5558)
## Summary

The following code was previously leading to unstable formatting:
```python
try:
    try:
        pass
    finally:
        print(1)  # issue7208
except A:
    pass
```
The comment would be formatted as a trailing comment of `try` which is
unstable as an end-of-line comment gets two extra whitespaces.

This was originally found in
99b00efd5e/Lib/getpass.py (L68-L91)

## Test Plan

I added a regression test
2023-07-06 16:07:47 +02:00
Kar Petrosyan
25981420c4 Add httpx into the Who's Using Ruff? section (#5560) 2023-07-06 13:52:28 +00:00
Charlie Marsh
b56b8915ca Allow MkDocs job to run on forks (#5553)
Conditionally check whether the secret is available -- see:
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsif.
2023-07-06 05:46:49 +00:00
Charlie Marsh
bf02c77fd7 Replace stat mapping with match statement (#5548) 2023-07-05 23:42:21 +00:00
Charlie Marsh
ba7041b6bf Remove Directive's dependency on Locator (#5547)
## Summary

It's a bit simpler to let the API just take the text itself, plus an
offset (to make the returned `TextRange` absolute, rather than
relative).
2023-07-05 23:33:57 +00:00
Charlie Marsh
5dff3195d4 Refactor tokens-based rules to take an &mut Vec<Diagnostic> (#5525) 2023-07-05 19:21:42 -04:00
Charlie Marsh
23363cafd1 Move Directive fields behind accessor methods (#5546) 2023-07-05 23:13:41 +00:00
Charlie Marsh
e4596ebc35 Remove leading and trailing space length from Directive (#5545)
## Summary

We only need this in one place (when removing the directive), and it
simplifies a lot of details to just compute it there.
2023-07-05 23:03:06 +00:00
Charlie Marsh
c9e02c52a8 Add separate configuration for MkDocs Insiders plugins (#5544)
## Summary

This PR adds a separate configuration file to enable us to turn on
[Insiders-only
plugins](https://squidfunk.github.io/mkdocs-material/insiders/getting-started/#built-in-plugins).

I've turned on the `typeset` plugin which ensures that the settings on
the left-hand navigation pane render as code:

<img width="1792" alt="Screen Shot 2023-07-05 at 6 27 20 PM"
src="https://github.com/astral-sh/ruff/assets/1309177/c93676dd-bb48-417a-9d3b-528bf001e9b7">
2023-07-05 18:40:21 -04:00
Charlie Marsh
d097b49371 Remove Directive::None variant (#5543)
## Summary

This is creating some weird, impossible states. Make impossible states
unrepresentable!
2023-07-05 22:22:21 +00:00
Charlie Marsh
ea270da289 Move some MkDocs responsibilities around (#5542)
## Summary

Note that I've also changed from `mkdocs serve` to `mkdocs serve -f
mkdocs.generated.yml` to be clearer that this is a generated file.
2023-07-05 22:06:01 +00:00
Charlie Marsh
cdb9fda3b8 Add debug-based snapshot tests for noqa directive parsing (#5535)
## Summary

Better tests, helpful for future refactors.
2023-07-05 21:49:07 +00:00
Charlie Marsh
a0c0b74b6d Use structs for noqa Directive variants (#5533)
## Summary

No behavioral changes, just clearer (IMO) and with better documentation.
2023-07-05 21:37:32 +00:00
Charlie Marsh
1a2e444799 Use Insiders version of mkdocs-material (#5540)
## Summary

This PR migrates our `mkdocs-material` version to
[Insiders](https://squidfunk.github.io/mkdocs-material/insiders/), which
we can access now that we're sponsors.

We can't allow public access to the Insiders version, so we instead have
a private fork, which contains a deploy key that I've added as a
read-only Actions secret in this repo. (That is: the deploy key only
lets you read that one repo, and do nothing else.)

In general, non-Astral contributors can use the non-insiders version,
and everything is expected to "work", but without the insiders features
(they're intended to be ignored). See:
https://squidfunk.github.io/mkdocs-material/insiders/#compatibility.
2023-07-05 20:36:26 +00:00
qdegraaf
6f548d9872 [isort] Add --case-sensitive flag (#5539)
## Summary

Adds a `--case-sensitive` setting/flag to isort (default: `false`)
which, when set to `true` sorts imports case sensitively instead of case
insensitively.

Tests and Docs can be improved, can do that if the general idea of the
implementation is in order.

First `isort` edit so any and all feedback is welcomed even more than
usual.

## Test Plan

Added a fixture with an assortment of imports in various cases.

## Issue links

Closes: https://github.com/astral-sh/ruff/issues/5514
2023-07-05 16:10:53 -04:00
Charlie Marsh
5a74a8e5a1 Avoid syntax errors when rewriting str(dict) in f-strings (#5538)
Closes https://github.com/astral-sh/ruff/issues/5530.
2023-07-05 19:22:22 +00:00
Charlie Marsh
c5bfd1e877 Allow descriptor instantiations in dataclass fields (#5537)
## Summary

Per the Python documentation, dataclasses are allowed to instantiate
descriptors, like so:

```python
class IntConversionDescriptor:
  def __init__(self, *, default):
    self._default = default

  def __set_name__(self, owner, name):
    self._name = "_" + name

  def __get__(self, obj, type):
    if obj is None:
      return self._default

    return getattr(obj, self._name, self._default)

  def __set__(self, obj, value):
    setattr(obj, self._name, int(value))

@dataclass
class InventoryItem:
  quantity_on_hand: IntConversionDescriptor = IntConversionDescriptor(default=100)
```

Closes https://github.com/astral-sh/ruff/issues/4451.
2023-07-05 15:19:24 -04:00
Charlie Marsh
9e1039f823 Enable attribute lookups via semantic model (#5536)
## Summary

This PR enables us to resolve attribute accesses within files, at least
for static and class methods. For example, we can now detect that this
is a function access (and avoid a false-positive):

```python
class Class:
    @staticmethod
    def error():
        return ValueError("Something")


# OK
raise Class.error()
```

Closes #5487.

Closes #5416.
2023-07-05 15:19:14 -04:00
Tom Kuson
9478454b96 [pylint] Implement Pylint typevar-double-variance (C0131) (#5517)
## Summary

Implement Pylint `typevar-double-variance` (`C0131`) as
`type-bivariance` (`PLC0131`). Includes documentation. Related to #970.
Renamed the rule to be more clear (it's not immediately obvious what
'double' means, IMO).

The Pylint implementation checks only `TypeVar`, but this PR checks
`ParamSpec` as well.

## Test Plan

Added tests.

`cargo test`
2023-07-05 14:53:41 -04:00
Charlie Marsh
9a8e5f7877 Run cargo update (#5534)
```console
❯ cargo update
    Updating crates.io index
    Updating git repository `https://github.com/charliermarsh/LibCST`
    Updating git repository `https://github.com/astral-sh/RustPython-Parser.git`
    Updating git repository `https://github.com/youknowone/unicode_names2.git`
    Updating bitflags v2.3.2 -> v2.3.3
    Updating bstr v1.5.0 -> v1.6.0
    Updating clap v4.3.8 -> v4.3.11
    Updating clap_builder v4.3.8 -> v4.3.11
    Updating clap_complete v4.3.1 -> v4.3.2
    Updating colored v2.0.0 -> v2.0.4
    Removing hermit-abi v0.2.6
    Removing hermit-abi v0.3.1
      Adding hermit-abi v0.3.2
    Updating is-terminal v0.4.7 -> v0.4.8
    Updating itoa v1.0.6 -> v1.0.8
      Adding linux-raw-sys v0.4.3
    Updating num_cpus v1.15.0 -> v1.16.0
    Updating paste v1.0.12 -> v1.0.13
    Updating pin-project-lite v0.2.9 -> v0.2.10
    Updating quote v1.0.28 -> v1.0.29
    Updating regex v1.8.4 -> v1.9.0
    Updating regex-automata v0.1.10 -> v0.3.0
    Updating regex-syntax v0.7.2 -> v0.7.3
    Removing rustix v0.37.20
      Adding rustix v0.37.23
      Adding rustix v0.38.3
    Updating rustversion v1.0.12 -> v1.0.13
    Updating ryu v1.0.13 -> v1.0.14
    Updating serde v1.0.164 -> v1.0.166
    Updating serde_derive v1.0.164 -> v1.0.166
    Updating serde_json v1.0.99 -> v1.0.100
    Updating syn v2.0.22 -> v2.0.23
    Updating thiserror v1.0.40 -> v1.0.41
    Updating thiserror-impl v1.0.40 -> v1.0.41
    Updating unicode-ident v1.0.9 -> v1.0.10
    Updating uuid v1.3.4 -> v1.4.0
    Updating windows-targets v0.48.0 -> v0.48.1
```
2023-07-05 12:34:15 -04:00
Dhruv Manilawala
6fd71e6f53 Avoid triggering DTZ001-006 when using .astimezone() (#5524)
## Summary

Avoid triggering DTZ001-006 when using `.astimezone()`

## Test Plan

Added test cases to call `.astimezone()` on DTZ001-006

fixes: #5516
2023-07-05 00:18:59 -04:00
Charlie Marsh
dd60a3865c Avoid triggering unnecessary-map (C417) for late-bound lambdas (#5520)
Closes https://github.com/astral-sh/ruff/issues/5502.
2023-07-04 22:11:29 -04:00
Charlie Marsh
0726dc25c2 Add some additional users to the README (#5522) 2023-07-05 02:09:50 +00:00
Charlie Marsh
634ed8975c Add pip to the ecosystem-ci check (#5521) 2023-07-05 02:06:21 +00:00
Evan Rittenhouse
5100c56273 Add rule documentation template to scripts/add_rule.py (#5519) 2023-07-04 21:57:26 -04:00
Charlie Marsh
26a268a3ec Refactor the unnecessary-map (C417) implementation (#5518)
## Summary

No behavioral changes. Just refactors + adding a test for a false
positive, which I'll fix in a downstream PR.
2023-07-04 20:25:54 -04:00
Charlie Marsh
324455f580 Bump version to 0.0.277 (#5515) 2023-07-04 17:31:32 -04:00
Charlie Marsh
da1c320bfa Add .ipynb_checkpoints, .pyenv, .pytest_cache, and .vscode to default excludes (#5513)
## Summary

VS Code extensions are
[recommended](https://code.visualstudio.com/docs/python/settings-reference#_linting-settings)
to exclude `.vscode` and `site-packages`. Black also now omits
`.vscode`, `.pytest_cache`, and `.ipynb_checkpoints` by default.
Omitting `.pyenv` is similar to omitting virtual environments, but
really only matters in the context of VS Code (see:
https://github.com/astral-sh/ruff/discussions/5509).

Closes: #5510.
2023-07-04 20:25:16 +00:00
Charlie Marsh
485d997d35 Tweak prefix match to use .all_rules() (#5512)
## Summary

No behavior change, but I think this is a little cleaner.
2023-07-04 20:02:57 +00:00
Aarni Koskela
d7214e77e6 Add ruff rule --all subcommand (with JSON output) (#5059)
## Summary

This adds a `ruff rule --all` switch that prints out a human-readable
Markdown or a machine-readable JSON document of the lint rules known to
Ruff.

I needed a machine-readable document of the rules [for a
project](https://github.com/astral-sh/ruff/discussions/5078), and
figured it could be useful for other people – or tooling! – to be able
to interrogate Ruff about its arcane knowledge.

The JSON output is an array of the same objects printed by `ruff rule
--format=json`.

## Test Plan

I ran `ruff rule --all --format=json`. I think more might be needed, but
maybe a snapshot test is overkill?
2023-07-04 19:45:38 +00:00
Charlie Marsh
952c623102 Avoid returning first-match for rule prefixes (#5511)
Closes #5495, but there's a TODO here to improve this further. The
current `from_code` implementation feels really indirect.
2023-07-04 19:23:05 +00:00
konsti
0a26201643 Merge clippy and clippy (wasm) jobs on CI (#5447)
## Summary

The clippy wasm job rarely fails if regular clippy doesn't, wasm clippy
still compiles a lot of native dependencies for the proc macro and we
have less CI jobs overall, so i think this an improvement to our CI.

```shell
$ CARGO_TARGET_DIR=target-wasm cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -j 2 -- -D warnings
$ du -sh target-wasm/*
12K	target-wasm/CACHEDIR.TAG
582M	target-wasm/debug
268M	target-wasm/wasm32-unknown-unknown
```

## Test plan

n/a
2023-07-04 15:22:00 -04:00
Tom Kuson
0e67757edb [pylint] Implement Pylint typevar-name-mismatch (C0132) (#5501)
## Summary

Implement Pylint `typevar-name-mismatch` (`C0132`) as
`type-param-name-mismatch` (`PLC0132`). Includes documentation. Related
to #970.

The Pylint implementation checks only `TypeVar`, but this PR checks
`TypeVarTuple`, `ParamSpec`, and `NewType` as well. This seems to better
represent the Pylint rule's [intended
behaviour](https://github.com/pylint-dev/pylint/issues/5224).

Full disclosure: I am not a fan of the translated name and think it
should probably be different.

## Test Plan

`cargo test`
2023-07-04 18:49:43 +00:00
Charlie Marsh
c395e44bd7 Avoid PERF rules for iteration-dependent assignments (#5508)
## Summary

We need to avoid raising "rewrite as a comprehension" violations in
cases like:

```python
d = defaultdict(list)

for i in [1, 2, 3]:
    d[i].append(i**2)
```

Closes https://github.com/astral-sh/ruff/issues/5494.

Closes https://github.com/astral-sh/ruff/issues/5500.
2023-07-04 18:21:05 +00:00
Charlie Marsh
75da72bd7f Update documentation to list double-quote preference first (#5507)
Closes https://github.com/astral-sh/ruff/issues/5496.
2023-07-04 18:06:01 +00:00
Charlie Marsh
521e6de2c8 Fix eval detection for suspicious-eval-usage (#5506)
Closes https://github.com/astral-sh/ruff/issues/5505.
2023-07-04 18:01:29 +00:00
Thomas de Zeeuw
0b963ddcfa Add unreachable code rule (#5384)
Co-authored-by: Thomas de Zeeuw <thomas@astral.sh>
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-07-04 14:27:23 +00:00
konsti
937de121f3 check-formatter-stability: Remove newlines and add --error-file (#5491)
## Summary

This makes the output of `check-formatter-stability` more concise by
removing extraneous newlines. It also adds a `--error-file` option to
that script that allows creating a file with just the errors (without
the status messages) to share with others.

## Test Plan

I ran it over CPython and looked at the output. I then added the
`--error-file` option and looked at the contents of the file
2023-07-04 07:54:35 +00:00
konsti
787e2fd49d Format import statements (#5493)
## Summary

Format import statements in all their variants. Specifically, this
implemented formatting `StmtImport`, `StmtImportFrom` and `Alias`.

## Test Plan

I added some custom snapshots, even though this has been covered well by
black's tests.
2023-07-04 07:07:20 +00:00
Aarni Koskela
6acc316d19 Turn Linters', etc. implicit into_iter()s into explicit rules() (#5436)
## Summary

As discussed on ~IRC~ Discord, this will make it easier for e.g. the
docs generation stuff to get all rules for a linter (using
`all_rules()`) instead of just non-nursery ones, and it also makes it
more Explicit Is Better Than Implicit to iterate over linter rules.

Grepping for `Item = Rule` reveals some remaining implicit
`IntoIterator`s that I didn't feel were necessarily in scope for this
(and honestly, iterating over a `RuleSet` makes sense).
2023-07-03 19:35:16 -04:00
konsti
a647f31600 Don't add a magic trailing comma for a single entry (#5463)
## Summary

If a comma separated list has only one entry, black will respect the
magic trailing comma, but it will not add a new one.

The following code will remain as is:

```python
b1 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
]
b2 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa,
]
b3 = [
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa,
    aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
]
```

## Test Plan

This was first discovered in
7eeadc82c2/django/contrib/admin/checks.py (L674-L681),
which i've minimized into a call test.

I've added tests for the three cases (one entry + no comma, one entry +
comma, more than one entry) to the list tests.

The diffs from the black tests get smaller.
2023-07-03 21:48:44 +02:00
Charlie Marsh
3992c47c00 Bump version to 0.0.276 (#5488) 2023-07-03 18:02:49 +00:00
Charlie Marsh
8de5a3d29d Allow Final assignments in stubs (#5490)
## Summary

This fixes one incompatibility with `flake8-pyi`, and gives us a clean
pass on `typeshed`.
2023-07-03 17:57:49 +00:00
Charlie Marsh
ed1dd09d02 Refine some perflint rules (#5484)
## Summary

Removing some false positives based on running over `zulip`.

`PERF401` now also detects cases like:

```py
original = list(range(10000))
filtered = []
for i in original:
    filtered.append(i * i)
```

Previously, these were caught by the list-copy rule, but these too need
comprehensions.
2023-07-03 13:53:17 -04:00
Charlie Marsh
ca497fabbd Remove some diagnostics.extend calls (#5483)
## Summary

It's more efficient (and more idiomatic for us) to pass in the `Checker`
directly.
2023-07-03 16:47:23 +00:00
Charlie Marsh
00fbbe4223 Remove some additional manual iterator matches (#5482)
## Summary

I've done a few of these PRs, I thought I'd caught them all, but missed
this pattern.
2023-07-03 16:29:59 +00:00
Charlie Marsh
dadad0e9ed Remove some allocations in argument detection (#5481)
## Summary

Drive-by PR to remove some allocations around argument name matching.
2023-07-03 12:21:26 -04:00
Charlie Marsh
d2450c25ab Audit remove_argument usages to use end-of-function (#5480)
## Summary

This PR applies the fix in #5478 to a variety of other call-sites, and
fixes some other range hygienic stuff in the rules that were modified.
2023-07-03 12:21:01 -04:00
Harutaka Kawamura
1e4b88969c Fix unnecessary-encode-utf8 to fix encode on parenthesized strings correctly (#5478)
## Summary

Fixes #5477

## Test Plan

New test cases.
2023-07-03 10:11:09 -04:00
Louis Dispa
dc072537e5 Fix python_formatter generate.py with rust path (#5475)
## Summary

This PR fix an issue with the `generate.py` file of the python
formatter.
Since https://github.com/astral-sh/ruff/pull/5369 the [node.rs
file](f51dc20497/crates/ruff_python_ast/src/node.rs)
used to generate the types now has `ast::` in the enum.

```rust
pub enum AnyNode {
   ModModule(ModModule),
   ModInteractive(ModInteractive),
   ModExpression(ModExpression),
   ModFunctionType(ModFunctionType),
   ...
```

And now:

```rust
pub enum AnyNode {
   ModModule(ast::ModModule),
   ModInteractive(ast::ModInteractive),
   ModExpression(ast::ModExpression),
   ModFunctionType(ast::ModFunctionType),
   ...
```

The python script was not parsing rust paths. This PR adds the
possibility to have it.

## Test Plan

This was tested locally.

### Script output

Before

```
['ast::ModModule),', 'ast::ModInteractive),', 'ast::ModExpression),', 'ast::ModFunctionType),', 'ast::StmtFunctionDef),', 'ast::StmtAsyncFunctionDef),', 'ast::StmtClassDef),', 'ast::StmtReturn),', 'ast::StmtDelete),', 'ast::StmtAssign),', 'ast::StmtAugAssign),', 'ast::StmtAnnAssign),', 'ast::StmtFor),', 'ast::StmtAsyncFor),', 'ast::StmtWhile),', 'ast::StmtIf),', 'ast::StmtWith),', 'ast::StmtAsyncWith),', 'ast::StmtMatch),', 'ast::StmtRaise),', 'ast::StmtTry),', 'ast::StmtTryStar),', 'ast::StmtAssert),', 'ast::StmtImport),', 'ast::StmtImportFrom),', 'ast::StmtGlobal),', 'ast::StmtNonlocal),', 'ast::StmtExpr),', 'ast::StmtPass),', 'ast::StmtBreak),', 'ast::StmtContinue),', 'ast::ExprBoolOp),', 'ast::ExprNamedExpr),', 'ast::ExprBinOp),', 'ast::ExprUnaryOp),', 'ast::ExprLambda),', 'ast::ExprIfExp),', 'ast::ExprDict),', 'ast::ExprSet),', 'ast::ExprListComp),', 'ast::ExprSetComp),', 'ast::ExprDictComp),', 'ast::ExprGeneratorExp),', 'ast::ExprAwait),', 'ast::ExprYield),', 'ast::ExprYieldFrom),', 'ast::ExprCompare),', 'ast::ExprCall),', 'ast::ExprFormattedValue),', 'ast::ExprJoinedStr),', 'ast::ExprConstant),', 'ast::ExprAttribute),', 'ast::ExprSubscript),', 'ast::ExprStarred),', 'ast::ExprName),', 'ast::ExprList),', 'ast::ExprTuple),', 'ast::ExprSlice),', 'ast::ExceptHandlerExceptHandler),', 'ast::PatternMatchValue),', 'ast::PatternMatchSingleton),', 'ast::PatternMatchSequence),', 'ast::PatternMatchMapping),', 'ast::PatternMatchClass),', 'ast::PatternMatchStar),', 'ast::PatternMatchAs),', 'ast::PatternMatchOr),', 'ast::TypeIgnoreTypeIgnore),', 'Comprehension),', 'Arguments),', 'Arg),', 'ArgWithDefault),', 'Keyword),', 'Alias),', 'WithItem),', 'MatchCase),', 'Decorator),']

error: unexpected closing delimiter: `)`
 --> <stdin>:3:55
  |
2 |             use ruff_formatter::{write, Buffer, FormatResult};
  |                                 - this opening brace...     - ...matches this closing brace
3 |             use rustpython_parser::ast::ast::ModModule),;
  |                                                       ^ unexpected closing delimiter

Traceback (most recent call last):
  File "/Users/ldispa/Documents/perso/ruff/crates/ruff_python_formatter/generate.py", line 100, in <module>
    node_path.write_text(rustfmt(code))
                         ^^^^^^^^^^^^^
  File "/Users/ldispa/Documents/perso/ruff/crates/ruff_python_formatter/generate.py", line 12, in rustfmt
    return check_output(["rustfmt", "--emit=stdout"], input=code, text=True)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/subprocess.py", line 466, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/subprocess.py", line 571, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['rustfmt', '--emit=stdout']' returned non-zero exit status 1.
```

After:
```
['ModModule', 'ModInteractive', 'ModExpression', 'ModFunctionType', 'StmtFunctionDef', 'StmtAsyncFunctionDef', 'StmtClassDef', 'StmtReturn', 'StmtDelete', 'StmtAssign', 'StmtAugAssign', 'StmtAnnAssign', 'StmtFor', 'StmtAsyncFor', 'StmtWhile', 'StmtIf', 'StmtWith', 'StmtAsyncWith', 'StmtMatch', 'StmtRaise', 'StmtTry', 'StmtTryStar', 'StmtAssert', 'StmtImport', 'StmtImportFrom', 'StmtGlobal', 'StmtNonlocal', 'StmtExpr', 'StmtPass', 'StmtBreak', 'StmtContinue', 'ExprBoolOp', 'ExprNamedExpr', 'ExprBinOp', 'ExprUnaryOp', 'ExprLambda', 'ExprIfExp', 'ExprDict', 'ExprSet', 'ExprListComp', 'ExprSetComp', 'ExprDictComp', 'ExprGeneratorExp', 'ExprAwait', 'ExprYield', 'ExprYieldFrom', 'ExprCompare', 'ExprCall', 'ExprFormattedValue', 'ExprJoinedStr', 'ExprConstant', 'ExprAttribute', 'ExprSubscript', 'ExprStarred', 'ExprName', 'ExprList', 'ExprTuple', 'ExprSlice', 'ExceptHandlerExceptHandler', 'PatternMatchValue', 'PatternMatchSingleton', 'PatternMatchSequence', 'PatternMatchMapping', 'PatternMatchClass', 'PatternMatchStar', 'PatternMatchAs', 'PatternMatchOr', 'TypeIgnoreTypeIgnore', 'Comprehension', 'Arguments', 'Arg', 'ArgWithDefault', 'Keyword', 'Alias', 'WithItem', 'MatchCase', 'Decorator']
```
2023-07-03 16:07:57 +02:00
konsti
7ac9e0252e Document Checking formatter stability and panics (#5415)
This adds the documentation, but ideally we should add the CI first
2023-07-03 11:22:19 +02:00
konsti
ca6ff72404 Change generator formatting dummy to include NOT_YET_IMPLEMENTED (#5464)
## Summary

Change generator formatting dummy to include `NOT_YET_IMPLEMENTED`. This
makes it easier to correctly identify them as dummies

## Test Plan

This is a dummy change
2023-07-03 09:11:14 +02:00
Charlie Marsh
94ac2c4e1b Reorganize some flake8-pyi rules (#5472) 2023-07-03 04:39:22 +00:00
qdegraaf
93b2bd7184 [perflint] Add PERF401 and PERF402 rules (#5298)
## Summary

Adds `PERF401` and `PERF402` mirroring `W8401` and `W8402` from
https://github.com/tonybaloney/perflint

Implementation is not super smart but should be at parity with upstream
implementation judging by:
c07391c176/perflint/comprehension_checker.py (L42-L73)

It essentially checks:

- If the body of a for-loop is just one statement
- If that statement is an `if` and the if-statement contains a call to
`append()` we flag `PERF401` and suggest a list comprehension
- If that statement is a plain call to `append()` or `insert()` we flag
`PERF402` and suggest `list()` or `list.copy()`

I've set the violation to only flag the first append call in a long
`if-else` statement for `PERF401`. Happy to change this to some other
location or make it multiple violations if that makes more sense.

## Test Plan

Fixtures were added with the relevant scenarios for both rules

## Issue Links

Refers: https://github.com/astral-sh/ruff/issues/4789
2023-07-03 04:03:09 +00:00
Justin Prieto
0bff4ed4d3 [flake8-pyi] Implement PYI002, PYI003, PYI004, PYI005 (#5457)
## Summary

Implements flake8-pyi checks 002, 003, 004, 005. The logic is a bit
complex, as you can see in the [original
code](57921813c1/pyi.py (L1403C18-L1403C18)).

ref: #848 

## Test Plan

Updated snapshot tests. Ran flake8 to double check lints, and ran ruff
with all PYI lints enabled to check for incorrect overlapping lint
errors.
2023-07-02 23:52:16 -04:00
Anders Kaseorg
df13e69c3c Format let-else with rustfmt nightly (#5461)
Support for `let…else` formatting was just merged to nightly
(rust-lang/rust#113225). Rerun `cargo fmt` with Rust nightly 2023-07-02
to pick this up. Followup to #939.

Signed-off-by: Anders Kaseorg <andersk@mit.edu>
2023-07-03 02:13:35 +00:00
Charlie Marsh
c8b9a46e2b [pyupgrade] Restore the keep-runtime-typing setting (#5470)
## Summary

This PR reverts #4427. See the included documentation for a detailed
explanation.

Closes #5434.
2023-07-03 02:11:31 +00:00
Charlie Marsh
6cc04d64e4 [flake8-django] Skip duplicate violations in DJ012 (#5469)
## Summary

This PR reduces the noise from `DJ012` by emitting a single violation
when you have multiple consecutive violations of the same "type".

For example, given:

```py
class MultipleConsecutiveFields(models.Model):
    """Model that contains multiple out-of-order field definitions in a row."""


    class Meta:
        verbose_name = "test"

    first_name = models.CharField(max_length=32)
    last_name = models.CharField(max_length=32)
```

It's convenient to only error on `first_name`, and not `last_name`,
since we're really flagging that the _section_ is out-of-order.

Closes #5465.
2023-07-02 21:09:49 -04:00
Charlie Marsh
d0b2fffb87 [numpy] Add numpy-deprecated-function (NPY003) (#5468)
## Summary

Closes #5456.
2023-07-02 20:50:14 -04:00
Charlie Marsh
b32d1e8d78 Detect consecutive, non-newline-delimited NumPy sections (#5467)
## Summary

Given a docstring like:

```py
def f(a: int, b: int) -> int:
    """Showcase function.
    Parameters
    ----------
    a : int
        _description_
    b : int
        _description_
    Returns
    -------
    int
        _description
    """
```

We were failing to identify `Returns` as a section, because the previous
line was neither empty nor ended with punctuation. This was causing a
false negative, where by we weren't flagging a missing line before
`Returns`. So, the very reason for the rule (no blank line) was causing
us to fail to catch it.

Note that, we did have a test case for this, which was working properly:

```py
def f() -> int:
    """Showcase function.
    Parameters
    ----------
    Returns
    -------
    """
```

...because the line before `Returns` "ends in a punctuation mark" (`-`).

Closes #5442.
2023-07-02 20:29:45 -04:00
Charlie Marsh
af7051b976 Include BaseException in B017 rule (#5466)
Closes #5462.
2023-07-02 20:18:33 -04:00
Micha Reiser
f0ec9ecd67 Show BestFitting mode if it isn't FirstLine (#5452) 2023-06-30 09:49:00 +00:00
Micha Reiser
f9129e435a Normalize '\r' in string literals to '\n'
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR normalizes line endings inside of strings to `\n` as required by the printer.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I added a new test using `\r\n` and ran the ecosystem check. There are no remaining end of line panics. 


https://gist.github.com/MichaReiser/8f36b1391ca7b48475b3a4f592d74ff4

<!-- How was it tested? -->
2023-06-30 10:13:23 +02:00
Micha Reiser
dc65007fe9 Use rayon to parallelize the stability check
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR uses rayon to parallelize the stability check by scheduling each project as its own task.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I ran the ecosystem check. It now makes use of all cores (except at the end, there are some large projects). 

## Performance

The check now completes in minutes where it took about 30 minutes before.

<!-- How was it tested? -->
2023-06-30 10:05:25 +02:00
Micha Reiser
9c2a75284b Preserve parentheses around left side of binary expression
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR fixes an issue where the binary expression formatting removed parentheses around the left hand side of an expression.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I added a new regression test and re-ran the ecosystem check. It brings down the `check-formatter-stability` output from a 3.4MB file down to 900KB. 

<!-- How was it tested? -->
2023-06-30 09:52:14 +02:00
Micha Reiser
ae25638b0b Update Black tests (#5438) 2023-06-30 06:32:50 +00:00
Micha Reiser
f7969cf23c ecosystem: Run git command with no human interaction flag (#5435) 2023-06-29 09:19:11 +02:00
Micha Reiser
955e9ef821 Fix invalid syntax for binary expression in unary op (#5370) 2023-06-29 08:09:26 +02:00
Micha Reiser
38189ed913 Fix invalid printer IR error (#5422) 2023-06-29 08:09:13 +02:00
David Szotten
ca5e10b5ea format StmtTryStar (#5418) 2023-06-29 08:07:33 +02:00
Charlie Marsh
a973019358 Rewrite a variety of .contains() calls as matches! statements (#5432)
## Summary

These have the potential to be much more efficient, as we've seen in the
past.
2023-06-28 22:42:27 -04:00
Charlie Marsh
aa887d5a1d Use "manual" fixability for E731 in shadowed context (#5430)
## Summary

This PR makes E731 a "manual" fix in one other context: when the lambda
is shadowing another variable in the scope. Function declarations (with
shadowing) cause issues for type checkers, and so rewriting an
annotation, e.g., in branches of an `if` statement can lead to failures.

Closes https://github.com/astral-sh/ruff/issues/5421.
2023-06-28 22:00:06 -04:00
Charlie Marsh
72f7f11bac Use matches! for reserved attribute lookup (#5431) 2023-06-29 01:52:11 +00:00
Tom Kuson
5aa2a90e17 Add documentation to flake8-logging-format rules (#5417)
## Summary

Completes the documentation for the `flake8-logging-format` rules.
Related to #2646.

I included both the `flake8-logging-format` recommendation to use the
`extra` keyword and the Pylint recommendation to pass format values as
parameters so that formatting is done lazily, as #970 suggests the
Pylint logging rules are covered by this ruleset. Using lazy formatting
via parameters is probably more common than avoiding formatting entirely
in favour of the `extra` argument, regardless.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-29 01:30:11 +00:00
Charlie Marsh
0e89c94947 Run shadowed-variable analyses in deferred handlers (#5181)
## Summary

This PR extracts a bunch of complex logic from `add_binding`, instead
running the the shadowing rules in the deferred handler, thereby
decoupling the binding phase (during which we build up the semantic
model) from the analysis phase, and generally making `add_binding` much
more focused.

This was made possible by improving the semantic model to better handle
deletions -- previously, we'd "lose track" of bindings if they were
deleted, which made this kind of refactor impossible.

## Test Plan

We have good automated coverage for this, but I want to benchmark it
separately.
2023-06-29 00:08:18 +00:00
Eric H
139a9f757b Update default configuration.md to mention C901 rule (#5397) 2023-06-28 21:22:16 +00:00
Charlie Marsh
c5e20505f8 Remove an unsafe access in the resolver (#5428) 2023-06-28 19:08:10 +00:00
Charlie Marsh
69c4b7fa11 Add dedicated struct for implicit imports (#5427)
## Summary

This was some feedback on a prior PR that I decided to act on
separately.
2023-06-28 18:55:43 +00:00
Charlie Marsh
0e12eb3071 Add a snapshot test for native module resolution (#5423) 2023-06-28 18:16:39 +00:00
Charlie Marsh
864f50a3a4 Remove all unwrap calls from the resolver (#5426) 2023-06-28 18:06:17 +00:00
Charlie Marsh
4d90a5a9bc Move resolver tests out to top-level (#5424)
## Summary

These are really tests for the entire crate.
2023-06-28 13:25:37 -04:00
Charlie Marsh
1d2d015bc5 Make standard input detection robust to invalid arguments (#5393)
## Summary

This PR fixes a silent failure that manifested itself in
https://github.com/astral-sh/ruff-vscode/issues/238. In short, if the
user provided invalid arguments to Ruff in the VS Code extension (like
`"ruff.args": ["a"]`), then we generated something like the following
command:

```console
/path/to/ruff --force-exclude --no-cache --no-fix --format json - --fix a --stdin-filename /path/to/file.py
```

Since this contains both `-` and `a` as the "input files", Ruff would
treat this as if we're linting the files names `-` and `a`, rather than
linting standard input.

This PR modifies out standard input detection to force standard input
when `--stdin-filename` is present, or at least one file is `-`. (We
then warn and ignore the others.)
2023-06-28 14:52:23 +00:00
Charlie Marsh
ea7bb199bc Fill-in missing implementation for is_native_module_file_name (#5410)
## Summary

This was just an oversight -- the last remaining `todo!()` that I never
filled in. We clearly don't have any test coverage for it yet, but this
mimics the Pyright implementation.
2023-06-28 14:50:54 +00:00
Charlie Marsh
979049b2a6 Make lib iteration platform-specific (#5406) 2023-06-28 13:52:20 +00:00
Charlie Marsh
6587fb844a Add snapshot tests for resolver (#5404)
## Summary

This PR adds some snapshot tests for the resolver based on executing
resolutions within a "mock" of the Airflow repo (that is: a folder that
contains a subset of the repo's files, but all empty, and with an
only-partially-complete virtual environment). It's intended to act as a
lightweight integration test, to enable us to test resolutions on a
"real" project without adding a dependency on Airflow itself.
2023-06-28 13:38:51 +00:00
Dhruv Manilawala
a68a86e18b fixup! Consider Jupyter index for code frames (--show-source) (#5402) (#5414) 2023-06-28 10:25:05 +00:00
Christian Clauss
b42d76494c types.rs: fnmatch url should point to current Python docs (#5413)
Like #5412
2023-06-28 15:54:13 +05:30
David Szotten
c7adb9117f format StmtAsyncWith (#5376)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-06-28 10:21:44 +00:00
David Szotten
1979103ec0 Format StmtTry (#5222)
Co-authored-by: Micha Reiser <micha@reiser.io>
2023-06-28 10:02:15 +00:00
Christian Clauss
9e2fd0c620 ruff rule SLOT uses URL to current Python docs (#5412)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

<!-- What's the purpose of the change? What does it do, and why? -->
Currently the URL at the bottom of the `ruff rule SLOT00x` output points
to Python 3.7 docs.
Given that Python 3.7 is now end-of-life (as of yesterday), let's
instead point users to the current Python docs.

## Test Plan

<!-- How was it tested? -->
2023-06-28 09:48:52 +00:00
Charlie Marsh
366edc5a3f Fix string annotation in docs (#5411) 2023-06-28 03:29:56 +00:00
Dhruv Manilawala
2aecaf5060 Consider Jupyter index for code frames (--show-source) (#5402)
## Summary

Consider Jupyter index for code frames (`--show-source`).

This solves two problems as mentioned in the linked issue:

> Omit any contents from adjoining cells

If the Jupyter index is present, we'll use that to check if the
surrounding
lines belong to the same cell as the content line. If not, we'll skip
that line
until we either reach the one which does or we reach the content line.

> code frame line number

If the Jupyter index is present, we'll use that to get the actual start
line in
corresponding to the computed start index.

## Test Plan

`cargo run --bin ruff -- check --no-cache --isolated --select=ALL --show-source /path/to/notebook.ipynb`

fixes: #5395
2023-06-28 08:54:51 +05:30
Dhruv Manilawala
d19324df69 Add Jupyter integration to the docs (#5403)
## Summary

Add Jupyter integration to the docs, specifically the Configuration and
FAQ sections.

## Test Plan

`mkdocs serve` and check that the new sections are visible and
functional.

fixes: #5396
2023-06-28 00:27:24 +00:00
Marti Raudsepp
2c99b268c6 Exclude docstrings from PYI053 (#5405)
## Summary

The `Y053` rule of `flake8-pyi` ignores docstrings, it only triggers on
other string literals.

The separate `Y021/PYI021` rule exists to disallow docstrings.

## Test Plan

Added some `# OK` test cases to `PYI053.py(i)` files.
2023-06-28 00:19:20 +00:00
Charlie Marsh
56f73de0cb Misc. clean-up for import resolver (#5401)
## Summary

Renaming functions, adding documentation, refactoring the test
infrastructure a bit.
2023-06-27 19:27:12 +00:00
Tom Kuson
a0a93a636f Implement Pylint single-string-used-for-slots (C0205) as single-string-slots (PLC0205) (#5399)
## Summary

Implement Pylint rule `single-string-used-for-slots` (`C0205`) as
`single-string-slots` (`PLC0205`). This rule checks for single strings
being assigned to `__slots__`. For example

```python
class Foo:
    __slots__: str = "bar"

    def __init__(self, bar: str) -> None:
        self.bar = bar
```

should be

```python
class Foo:
    __slots__: tuple[str, ...] = ("bar",)

    def __init__(self, bar: str) -> None:
        self.bar = bar
```

Related to #970. Includes documentation.

## Test Plan

`cargo test`
2023-06-27 18:33:58 +00:00
Tom Kuson
035f8993f4 Complete documentation for pydocstyle rules (#5387)
## Summary

Completes the documentation for the `pydocstyle` ruleset. Related to
#2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-27 18:12:21 +00:00
Charlie Marsh
032b967b05 Enable --watch for Jupyter notebooks (#5394)
## Summary

The list of extensions that support watching is hard-coded
(unfortunately); this PR adds `.ipynb` to the list.
2023-06-27 12:53:47 -04:00
Dhruv Manilawala
962479d943 Replace same length equal line with dash line in D407 (#5383)
## Summary

Replace same length equal line with dash line in D407

Do we want to update the message and autofix title to reflect this
change?

## Test Plan

Added test cases for:
- Equal line length == dash line length
- Equal line length != dash line length

fixes: #5378
2023-06-27 16:50:20 +00:00
Evan Rittenhouse
ff0d0ab7a0 Add applicability to pydocstyle (#5390) 2023-06-27 12:40:19 -04:00
Evan Rittenhouse
0585e14d3b Add applicability to flake8_pytest_style (#5389) 2023-06-27 12:39:56 -04:00
Charlie Marsh
1ed227a1e0 Port Pyright's import resolver to Rust (#5381)
## Summary

This PR contains the first step towards enabling robust first-party,
third-party, and standard library import resolution in Ruff (including
support for `typeshed`, stub files, native modules, etc.) by porting
Pyright's import resolver to Rust.

The strategy taken here was to start with a more-or-less direct port of
the Pyright's TypeScript resolver. The code is intentionally similar,
and the test suite is effectively a superset of Pyright's test suite for
its own resolver. Due to the nature of the port, the code is very, very
non-idiomatic for Rust. The code is also entirely unused outside of the
test suite, and no effort has been made to integrate it with the rest of
the codebase.

Future work will include:

- Refactoring the code (now that it works) to match Rust and Ruff
idioms.
- Further testing, in practice, to ensure that the resolver can resolve
imports in a complex project, when provided with a virtual environment
path.
- Caching, to minimize filesystem lookups and redundant resolutions.
- Integration into Ruff itself (use Ruff's existing settings, find rules
that can make use of robust resolution, etc.)
2023-06-27 16:15:07 +00:00
Charlie Marsh
502e15585d Ignore unpacking in iteration-over-set (#5392)
Closes #5386.
2023-06-27 15:33:42 +00:00
konstin
520f4f33c3 Fix ruff_dev repeat by removing short argument (#5388)
ruff_dev repeat recently broke (i think with the cargo update?):

> thread 'main' panicked at 'Command repeat: Short option names must be
unique for each argument, but '-n' is in use by both 'no_cache' and
'repeat''

This fixes this by removing the short argument.
2023-06-27 13:29:20 +00:00
konstin
7f6cb9dfb5 Format call expressions (without call chaining) (#5341)
## Summary

This formats call expressions with magic trailing comma and parentheses
behaviour but without call chaining

## Test Plan

Lots of new test fixtures, including some that don't work yet
2023-06-27 09:29:40 +00:00
David Szotten
50a7769d69 magic trailing comma for ExprList (#5365) 2023-06-26 21:59:01 +02:00
Evan Rittenhouse
190bed124f [perflint] Implement try-except-in-loop (PERF203) (#5166)
## Summary

Implements PERF203 from #4789, which throws if a `try/except` block is
inside of a loop. Not sure if we want to extend the diagnostic to the
`except` as well, but I thought that that may get a little messy. We may
also want to just throw on the word `try` - open to suggestions though.

## Test Plan
`cargo test`
2023-06-26 17:34:37 +00:00
Charlie Marsh
d53b986fd4 Fix autofix capabilities in playground (#5375)
## Summary

These had just bitrotted over time -- we were no longer passing along
the row-and-column indices, etc.

## Test Plan

![Screen Shot 2023-06-26 at 12 03 41
PM](https://github.com/astral-sh/ruff/assets/1309177/6791330d-010b-45d3-91ef-531d4745193f)
2023-06-26 16:40:28 +00:00
Charlie Marsh
8a1bb7a5af Fix version number in playground (#5372)
## Summary

`v0.0.275` in the top-right was showing `v0.0.0` at all times.

## Test Plan

![Screen Shot 2023-06-26 at 11 31 16
AM](https://github.com/astral-sh/ruff/assets/1309177/e6cd0e19-6a5f-4b46-a060-54f492524737)
2023-06-26 15:56:12 +00:00
Dhruv Manilawala
2fc38d81e6 Experimental release for Jupyter notebook integration (#5363)
## Summary

Experimental release for Jupyter Notebook integration.

Currently, this requires a user to explicitly opt-in using the
[include](https://beta.ruff.rs/docs/settings/#include) configuration:

```toml
[tool.ruff]
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]
```

Or, a user can pass in the file directly:

```sh
ruff check path/to/notebook.ipynb
```

For known limitations, please refer #5188 

## Test Plan

Following command should work without the `--all-features` flag:

```sh
cargo dev round-trip /path/to/notebook.ipynb
```

Following command should work with the above config file along with
`select = ["ALL"]`:

```sh
cargo run --bin ruff -- check --no-cache --config=../test-repos/openai-cookbook/pyproject.toml --fix ../test-repos/openai-cookbook/
```

Passing the Jupyter notebook directly:

```sh
cargo run --bin ruff -- check --no-cache --isolated --select=ALL --fix ../test-repos/openai-cookbook/examples/Classification_using_embeddings.ipynb
```
2023-06-26 21:22:42 +05:30
Charlie Marsh
fa1b85b3da Remove prelude from ruff_python_ast (#5369)
## Summary

Per @MichaReiser, this is causing more confusion than it is helpful.
2023-06-26 11:43:49 -04:00
Tom Kuson
baa7264ca4 Add documentation for flake8-2020 (#5366)
## Summary

Completes the documentation for the `flake8-2020` ruleset. Related to
#2646 .

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-26 15:24:42 +00:00
Tom Kuson
fde3f09370 Add documentation missing docstring rules (D1XX) (#5330)
## Summary

Add documentation to the `D1XX` rules that flag missing docstrings. 

The examples are quite long and docstrings practices vary a lot between
projects, so I thought it would be best that the documentation for these
rules be their own PR separate to the other `pydocstyle` rules.

Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-26 14:44:46 +00:00
David Szotten
d00559e42a format StmtWith (#5350) 2023-06-26 15:09:06 +01:00
Micha Reiser
49cabca3e7 Format implicit string continuation (#5328) 2023-06-26 12:41:47 +00:00
Micha Reiser
313711aaf9 Prefer the configured quote style
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR extends the string formatting to respect the configured quote style.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

Extended the string test with new cases and set it up to run twice: Once with the `quote_style: Doube`, and once with `quote_style: Single` single and double quotes. 

<!-- How was it tested? -->
2023-06-26 14:24:25 +02:00
Micha Reiser
f18a1f70de Add tests for skip magic trailing comma
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR adds tests that verify that the magic trailing comma is not respected if disabled in the formatter options. 

Our test setup now allows to create a `<fixture-name>.options.json` file that contains an array of configurations that should be tested. 

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

It's all about tests :) 

<!-- How was it tested? -->
2023-06-26 14:15:55 +02:00
Micha Reiser
dd0d1afb66 Create PyFormatOptions
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR adds a new `PyFormatOptions` struct that stores the python formatter options. 
The new options aren't used yet, with the exception of magical trailing commas and the options passed to the printer. 
I'll follow up with more PRs that use the new options (e.g. `QuoteStyle`).

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

`cargo test` I'll follow up with a new PR that adds support for overriding the options in our fixture tests.
2023-06-26 14:02:17 +02:00
konstin
a52cd47c7f Fix attribute chain own line comments (#5340)
## Motation

Previously,
```python
x = (
    a1
    .a2
    # a
    .  # b
    # c
    a3
)
```
got formatted as
```python
x = a1.a2
# a
.  # b
# c
a3
```
which is invalid syntax. This fixes that.

## Summary

This implements a basic form of attribute chaining
(<https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#call-chains>)
by checking if any inner attribute access contains an own line comment,
and if this is the case, adds parentheses around the outermost attribute
access while disabling parentheses for all inner attribute expressions.
We want to replace this with an implementation that uses recursion or a
stack while formatting instead of in `needs_parentheses` and also
includes calls rather sooner than later, but i'm fixing this now because
i'm uncomfortable with having known invalid syntax generation in the
formatter.

## Test Plan

I added new fixtures.
2023-06-26 09:13:07 +00:00
Micha Reiser
8879927b9a Use insta::glob instead of fixture macro (#5364) 2023-06-26 08:46:18 +00:00
Charlie Marsh
dce6a046b0 Add tests for escape-sequence-in-docstring (#5362)
## Summary

Looks like I added a regression in #5360. This PR fixes it and adds
dedicated tests to avoid it in the future.
2023-06-25 22:42:12 -04:00
Charlie Marsh
18c73c1f9b Improve backslash-detection rule for docstrings (#5360) 2023-06-26 01:58:20 +00:00
Charlie Marsh
19c221a2d2 Use matches for os-error-alias (#5361) 2023-06-26 01:57:52 +00:00
Tom Kuson
fd0c3faa70 Add documentation to rules that check docstring quotes (D3XX) (#5351)
## Summary

Add documentation to the `D3XX` rules that check for issues with
docstring quotes. Related to #2646.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-25 22:34:03 +00:00
Charlie Marsh
1fe4073b56 Update the invalid-escape-sequence rule (#5359)
Just a couple small tweaks based on reading the rule with fresh eyes and
new best-practices.
2023-06-25 22:20:31 +00:00
Charlie Marsh
b233763156 Run cargo update (#5357) 2023-06-25 18:16:59 -04:00
Charlie Marsh
1ef4eee089 Add space when migrating to raw string (#5358)
## Summary

We had to do this for f-strings too -- if we add a prefix to `"foo"` in
`return"foo"`, we also need to add a leading space.
2023-06-25 18:10:08 -04:00
Shantanu
0ce38b650e Change W605 autofix to use raw strings if possible (#5352)
Fixes #5061.
2023-06-25 17:35:07 -04:00
Evan Rittenhouse
e0a507e48e Add Applicability to flake8_simplify (#5348) 2023-06-23 22:54:43 +00:00
Dhruv Manilawala
adf5cb5ff7 Ignore type aliases for RUF013 (#5344)
## Summary

Ignore type aliases for RUF013 to avoid flagging false positives:

```python
from typing import Optional

MaybeInt = Optional[int]


def f(arg: MaybeInt = None):
    pass
```

But, at the expense of having false negatives:

```python
Text = str | bytes


def f(arg: Text = None):
    pass
```

## Test Plan

`cargo test`

fixes: #5295
2023-06-23 22:51:09 +00:00
Micha Reiser
d3d69a031e Add JoinCommaSeparatedBuilder (#5342) 2023-06-23 22:03:05 +01:00
Micha Reiser
6ba9d5d5a4 Upgrade RustPython (#5334) 2023-06-23 20:39:47 +00:00
Charlie Marsh
f45d1c2b84 Remove HashMap and HashSet for known-standard-library detection (#5345)
## Summary

This is a lot more concise and probably much more performant (with fewer
instructions).
2023-06-23 19:59:03 +00:00
konstin
4b65446de6 Refactor magic trailing comma (#5339)
## Summary

This is small refactoring to reuse the code that detects the magic
trailing comma across functions. I make this change now to avoid copying
code in a later PR. @MichaReiser is planning on making a larger
refactoring later that integrates with the join nodes builder

## Test Plan

No functional changes. The magic trailing comma behaviour is checked by
the fixtures.
2023-06-23 18:53:55 +02:00
Micha Reiser
cb580f960f Make small tweaks to the profiling documentation (#5335) 2023-06-23 18:11:41 +02:00
James Berry
f85eb709e2 Visit AugAssign target after value (#5325)
## Summary

When visiting AugAssign in evaluation order, the AugAssign `target`
should be visited after it's `value`. Based on my testing, the pseudo
code for `a += b` is effectively:
```python
tmp = a
a = tmp.__iadd__(b)
```

That is, an ideal traversal order would look something like this:
1. load a
2. b
3. op
4. store a

But, there is only a single AST node which captures `a` in the statement
`a += b`, so it cannot be traversed both before and after the traversal
of `b` and the `op`.

Nonetheless, I think traversing `a` after `b` and the `op` makes the
most sense for a number of reasons:
1. All the other assignment expressions traverse their `value`s before
their `target`s. Having `AugAssign` traverse in the same order would be
more consistent.
2. Within the AST, the `ctx` of the `target` for an `AugAssign` is
`Store` (though technically this is a `Load` and `Store` operation, the
AST only indicates it as a `Store`). Since the the store portion of the
`AugAssign` occurs last, I think it makes sense to traverse the `target`
last as well.

The effect of this is marginal, but it may have an impact on the
behavior of #5271.
2023-06-23 09:54:54 -04:00
Charlie Marsh
2f03159c8b Use SSH clones in update_schemastore.py (#5322) 2023-06-23 09:50:10 -04:00
Thomas de Zeeuw
1c638264b2 Keep track of when files are last seen in the cache (#5214)
## Summary

And remove cached files that we haven't seen for a certain period of
time, currently 30 days.

For the last seen timestamp we actually use an `u64`, it's smaller on
disk than `SystemTime` (which size is OS dependent) and fits in an
`AtomicU64` which we can use to update it without locks.

## Test Plan

Added a new unit test, run by `cargo test`.
2023-06-23 15:40:35 +02:00
Micha Reiser
2dfa6ff58d Fix unstable set comprehension formatting (#5327) 2023-06-23 11:50:24 +02:00
konstin
930f03de98 Don't mistake a following if for an elif (#5296)
In the following code, the comment used to get wrongly associated with
the `if False` since it looked like an elif. This fixes it by checking
the indentation and adding a regression test
```python
if True:
    pass
else:  # Comment
    if False:
        pass
    pass
```
    
Originally found in
1570b94a02/gradio/external.py (L478)
2023-06-23 10:07:28 +02:00
Micha Reiser
c52aa8f065 Basic string formatting
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR implements formatting for non-f-string Strings that do not use implicit concatenation. 

Docstring formatting is out of the scope of this PR.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I added a few tests for simple string literals. 

## Performance

Ouch. This is hitting performance somewhat hard. This is probably because we now iterate each string a couple of times:

1. To detect if it is an implicit string continuation
2. To detect if the string contains any new lines
3. To detect the preferred quote
4. To normalize the string

Edit: I integrated the detection of newlines into the preferred quote detection so that we only iterate the string three time.
We can probably do better by merging the implicit string continuation with the quote detection and new line detection by iterating till the end of the string part and returning the offset. We then use our simple tokenizer to skip over any comments or whitespace until we find the first non trivia token. From there we keep continue doing this in a loop until we reach the end o the string. I'll leave this improvement for later.
2023-06-23 09:46:05 +02:00
Micha Reiser
3e12bdff45 Format Compare Op
<!--
Thank you for contributing to Ruff! To help us out with reviewing, please consider the following:

- Does this pull request include a summary of the change? (See below.)
- Does this pull request include a descriptive title?
- Does this pull request include references to any relevant issues?
-->

## Summary

This PR adds basic formatting for compare operations.

The implementation currently breaks diffeently when nesting binary like expressions. I haven't yet figured out what Black's logic is in that case but I think that this by itself is already an improvement worth merging.

<!-- What's the purpose of the change? What does it do, and why? -->

## Test Plan

I added a few new tests 

<!-- How was it tested? -->
2023-06-23 09:35:29 +02:00
James Berry
2142bf6141 Fix annotation and format spec visitors (#5324)
## Summary

The `Visitor` and `preorder::Visitor` traits provide some convenience
functions, `visit_annotation` and `visit_format_spec`, for handling
annotation and format spec expressions respectively. Both of these
functions accept an `&Expr` and have a default implementation which
delegates to `walk_expr`. The problem with this approach is that any
custom handling done in `visit_expr` will be skipped for annotations and
format specs. Instead, to capture any custom logic implemented in
`visit_expr`, both of these function's default implementations should
delegate to `visit_expr` instead of `walk_expr`.

## Example

Consider the below `Visitor` implementation:
```rust
impl<'a> Visitor<'a> for Example<'a> {
    fn visit_expr(&mut self, expr: &'a Expr) {
        match expr {
            Expr::Name(ExprName { id, .. }) => println!("Visiting {:?}", id),
            _ => walk_expr(self, expr),
        }
    }
}
```

Run on the following Python snippet:
```python
a: b
```

I would expect such a visitor to print the following:
```
Visiting b
Visiting a
```

But it instead prints the following:
```
Visiting a
```

Our custom `visit_expr` handler is not invoked for the annotation.

## Test Plan

Tests added in #5271 caught this behavior.
2023-06-23 03:55:42 +00:00
Tom Kuson
1cf307c34c Fix collection-literal-concatenation documentation (#5320)
## Summary

Move `collection-literal-concatenation` markdown documentation to the
correct place.

Fixes error in #5262.

## Test Plan

`python scripts/check_docs_formatted.py`
2023-06-22 18:37:54 -04:00
Charlie Marsh
7819b95d7f Avoid syntax errors when removing f-string prefixes (#5319)
Closes https://github.com/astral-sh/ruff/issues/5281.

Closes https://github.com/astral-sh/ruff/issues/4827.
2023-06-22 17:21:09 -04:00
Lukas Mayrhofer
4a81cfc51a Allow @Author format for "Missing Author" rule in flake8-todos (#4903)
## Summary

The TD-002 rule "Missing Author" was updated to allow another format
using "@". This reflects the current 0.3.0 version of flake8-todos.
2023-06-22 20:53:58 +00:00
qdegraaf
38e618cd18 [perflint] Add PERF101 with autofix (#5121)
## Summary

Adds PERF101 which checks for unnecessary casts to `list` in for loops. 

NOTE: Is not fully equal to its upstream implementation as this
implementation does not flag based on type annotations
(i.e.):
```python
def foo(x: List[str]):
    for y in list(x):
        ...
```

With the current set-up it's quite hard to get the annotation from a
function arg from its binding. Problem is best considered broader than
this implementation.

## Test Plan

Added fixture. 

## Issue links

Refers: https://github.com/astral-sh/ruff/issues/4789

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2023-06-22 20:44:26 +00:00
Charlie Marsh
50f0edd2cb Add dark- and light-mode image modifiers for custom MkDocs themes (#5318)
## Summary

Roughly following the docs
[here](https://squidfunk.github.io/mkdocs-material/reference/images/#custom-light-scheme).

Closes #5311.
2023-06-22 16:11:38 -04:00
Edgar R. M
e0e1d13d9f Fix diagnostics variable name in add_plugin.py script (#5317)
## Summary

Fix a variable name in the `add_plugin.py` script.

## Test Plan

I don't think there are any tests for the scripts, other than manual
confirmation
2023-06-22 20:06:47 +00:00
Charlie Marsh
8bc7378002 Add PythonVersion::Py312 (#5316)
Closes #5310.
2023-06-22 20:01:07 +00:00
Charlie Marsh
cdbd0bd5cd Respect abc decorators when classifying function types (#5315)
Closes #5307.
2023-06-22 19:52:36 +00:00
Charlie Marsh
5f88ff8a96 Allow __slots__ assignments in mutable-class-default (#5314)
Closes #5309.
2023-06-22 19:40:54 +00:00
Charlie Marsh
1c2be54b4a Support pydantic.BaseSettings in mutable-class-default (#5312)
Closes #5308.
2023-06-22 19:27:05 +00:00
Charlie Marsh
5dd00b19e6 Remove off-palette colors from code (#5305) 2023-06-22 16:31:22 +00:00
Charlie Marsh
c0f93fcf3e Publish GitHub release as draft (#5304)
I accidentally changed `draft: false` to `draft: true` in #5240. I
actually think Copilot did this without me realizing.
2023-06-22 16:11:43 +00:00
Charlie Marsh
3238a6ef1f Fix 'our' to 'your' typo (#5303) 2023-06-22 15:58:24 +00:00
Charlie Marsh
96ecfae1c5 Remove off-palette colors (#5302) 2023-06-22 15:52:03 +00:00
konstin
03694ef649 More stability checker options (#5299)
## Summary

This contains three changes:
* repos in `check_ecosystem.py` are stored as `org:name` instead of
`org/name` to create a flat directory layout
* `check_ecosystem.py` performs a maximum of 50 parallel jobs at the
same time to avoid consuming to much RAM
* `check-formatter-stability` gets a new option `--multi-project` so
it's possible to do `cargo run --bin ruff_dev --
check-formatter-stability --multi-project target/checkouts`
With these three changes it becomes easy to check the formatter
stability over a larger number of repositories. This is part of the
integration of integrating formatter regressions checks into the
ecosystem checks.

## Test Plan

```shell
python scripts/check_ecosystem.py --checkouts target/checkouts --projects github_search.jsonl -v $(which true) $(which true)
cargo run --bin ruff_dev -- check-formatter-stability --multi-project target/checkouts
```
2023-06-22 15:48:11 +00:00
Charlie Marsh
f9f0cf7524 Use __future__ imports in scripts (#5301) 2023-06-22 11:40:16 -04:00
Tom Kuson
eaa10ad2d9 Fix deprecated-import false positives (#5291)
## Summary

Remove recommendations to replace
`typing_extensions.dataclass_transform` and
`typing_extensions.SupportsIndex` with their `typing` library
counterparts.

Closes #5112.

## Test Plan

Added extra checks to the test fixture.

`cargo test`
2023-06-22 15:34:44 +00:00
Evan Rittenhouse
84259f5440 Add Applicability to pycodestyle (#5282) 2023-06-22 11:25:20 -04:00
trag1c
e8ebe0a425 Update docs to match updated logo and color palette (#5283)
![8511](https://github.com/astral-sh/ruff/assets/77130613/862d151f-ff1d-4da8-9230-8dd32f41f197)

## Summary

Supersedes #5277, includes redesigned dark mode.

## Test Plan

* `python scripts/generate_mkdocs.py`
* `mkdocs serve`
2023-06-22 11:19:34 -04:00
konstin
d407165aa7 Fix formatter panic with comment after parenthesized dict value (#5293)
## Summary

This snippet used to panic because it expected to see a comma or
something similar after the `2` but met the closing parentheses that is
not part of the range and panicked
```python
a = {
    1: (2),
    # comment
    3: True,
}
```

Originally found in
636a717ef0/testing/marionette/client/marionette_driver/geckoinstance.py (L109)

This snippet is also the test plan.
2023-06-22 16:52:48 +02:00
Micha Reiser
f7e1cf4b51 Format class definitions (#5289) 2023-06-22 09:09:43 +00:00
konstin
7d4f8e59da Improve FormatExprCall dummy (#5290)
This solves an instability when formatting cpython. It also introduces
another one, but i think it's still a worthwhile change for now.

There's no proper testing since this is just a dummy.
2023-06-22 10:59:30 +02:00
Charlie Marsh
2c63f8cdea Update reference to release step (#5280) 2023-06-22 02:04:43 +00:00
Charlie Marsh
1c0a3a467f Bump version to 0.0.275 (#5276) 2023-06-21 21:53:37 -04:00
Charlie Marsh
6b8b318d6b Use mod tests consistently (#5278)
As per the Rust documentation.
2023-06-22 01:50:28 +00:00
Charlie Marsh
c0c59b82ec Use 'Checks for uses' consistently (#5279) 2023-06-22 01:44:52 +00:00
1897 changed files with 195335 additions and 38609 deletions

1
.gitattributes vendored
View File

@@ -4,3 +4,4 @@ crates/ruff/resources/test/fixtures/isort/line_ending_crlf.py text eol=crlf
crates/ruff/resources/test/fixtures/pycodestyle/W605_1.py text eol=crlf
ruff.schema.json linguist-generated=true text=auto eol=lf
*.md.snap linguist-language=Markdown

View File

@@ -2,6 +2,14 @@ name: Benchmark
on:
pull_request:
paths:
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain'
- 'crates/**'
- '!crates/ruff_dev'
- '!crates/ruff_shrinking'
workflow_dispatch:
concurrency:

View File

@@ -16,9 +16,46 @@ env:
CARGO_TERM_COLOR: always
RUSTUP_MAX_RETRIES: 10
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.7" # to build abi3 wheels
PYTHON_VERSION: "3.11" # to build abi3 wheels
jobs:
determine_changes:
name: "Determine changes"
runs-on: ubuntu-latest
outputs:
linter: ${{ steps.changed.outputs.linter_any_changed }}
formatter: ${{ steps.changed.outputs.formatter_any_changed }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: tj-actions/changed-files@v37
id: changed
with:
files_yaml: |
linter:
- Cargo.toml
- Cargo.lock
- crates/**
- "!crates/ruff_python_formatter/**"
- "!crates/ruff_formatter/**"
- "!crates/ruff_dev/**"
- "!crates/ruff_shrinking/**"
formatter:
- Cargo.toml
- Cargo.lock
- crates/ruff_python_formatter/**
- crates/ruff_formatter/**
- crates/ruff_python_trivia/**
- crates/ruff_python_ast/**
- crates/ruff_source_file/**
- crates/ruff_python_index/**
- crates/ruff_text_size/**
- crates/ruff_python_parser/**
cargo-fmt:
name: "cargo fmt"
runs-on: ubuntu-latest
@@ -31,17 +68,6 @@ jobs:
cargo-clippy:
name: "cargo clippy"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: |
rustup component add clippy
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo-clippy-wasm:
name: "cargo clippy (wasm)"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
@@ -49,7 +75,10 @@ jobs:
rustup component add clippy
rustup target add wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
- name: "Clippy"
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- name: "Clippy (wasm)"
run: cargo clippy -p ruff_wasm --target wasm32-unknown-unknown --all-features -- -D warnings
cargo-test:
strategy:
@@ -61,22 +90,22 @@ jobs:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-insta
- name: "Install cargo insta"
uses: taiki-e/install-action@v2
with:
tool: cargo-insta
- run: pip install black[d]==23.1.0
- uses: Swatinem/rust-cache@v2
- name: "Run tests (Ubuntu)"
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
cargo insta test --all --all-features --delete-unreferenced-snapshots
git diff --exit-code
run: cargo insta test --all --all-features --unreferenced reject
- name: "Run tests (Windows)"
if: ${{ matrix.os == 'windows-latest' }}
shell: bash
run: |
cargo insta test --all --all-features
git diff --exit-code
# We can't reject unreferenced snapshots on windows because flake8_executable can't run on windows
run: cargo insta test --all --all-features
- run: cargo test --package ruff_cli --test black_compatibility_test -- --ignored
# Skipped as it's currently broken. The resource were moved from the
# TODO: Skipped as it's currently broken. The resource were moved from the
# ruff_cli to ruff crate, but this test was not updated.
if: false
# Check for broken links in the documentation.
@@ -145,14 +174,16 @@ jobs:
ecosystem:
name: "ecosystem"
runs-on: ubuntu-latest
needs: cargo-test
needs:
- cargo-test
- determine_changes
# Only runs on pull requests, since that is the only we way we can find the base version for comparison.
if: github.event_name == 'pull_request'
if: github.event_name == 'pull_request' && needs.determine_changes.outputs.linter == 'true'
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version: ${{ env.PYTHON_VERSION }}
- uses: actions/download-artifact@v3
name: Download Ruff binary
@@ -236,7 +267,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.11"
python-version: ${{ env.PYTHON_VERSION }}
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
@@ -260,13 +291,24 @@ jobs:
docs:
name: "mkdocs"
runs-on: ubuntu-latest
env:
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: pip install -r docs/requirements.txt
- name: "Update README File"
run: python scripts/transform_readme.py --target mkdocs
@@ -274,5 +316,30 @@ jobs:
run: python scripts/generate_mkdocs.py
- name: "Check docs formatting"
run: python scripts/check_docs_formatted.py
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
run: mkdocs build --strict
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.generated.yml
check-formatter-ecosystem:
name: "Formatter ecosystem and progress checks"
runs-on: ubuntu-latest
needs: determine_changes
if: needs.determine_changes.outputs.formatter == 'true'
steps:
- uses: actions/checkout@v3
- name: "Install Rust toolchain"
run: rustup show
- name: "Cache rust"
uses: Swatinem/rust-cache@v2
- name: "Formatter progress"
run: scripts/formatter_progress.sh
- name: "Github step summary"
run: grep "similarity index" target/progress_projects_report.txt | sort > $GITHUB_STEP_SUMMARY
# CPython is not black formatted, so we run only the stability check
- name: "Clone CPython 3.10"
run: git clone --branch 3.10 --depth 1 https://github.com/python/cpython.git crates/ruff/resources/test/cpython
- name: "Check CPython stability"
run: cargo run --bin ruff_dev -- format-dev --stability-check crates/ruff/resources/test/cpython

View File

@@ -10,20 +10,34 @@ jobs:
runs-on: ubuntu-latest
env:
CF_API_TOKEN_EXISTS: ${{ secrets.CF_API_TOKEN != '' }}
MKDOCS_INSIDERS_SSH_KEY_EXISTS: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY != '' }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: "Add SSH key"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.MKDOCS_INSIDERS_SSH_KEY }}
- name: "Install Rust toolchain"
run: rustup show
- uses: Swatinem/rust-cache@v2
- name: "Install Insiders dependencies"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: pip install -r docs/requirements-insiders.txt
- name: "Install dependencies"
run: |
pip install -r docs/requirements.txt
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: pip install -r docs/requirements.txt
- name: "Copy README File"
run: |
python scripts/transform_readme.py --target mkdocs
python scripts/generate_mkdocs.py
mkdocs build --strict
- name: "Build Insiders docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS == 'true' }}
run: mkdocs build --strict -f mkdocs.insiders.yml
- name: "Build docs"
if: ${{ env.MKDOCS_INSIDERS_SSH_KEY_EXISTS != 'true' }}
run: mkdocs build --strict -f mkdocs.generated.yml
- name: "Deploy to Cloudflare Pages"
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
uses: cloudflare/wrangler-action@2.0.0

View File

@@ -9,7 +9,7 @@ concurrency:
env:
PACKAGE_NAME: flake8-to-ruff
CRATE_NAME: flake8_to_ruff
PYTHON_VERSION: "3.7" # to build abi3 wheels
PYTHON_VERSION: "3.11"
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always

View File

@@ -9,7 +9,7 @@ on:
sha:
description: "Optionally, the full sha of the commit to be released"
type: string
push:
pull_request:
paths:
# When we change pyproject.toml, we want to ensure that the maturin builds still work
- pyproject.toml
@@ -20,7 +20,7 @@ concurrency:
env:
PACKAGE_NAME: ruff
PYTHON_VERSION: "3.7" # to build abi3 wheels
PYTHON_VERSION: "3.11"
CARGO_INCREMENTAL: 0
CARGO_NET_RETRY: 10
CARGO_TERM_COLOR: always
@@ -491,16 +491,16 @@ jobs:
- name: "Publish to GitHub"
uses: softprops/action-gh-release@v1
with:
draft: false
draft: true
files: binaries/*
tag_name: v${{ inputs.tag }}
# After the release has been published, we update downstream repositories
# This is separate because if this fails the release is still fine, we just need to do some manual workflow triggers
update-dependents:
name: Release
name: Update dependents
runs-on: ubuntu-latest
needs: release
needs: publish-release
steps:
- name: "Update pre-commit mirror"
uses: actions/github-script@v6

5
.gitignore vendored
View File

@@ -1,8 +1,7 @@
# Benchmarking cpython (CONTRIBUTING.md)
crates/ruff/resources/test/cpython
# generate_mkdocs.py
mkdocs.yml
.overrides
mkdocs.generated.yml
# check_ecosystem.py
ruff-old
github_search*.jsonl
@@ -11,7 +10,7 @@ schemastore
# `maturin develop` and ecosystem_all_check.sh
.venv*
# Formatter debugging (crates/ruff_python_formatter/README.md)
scratch.py
scratch.*
# Created by `perf` (CONTRIBUTING.md)
perf.data
perf.data.old

View File

@@ -1,6 +1,10 @@
# default to true for all rules
default: true
# MD007/unordered-list-indent
MD007:
indent: 4
# MD033/no-inline-html
MD033: false
@@ -8,7 +12,4 @@ MD033: false
MD041: false
# MD013/line-length
MD013:
line_length: 100
code_blocks: false
ignore_code_blocks: true
MD013: false

View File

@@ -6,7 +6,9 @@ exclude: |
crates/ruff/src/rules/.*/snapshots/.*|
crates/ruff_cli/resources/.*|
crates/ruff_python_formatter/resources/.*|
crates/ruff_python_formatter/src/snapshots/.*
crates/ruff_python_formatter/tests/snapshots/.*|
crates/ruff_python_resolver/resources/.*|
crates/ruff_python_resolver/tests/snapshots/.*
)$
repos:
@@ -20,6 +22,7 @@ repos:
hooks:
- id: mdformat
additional_dependencies:
- mdformat-mkdocs
- mdformat-black
- black==23.1.0 # Must be the latest version of Black

View File

@@ -1,5 +1,67 @@
# Breaking Changes
## 0.0.277
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
Ruff maintains a list of default exclusions, which now consists of the following patterns:
- `.bzr`
- `.direnv`
- `.eggs`
- `.git`
- `.git-rewrite`
- `.hg`
- `.ipynb_checkpoints`
- `.mypy_cache`
- `.nox`
- `.pants.d`
- `.pyenv`
- `.pytest_cache`
- `.pytype`
- `.ruff_cache`
- `.svn`
- `.tox`
- `.venv`
- `.vscode`
- `__pypackages__`
- `_build`
- `buck-out`
- `build`
- `dist`
- `node_modules`
- `venv`
Previously, the `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` directories were not
excluded by default. This change brings Ruff's default exclusions in line with other tools like
Black.
## 0.0.276
### The `keep-runtime-typing` setting has been reinstated ([#5470](https://github.com/astral-sh/ruff/pull/5470))
The `keep-runtime-typing` setting has been reinstated with revised semantics. This setting was
removed in [#4427](https://github.com/astral-sh/ruff/pull/4427), as it was equivalent to ignoring
the `UP006` and `UP007` rules via Ruff's standard `ignore` mechanism.
Taking `UP006` (rewrite `List[int]` to `list[int]`) as an example, the setting now behaves as
follows:
- On Python 3.7 and Python 3.8, setting `keep-runtime-typing = true` will cause Ruff to ignore
`UP006` violations, even if `from __future__ import annotations` is present in the file.
While such annotations are valid in Python 3.7 and Python 3.8 when combined with
`from __future__ import annotations`, they aren't supported by libraries like Pydantic and
FastAPI, which rely on runtime type checking.
- On Python 3.9 and above, the setting has no effect, as `list[int]` is a valid type annotation,
and libraries like Pydantic and FastAPI support it without issue.
In short: `keep-runtime-typing` can be used to ensure that Ruff doesn't introduce type annotations
that are not supported at runtime by the current Python version, which are unsupported by libraries
like Pydantic and FastAPI.
Note that this is not a breaking change, but is included here to complement the previous removal
of `keep-runtime-typing`.
## 0.0.268
### The `keep-runtime-typing` setting has been removed ([#4427](https://github.com/astral-sh/ruff/pull/4427))
@@ -141,25 +203,25 @@ This change is largely backwards compatible -- most users should experience
no change in behavior. However, please note the following exceptions:
- Subcommands will now fail when invoked with unsupported arguments, instead
of silently ignoring them. For example, the following will now fail:
of silently ignoring them. For example, the following will now fail:
```console
ruff --clean --respect-gitignore
```
```console
ruff --clean --respect-gitignore
```
(the `clean` command doesn't support `--respect-gitignore`.)
(the `clean` command doesn't support `--respect-gitignore`.)
- The semantics of `ruff <arg>` have changed slightly when `<arg>` is a valid subcommand.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
For example, prior to this release, running `ruff rule` would run `ruff` over a file or
directory called `rule`. Now, `ruff rule` would invoke the `rule` subcommand. This should
only impact projects with files or directories named `rule`, `check`, `explain`, `clean`,
or `generate-shell-completion`.
- Scripts that invoke ruff should supply `--` before any positional arguments.
(The semantics of `ruff -- <arg>` have not changed.)
(The semantics of `ruff -- <arg>` have not changed.)
- `--explain` previously treated `--format grouped` as a synonym for `--format text`.
This is no longer supported; instead, use `--format text`.
This is no longer supported; instead, use `--format text`.
## 0.0.226

View File

@@ -6,10 +6,10 @@
- [Scope](#scope)
- [Enforcement](#enforcement)
- [Enforcement Guidelines](#enforcement-guidelines)
- [1. Correction](#1-correction)
- [2. Warning](#2-warning)
- [3. Temporary Ban](#3-temporary-ban)
- [4. Permanent Ban](#4-permanent-ban)
- [1. Correction](#1-correction)
- [2. Warning](#2-warning)
- [3. Temporary Ban](#3-temporary-ban)
- [4. Permanent Ban](#4-permanent-ban)
- [Attribution](#attribution)
## Our Pledge
@@ -33,20 +33,20 @@ community include:
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
professional setting
## Enforcement Responsibilities

View File

@@ -3,16 +3,29 @@
Welcome! We're happy to have you here. Thank you in advance for your contribution to Ruff.
- [The Basics](#the-basics)
- [Prerequisites](#prerequisites)
- [Development](#development)
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [Prerequisites](#prerequisites)
- [Development](#development)
- [Project Structure](#project-structure)
- [Example: Adding a new lint rule](#example-adding-a-new-lint-rule)
- [Rule naming convention](#rule-naming-convention)
- [Rule testing: fixtures and snapshots](#rule-testing-fixtures-and-snapshots)
- [Example: Adding a new configuration option](#example-adding-a-new-configuration-option)
- [MkDocs](#mkdocs)
- [Release Process](#release-process)
- [Benchmarks](#benchmarking-and-profiling)
- [Creating a new release](#creating-a-new-release)
- [Ecosystem CI](#ecosystem-ci)
- [Benchmarking and Profiling](#benchmarking-and-profiling)
- [CPython Benchmark](#cpython-benchmark)
- [Microbenchmarks](#microbenchmarks)
- [Benchmark-driven Development](#benchmark-driven-development)
- [PR Summary](#pr-summary)
- [Tips](#tips)
- [Profiling Projects](#profiling-projects)
- [Linux](#linux)
- [Mac](#mac)
- [`cargo dev`](#cargo-dev)
- [Subsystems](#subsystems)
- [Compilation Pipeline](#compilation-pipeline)
## The Basics
@@ -23,7 +36,10 @@ For small changes (e.g., bug fixes), feel free to submit a PR.
For larger changes (e.g., new lint rules, new functionality, new configuration options), consider
creating an [**issue**](https://github.com/astral-sh/ruff/issues) outlining your proposed change.
You can also join us on [**Discord**](https://discord.gg/c9MhzV8aU5) to discuss your idea with the
community.
community. We've labeled [beginner-friendly tasks](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
in the issue tracker, along with [bugs](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
and [improvements](https://github.com/astral-sh/ruff/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted)
that are ready for contributions.
If you're looking for a place to start, we recommend implementing a new lint rule (see:
[_Adding a new lint rule_](#example-adding-a-new-lint-rule), which will allow you to learn from and
@@ -34,6 +50,8 @@ As a concrete example: consider taking on one of the rules from the [`flake8-pyi
plugin, and looking to the originating [Python source](https://github.com/PyCQA/flake8-pyi) for
guidance.
If you have suggestions on how we might improve the contributing documentation, [let us know](https://github.com/astral-sh/ruff/discussions/5693)!
### Prerequisites
Ruff is written in Rust. You'll need to install the
@@ -53,7 +71,7 @@ pipx install pre-commit # or `pip install pre-commit` if you have a virtualenv
### Development
After cloning the repository, run Ruff locally with:
After cloning the repository, run Ruff locally from the repository root with:
```shell
cargo run -p ruff_cli -- check /path/to/file.py --no-cache
@@ -92,48 +110,58 @@ The vast majority of the code, including all lint rules, lives in the `ruff` cra
At time of writing, the repository includes the following crates:
- `crates/ruff`: library crate containing all lint rules and the core logic for running them.
If you're working on a rule, this is the crate for you.
- `crates/ruff_benchmark`: binary crate for running micro-benchmarks.
- `crates/ruff_cache`: library crate for caching lint results.
- `crates/ruff_cli`: binary crate containing Ruff's command-line interface.
- `crates/ruff_dev`: binary crate containing utilities used in the development of Ruff itself (e.g.,
`cargo dev generate-all`).
- `crates/ruff_diagnostics`: library crate for the lint diagnostics APIs.
- `crates/ruff_formatter`: library crate for generic code formatting logic based on an intermediate
representation.
`cargo dev generate-all`), see the [`cargo dev`](#cargo-dev) section below.
- `crates/ruff_diagnostics`: library crate for the rule-independent abstractions in the lint
diagnostics APIs.
- `crates/ruff_formatter`: library crate for language agnostic code formatting logic based on an
intermediate representation. The backend for `ruff_python_formatter`.
- `crates/ruff_index`: library crate inspired by `rustc_index`.
- `crates/ruff_macros`: library crate containing macros used by Ruff.
- `crates/ruff_macros`: proc macro crate containing macros used by Ruff.
- `crates/ruff_python_ast`: library crate containing Python-specific AST types and utilities.
- `crates/ruff_python_formatter`: library crate containing Python-specific code formatting logic.
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
- `crates/ruff_python_codegen`: library crate containing utilities for generating Python source code.
- `crates/ruff_python_formatter`: library crate implementing the Python formatter. Emits an
intermediate representation for each node, which `ruff_formatter` prints based on the configured
line length.
- `crates/ruff_python_semantic`: library crate containing Python-specific semantic analysis logic,
including Ruff's semantic model.
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data.
- `crates/ruff_python_whitespace`: library crate containing Python-specific whitespace analysis
logic.
- `crates/ruff_rustpython`: library crate containing `RustPython`-specific utilities.
- `crates/ruff_testing_macros`: library crate containing macros used for testing Ruff.
- `crates/ruff_textwrap`: library crate to indent and dedent Python source code.
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module.
including Ruff's semantic model. Used to resolve queries like "What import does this variable
refer to?"
- `crates/ruff_python_stdlib`: library crate containing Python-specific standard library data, e.g.
the names of all built-in exceptions and which standard library types are immutable.
- `crates/ruff_python_trivia`: library crate containing Python-specific trivia utilities (e.g.,
for analyzing indentation, newlines, etc.).
- `crates/ruff_python_parser`: library crate containing the Python parser.
- `crates/ruff_wasm`: library crate for exposing Ruff as a WebAssembly module. Powers the
[Ruff Playground](https://play.ruff.rs/).
### Example: Adding a new lint rule
At a high level, the steps involved in adding a new lint rule are as follows:
1. Determine a name for the new rule as per our [rule naming convention](#rule-naming-convention)
(e.g., `AssertFalse`, as in, "allow `assert False`").
(e.g., `AssertFalse`, as in, "allow `assert False`").
1. Create a file for your rule (e.g., `crates/ruff/src/rules/flake8_bugbear/rules/assert_false.rs`).
1. In that file, define a violation struct (e.g., `pub struct AssertFalse`). You can grep for
`#[violation]` to see examples.
`#[violation]` to see examples.
1. In that file, define a function that adds the violation to the diagnostic list as appropriate
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
an `ast::StmtAssert` node).
(e.g., `pub(crate) fn assert_false`) based on whatever inputs are required for the rule (e.g.,
an `ast::StmtAssert` node).
1. Define the logic for triggering the violation in `crates/ruff/src/checkers/ast/mod.rs` (for
AST-based checks), `crates/ruff/src/checkers/tokens.rs` (for token-based checks),
`crates/ruff/src/checkers/lines.rs` (for text-based checks), or
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based checks).
1. Define the logic for invoking the diagnostic in `crates/ruff/src/checkers/ast/analyze` (for
AST-based rules), `crates/ruff/src/checkers/tokens.rs` (for token-based rules),
`crates/ruff/src/checkers/physical_lines.rs` (for text-based rules),
`crates/ruff/src/checkers/filesystem.rs` (for filesystem-based rules), etc. For AST-based rules,
you'll likely want to modify `analyze/statement.rs` (if your rule is based on analyzing
statements, like imports) or `analyze/expression.rs` (if your rule is based on analyzing
expressions, like function calls).
1. Map the violation struct to a rule code in `crates/ruff/src/codes.rs` (e.g., `B011`).
@@ -166,13 +194,13 @@ suppression comment would be framed as "allow `assert False`".
As such, rule names should...
- Highlight the pattern that is being linted against, rather than the preferred alternative.
For example, `AssertFalse` guards against `assert False` statements.
For example, `AssertFalse` guards against `assert False` statements.
- _Not_ contain instructions on how to fix the violation, which instead belong in the rule
documentation and the `autofix_title`.
documentation and the `autofix_title`.
- _Not_ contain a redundant prefix, like `Disallow` or `Banned`, which are already implied by the
convention.
convention.
When re-implementing rules from other linters, we prioritize adhering to this convention over
preserving the original rule name.
@@ -187,25 +215,28 @@ Ruff's output for each fixture, which you can then commit alongside your changes
Once you've completed the code for the rule itself, you can define tests with the following steps:
1. Add a Python file to `crates/ruff/resources/test/fixtures/[linter]` that contains the code you
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
examples of both violations and non-violations.
want to test. The file name should match the rule name (e.g., `E402.py`), and it should include
examples of both violations and non-violations.
1. Run Ruff locally against your file and verify the output is as expected. Once you're satisfied
with the output (you see the violations you expect, and no others), proceed to the next step.
For example, if you're adding a new rule named `E402`, you would run:
with the output (you see the violations you expect, and no others), proceed to the next step.
For example, if you're adding a new rule named `E402`, you would run:
```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache
```
```shell
cargo run -p ruff_cli -- check crates/ruff/resources/test/fixtures/pycodestyle/E402.py --no-cache --select E402
```
**Note:** Only a subset of rules are enabled by default. When testing a new rule, ensure that
you activate it by adding `--select ${rule_code}` to the command.
1. Add the test to the relevant `crates/ruff/src/rules/[linter]/mod.rs` file. If you're contributing
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
a rule to a pre-existing set, you should be able to find a similar example to pattern-match
against. If you're adding a new linter, you'll need to create a new `mod.rs` file (see,
e.g., `crates/ruff/src/rules/flake8_bugbear/mod.rs`)
1. Run `cargo test`. Your test will fail, but you'll be prompted to follow-up
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
then commit the snapshot file alongside the rest of your changes.
with `cargo insta review`. Run `cargo insta review`, review and accept the generated snapshot,
then commit the snapshot file alongside the rest of your changes.
1. Run `cargo test` again to ensure that your test passes.
@@ -243,21 +274,25 @@ To preview any changes to the documentation locally:
1. Install MkDocs and Material for MkDocs with:
```shell
pip install -r docs/requirements.txt
```
```shell
pip install -r docs/requirements.txt
```
1. Generate the MkDocs site with:
```shell
python scripts/generate_mkdocs.py
```
```shell
python scripts/generate_mkdocs.py
```
1. Run the development server with:
```shell
mkdocs serve
```
```shell
# For contributors.
mkdocs serve -f mkdocs.generated.yml
# For members of the Astral org, which has access to MkDocs Insiders via sponsorship.
mkdocs serve -f mkdocs.insiders.yml
```
The documentation should then be available locally at
[http://127.0.0.1:8000/docs/](http://127.0.0.1:8000/docs/).
@@ -278,20 +313,19 @@ even patch releases may contain [non-backwards-compatible changes](https://semve
1. Create a PR with the version and `BREAKING_CHANGES.md` updated
1. Merge the PR
1. Run the release workflow with the version number (without starting `v`) as input. Make sure
main has your merged PR as last commit
main has your merged PR as last commit
1. The release workflow will do the following:
1. Build all the assets. If this fails (even though we tested in step 4), we havent tagged or
uploaded anything, you can restart after pushing a fix
1. Upload to pypi
1. Create and push the git tag (from pyproject.toml). We create the git tag only here
because we can't change it ([#4468](https://github.com/charliermarsh/ruff/issues/4468)), so
we want to make sure everything up to and including publishing to pypi worked.
1. Attach artifacts to draft GitHub release
1. Trigger downstream repositories. This can fail without causing fallout, it is possible (if
inconvenient) to trigger the downstream jobs manually
1. Create release notes in GitHub UI and promote from draft to proper release(<https://github.com/charliermarsh/ruff/releases/new>)
1. Build all the assets. If this fails (even though we tested in step 4), we haven't tagged or
uploaded anything, you can restart after pushing a fix.
1. Upload to PyPI.
1. Create and push the Git tag (as extracted from `pyproject.toml`). We create the Git tag only
after building the wheels and uploading to PyPI, since we can't delete or modify the tag ([#4468](https://github.com/charliermarsh/ruff/issues/4468)).
1. Attach artifacts to draft GitHub release
1. Trigger downstream repositories. This can fail non-catastrophically, as we can run any
downstream jobs manually if needed.
1. Create release notes in GitHub UI and promote from draft.
1. If needed, [update the schemastore](https://github.com/charliermarsh/ruff/blob/main/scripts/update_schemastore.py)
1. If needed, update ruff-lsp and ruff-vscode
1. If needed, update the `ruff-lsp` and `ruff-vscode` repositories.
## Ecosystem CI
@@ -327,22 +361,18 @@ git clone --branch 3.10 https://github.com/python/cpython.git crates/ruff/resour
To benchmark the release build:
```shell
cargo build --release && hyperfine --ignore-failure --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache" \
"./target/release/ruff ./crates/ruff/resources/test/cpython/"
cargo build --release && hyperfine --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e" \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ -e"
Benchmark 1: ./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache
Time (mean ± σ): 293.8 ms ± 3.2 ms [User: 2384.6 ms, System: 90.3 ms]
Range (min … max): 289.9 ms … 301.6 ms 10 runs
Warning: Ignoring non-zero exit code.
Benchmark 2: ./target/release/ruff ./crates/ruff/resources/test/cpython/
Time (mean ± σ): 48.0 ms ± 3.1 ms [User: 65.2 ms, System: 124.7 ms]
Range (min … max): 45.0 ms … 66.7 ms 62 runs
Warning: Ignoring non-zero exit code.
Summary
'./target/release/ruff ./crates/ruff/resources/test/cpython/' ran
6.12 ± 0.41 times faster than './target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache'
@@ -394,6 +424,13 @@ Summary
159.43 ± 2.48 times faster than 'pycodestyle crates/ruff/resources/test/cpython'
```
To benchmark a subset of rules, e.g. `LineTooLong` and `DocLineTooLong`:
```shell
cargo build --release && hyperfine --warmup 10 \
"./target/release/ruff ./crates/ruff/resources/test/cpython/ --no-cache -e --select W505,E501"
```
You can run `poetry install` from `./scripts/benchmarks` to create a working environment for the
above. All reported benchmarks were computed using the versions specified by
`./scripts/benchmarks/pyproject.toml` on Python 3.11.
@@ -438,7 +475,7 @@ Benchmark 1: find . -type f -name "*.py" | xargs -P 0 pyupgrade --py311-plus
Range (min … max): 29.813 s … 30.356 s 10 runs
```
## Microbenchmarks
### Microbenchmarks
The `ruff_benchmark` crate benchmarks the linter and the formatter on individual files.
@@ -448,7 +485,7 @@ You can run the benchmarks with
cargo benchmark
```
### Benchmark driven Development
#### Benchmark-driven Development
Ruff uses [Criterion.rs](https://bheisler.github.io/criterion.rs/book/) for benchmarks. You can use
`--save-baseline=<name>` to store an initial baseline benchmark (e.g. on `main`) and then use
@@ -463,7 +500,7 @@ cargo benchmark --save-baseline=main
cargo benchmark --baseline=main
```
### PR Summary
#### PR Summary
You can use `--save-baseline` and `critcmp` to get a pretty comparison between two recordings.
This is useful to illustrate the improvements of a PR.
@@ -484,26 +521,26 @@ You must install [`critcmp`](https://github.com/BurntSushi/critcmp) for the comp
cargo install critcmp
```
### Tips
#### Tips
- Use `cargo benchmark <filter>` to only run specific benchmarks. For example: `cargo benchmark linter/pydantic`
to only run the pydantic tests.
to only run the pydantic tests.
- Use `cargo benchmark --quiet` for a more cleaned up output (without statistical relevance)
- Use `cargo benchmark --quick` to get faster results (more prone to noise)
## Profiling Projects
### Profiling Projects
You can either use the microbenchmarks from above or a project directory for benchmarking. There
are a lot of profiling tools out there,
[The Rust Performance Book](https://nnethercote.github.io/perf-book/profiling.html) lists some
examples.
### Linux
#### Linux
Install `perf` and build `ruff_benchmark` with the `release-debug` profile and then run it with perf
```shell
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record -g -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
cargo bench -p ruff_benchmark --no-run --profile=release-debug && perf record --call-graph dwarf -F 9999 cargo bench -p ruff_benchmark --profile=release-debug -- --profile-time=1
```
You can also use the `ruff_dev` launcher to run `ruff check` multiple times on a repository to
@@ -531,7 +568,7 @@ An alternative is to convert the perf data to `flamegraph.svg` using
flamegraph --perfdata perf.data
```
### Mac
#### Mac
Install [`cargo-instruments`](https://crates.io/crates/cargo-instruments):
@@ -546,7 +583,292 @@ cargo instruments -t time --bench linter --profile release-debug -p ruff_benchma
```
- `-t`: Specifies what to profile. Useful options are `time` to profile the wall time and `alloc`
for profiling the allocations.
for profiling the allocations.
- You may want to pass an additional filter to run a single test file
Otherwise, follow the instructions from the linux section.
## `cargo dev`
`cargo dev` is a shortcut for `cargo run --package ruff_dev --bin ruff_dev`. You can run some useful
utils with it:
- `cargo dev print-ast <file>`: Print the AST of a python file using the
[RustPython parser](https://github.com/astral-sh/RustPython-Parser/tree/main/parser) that is
mainly used in Ruff. For `if True: pass # comment`, you can see the syntax tree, the byte offsets
for start and stop of each node and also how the `:` token, the comment and whitespace are not
represented anymore:
```text
[
If(
StmtIf {
range: 0..13,
test: Constant(
ExprConstant {
range: 3..7,
value: Bool(
true,
),
kind: None,
},
),
body: [
Pass(
StmtPass {
range: 9..13,
},
),
],
orelse: [],
},
),
]
```
- `cargo dev print-tokens <file>`: Print the tokens that the AST is built upon. Again for
`if True: pass # comment`:
```text
0 If 2
3 True 7
7 Colon 8
9 Pass 13
14 Comment(
"# comment",
) 23
23 Newline 24
```
- `cargo dev print-cst <file>`: Print the CST of a python file using
[LibCST](https://github.com/Instagram/LibCST), which is used in addition to the RustPython parser
in Ruff. E.g. for `if True: pass # comment` everything including the whitespace is represented:
```text
Module {
body: [
Compound(
If(
If {
test: Name(
Name {
value: "True",
lpar: [],
rpar: [],
},
),
body: SimpleStatementSuite(
SimpleStatementSuite {
body: [
Pass(
Pass {
semicolon: None,
},
),
],
leading_whitespace: SimpleWhitespace(
" ",
),
trailing_whitespace: TrailingWhitespace {
whitespace: SimpleWhitespace(
" ",
),
comment: Some(
Comment(
"# comment",
),
),
newline: Newline(
None,
Real,
),
},
},
),
orelse: None,
leading_lines: [],
whitespace_before_test: SimpleWhitespace(
" ",
),
whitespace_after_test: SimpleWhitespace(
"",
),
is_elif: false,
},
),
),
],
header: [],
footer: [],
default_indent: " ",
default_newline: "\n",
has_trailing_newline: true,
encoding: "utf-8",
}
```
- `cargo dev generate-all`: Update `ruff.schema.json`, `docs/configuration.md` and `docs/rules`.
You can also set `RUFF_UPDATE_SCHEMA=1` to update `ruff.schema.json` during `cargo test`.
- `cargo dev generate-cli-help`, `cargo dev generate-docs` and `cargo dev generate-json-schema`:
Update just `docs/configuration.md`, `docs/rules` and `ruff.schema.json` respectively.
- `cargo dev generate-options`: Generate a markdown-compatible table of all `pyproject.toml`
options. Used for <https://beta.ruff.rs/docs/settings/>
- `cargo dev generate-rules-table`: Generate a markdown-compatible table of all rules. Used for <https://beta.ruff.rs/docs/rules/>
- `cargo dev round-trip <python file or jupyter notebook>`: Read a Python file or Jupyter Notebook,
parse it, serialize the parsed representation and write it back. Used to check how good our
representation is so that fixes don't rewrite irrelevant parts of a file.
- `cargo dev format_dev`: See ruff_python_formatter README.md
## Subsystems
### Compilation Pipeline
If we view Ruff as a compiler, in which the inputs are paths to Python files and the outputs are
diagnostics, then our current compilation pipeline proceeds as follows:
1. **File discovery**: Given paths like `foo/`, locate all Python files in any specified subdirectories, taking into account our hierarchical settings system and any `exclude` options.
1. **Package resolution**: Determine the "package root" for every file by traversing over its parent directories and looking for `__init__.py` files.
1. **Cache initialization**: For every "package root", initialize an empty cache.
1. **Analysis**: For every file, in parallel:
1. **Cache read**: If the file is cached (i.e., its modification timestamp hasn't changed since it was last analyzed), short-circuit, and return the cached diagnostics.
1. **Tokenization**: Run the lexer over the file to generate a token stream.
1. **Indexing**: Extract metadata from the token stream, such as: comment ranges, `# noqa` locations, `# isort: off` locations, "doc lines", etc.
1. **Token-based rule evaluation**: Run any lint rules that are based on the contents of the token stream (e.g., commented-out code).
1. **Filesystem-based rule evaluation**: Run any lint rules that are based on the contents of the filesystem (e.g., lack of `__init__.py` file in a package).
1. **Logical line-based rule evaluation**: Run any lint rules that are based on logical lines (e.g., stylistic rules).
1. **Parsing**: Run the parser over the token stream to produce an AST. (This consumes the token stream, so anything that relies on the token stream needs to happen before parsing.)
1. **AST-based rule evaluation**: Run any lint rules that are based on the AST. This includes the vast majority of lint rules. As part of this step, we also build the semantic model for the current file as we traverse over the AST. Some lint rules are evaluated eagerly, as we iterate over the AST, while others are evaluated in a deferred manner (e.g., unused imports, since we can't determine whether an import is unused until we've finished analyzing the entire file), after we've finished the initial traversal.
1. **Import-based rule evaluation**: Run any lint rules that are based on the module's imports (e.g., import sorting). These could, in theory, be included in the AST-based rule evaluation phase — they're just separated for simplicity.
1. **Physical line-based rule evaluation**: Run any lint rules that are based on physical lines (e.g., line-length).
1. **Suppression enforcement**: Remove any violations that are suppressed via `# noqa` directives or `per-file-ignores`.
1. **Cache write**: Write the generated diagnostics to the package cache using the file as a key.
1. **Reporting**: Print diagnostics in the specified format (text, JSON, etc.), to the specified output channel (stdout, a file, etc.).
### Import Categorization
To understand Ruff's import categorization system, we first need to define two concepts:
- "Project root": The directory containing the `pyproject.toml`, `ruff.toml`, or `.ruff.toml` file,
discovered by identifying the "closest" such directory for each Python file. (If you're running
via `ruff --config /path/to/pyproject.toml`, then the current working directory is used as the
"project root".)
- "Package root": The top-most directory defining the Python package that includes a given Python
file. To find the package root for a given Python file, traverse up its parent directories until
you reach a parent directory that doesn't contain an `__init__.py` file (and isn't marked as
a [namespace package](https://beta.ruff.rs/docs/settings/#namespace-packages)); take the directory
just before that, i.e., the first directory in the package.
For example, given:
```text
my_project
├── pyproject.toml
└── src
└── foo
├── __init__.py
└── bar
├── __init__.py
└── baz.py
```
Then when analyzing `baz.py`, the project root would be the top-level directory (`./my_project`),
and the package root would be `./my_project/src/foo`.
#### Project root
The project root does not have a significant impact beyond that all relative paths within the loaded
configuration file are resolved relative to the project root.
For example, to indicate that `bar` above is a namespace package (it isn't, but let's run with it),
the `pyproject.toml` would list `namespace-packages = ["./src/bar"]`, which would resolve
to `my_project/src/bar`.
The same logic applies when providing a configuration file via `--config`. In that case, the
_current working directory_ is used as the project root, and so all paths in that configuration file
are resolved relative to the current working directory. (As a general rule, we want to avoid relying
on the current working directory as much as possible, to ensure that Ruff exhibits the same behavior
regardless of where and how you invoke it — but that's hard to avoid in this case.)
Additionally, if a `pyproject.toml` file _extends_ another configuration file, Ruff will still use
the directory containing that `pyproject.toml` file as the project root. For example, if
`./my_project/pyproject.toml` contains:
```toml
[tool.ruff]
extend = "/path/to/pyproject.toml"
```
Then Ruff will use `./my_project` as the project root, even though the configuration file extends
`/path/to/pyproject.toml`. As such, if the configuration file at `/path/to/pyproject.toml` contains
any relative paths, they will be resolved relative to `./my_project`.
If a project uses nested configuration files, then Ruff would detect multiple project roots, one for
each configuration file.
#### Package root
The package root is used to determine a file's "module path". Consider, again, `baz.py`. In that
case, `./my_project/src/foo` was identified as the package root, so the module path for `baz.py`
would resolve to `foo.bar.baz` — as computed by taking the relative path from the package root
(inclusive of the root itself). The module path can be thought of as "the path you would use to
import the module" (e.g., `import foo.bar.baz`).
The package root and module path are used to, e.g., convert relative to absolute imports, and for
import categorization, as described below.
#### Import categorization
When sorting and formatting import blocks, Ruff categorizes every import into one of five
categories:
1. **"Future"**: the import is a `__future__` import. That's easy: just look at the name of the
imported module!
1. **"Standard library"**: the import comes from the Python standard library (e.g., `import os`).
This is easy too: we include a list of all known standard library modules in Ruff itself, so it's
a simple lookup.
1. **"Local folder"**: the import is a relative import (e.g., `from .foo import bar`). This is easy
too: just check if the import includes a `level` (i.e., a dot-prefix).
1. **"First party"**: the import is part of the current project. (More on this below.)
1. **"Third party"**: everything else.
The real challenge lies in determining whether an import is first-party — everything else is either
trivial, or (as in the case of third-party) merely defined as "not first-party".
There are three ways in which an import can be categorized as "first-party":
1. **Explicit settings**: the import is marked as such via the `known-first-party` setting. (This
should generally be seen as an escape hatch.)
1. **Same-package**: the imported module is in the same package as the current file. This gets back
to the importance of the "package root" and the file's "module path". Imagine that we're
analyzing `baz.py` above. If `baz.py` contains any imports that appear to come from the `foo`
package (e.g., `from foo import bar` or `import foo.bar`), they'll be classified as first-party
automatically. This check is as simple as comparing the first segment of the current file's
module path to the first segment of the import.
1. **Source roots**: Ruff supports a `[src](https://beta.ruff.rs/docs/settings/#src)` setting, which
sets the directories to scan when identifying first-party imports. The algorithm is
straightforward: given an import, like `import foo`, iterate over the directories enumerated in
the `src` setting and, for each directory, check for the existence of a subdirectory `foo` or a
file `foo.py`.
By default, `src` is set to the project root. In the above example, we'd want to set
`src = ["./src"]` to ensure that we locate `./my_project/src/foo` and thus categorize `import foo`
as first-party in `baz.py`. In practice, for this limited example, setting `src = ["./src"]` is
unnecessary, as all imports within `./my_project/src/foo` would be categorized as first-party via
the same-package heuristic; but your project contains multiple packages, you'll want to set `src`
explicitly.

1155
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,12 +21,11 @@ filetime = { version = "0.2.20" }
glob = { version = "0.3.1" }
globset = { version = "0.4.10" }
ignore = { version = "0.4.20" }
insta = { version = "1.28.0" }
insta = { version = "1.31.0", feature = ["filters", "glob"] }
is-macro = { version = "0.2.2" }
itertools = { version = "0.10.5" }
log = { version = "0.4.17" }
memchr = "2.5.0"
nohash-hasher = { version = "0.2.0" }
num-bigint = { version = "0.4.3" }
num-traits = { version = "0.2.15" }
once_cell = { version = "1.17.1" }
@@ -45,20 +44,12 @@ strum = { version = "0.24.1", features = ["strum_macros"] }
strum_macros = { version = "0.24.3" }
syn = { version = "2.0.15" }
test-case = { version = "3.0.0" }
thiserror = { version = "1.0.43" }
toml = { version = "0.7.2" }
wsl = { version = "0.1.0" }
# v0.0.1
libcst = { git = "https://github.com/charliermarsh/LibCST", rev = "80e4c1399f95e5beb532fdd1e209ad2dbb470438" }
# v0.0.3
ruff_text_size = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" }
# v0.0.3
rustpython-ast = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["all-nodes-with-ranges", "num-bigint"]}
# v0.0.3
rustpython-format = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false, features = ["num-bigint"] }
# v0.0.3
rustpython-literal = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca", default-features = false }
# v0.0.3
rustpython-parser = { git = "https://github.com/astral-sh/RustPython-Parser.git", rev = "08ebbe40d7776cac6e3ba66277d435056f2b8dca" , default-features = false, features = ["full-lexer", "all-nodes-with-ranges", "num-bigint"] }
# v1.0.1
libcst = { git = "https://github.com/Instagram/LibCST.git", rev = "3cacca1a1029f05707e50703b49fe3dd860aa839", default-features = false }
[profile.release]
lto = "fat"
@@ -72,7 +63,7 @@ opt-level = 3
# Reduce complexity of a parser function that would trigger a locals limit in a wasm tool.
# https://github.com/bytecodealliance/wasm-tools/blob/b5c3d98e40590512a3b12470ef358d5c7b983b15/crates/wasmparser/src/limits.rs#L29
[profile.dev.package.rustpython-parser]
[profile.dev.package.ruff_python_parser]
opt-level = 1
# Use the `--profile release-debug` flag to show symbols in release mode.

26
LICENSE
View File

@@ -1224,6 +1224,32 @@ are:
SOFTWARE.
"""
- Pyright, licensed as follows:
"""
MIT License
Pyright - A static type checker for the Python language
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
"""
- rust-analyzer/text-size, licensed under the MIT license:
"""
Permission is hereby granted, free of charge, to any

View File

@@ -2,7 +2,7 @@
# Ruff
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![image](https://img.shields.io/pypi/v/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/l/ruff.svg)](https://pypi.python.org/pypi/ruff)
[![image](https://img.shields.io/pypi/pyversions/ruff.svg)](https://pypi.python.org/pypi/ruff)
@@ -14,9 +14,9 @@ An extremely fast Python linter, written in Rust.
<p align="center">
<picture align="center">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/212613422-7faaf278-706b-4294-ad92-236ffcab3430.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/212613257-5f4bca12-6d6b-4c79-9bac-51a4c6d08928.svg">
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/1309177/232603514-c95e9b0f-6b31-43de-9a80-9e844173fd6a.svg">
<source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
<img alt="Shows a bar chart with benchmark results." src="https://user-images.githubusercontent.com/1309177/232603516-4fb4892d-585c-4b20-b810-3db9161831e4.svg">
</picture>
</p>
@@ -32,9 +32,10 @@ An extremely fast Python linter, written in Rust.
- 🔧 Autofix support, for automatic error correction (e.g., automatically remove unused imports)
- 📏 Over [500 built-in rules](https://beta.ruff.rs/docs/rules/)
- ⚖️ [Near-parity](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) with the
built-in Flake8 rule set
built-in Flake8 rule set
- 🔌 Native re-implementations of dozens of Flake8 plugins, like flake8-bugbear
- ⌨️ First-party editor integrations for [VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- ⌨️ First-party [editor integrations](https://beta.ruff.rs/docs/editor-integrations/) for
[VS Code](https://github.com/astral-sh/ruff-vscode) and [more](https://github.com/astral-sh/ruff-lsp)
- 🌎 Monorepo-friendly, with [hierarchical and cascading configuration](https://beta.ruff.rs/docs/configuration/#pyprojecttoml-discovery)
Ruff aims to be orders of magnitude faster than alternative tools while integrating more
@@ -139,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.274
rev: v0.0.280
hooks:
- id: ruff
```
@@ -330,9 +331,11 @@ We're grateful to the maintainers of these tools for their work, and for all
the value they've provided to the Python community.
Ruff's autoformatter is built on a fork of Rome's [`rome_formatter`](https://github.com/rome/tools/tree/main/crates/rome_formatter),
and again draws on both the APIs and implementation details of [Rome](https://github.com/rome/tools),
and again draws on both API and implementation details from [Rome](https://github.com/rome/tools),
[Prettier](https://github.com/prettier/prettier), and [Black](https://github.com/psf/black).
Ruff's import resolver is based on the import resolution algorithm from [Pyright](https://github.com/microsoft/pyright).
Ruff is also influenced by a number of tools outside the Python ecosystem, like
[Clippy](https://github.com/rust-lang/rust-clippy) and [ESLint](https://github.com/eslint/eslint).
@@ -345,6 +348,7 @@ Ruff is released under the MIT license.
Ruff is used by a number of major open-source projects and companies, including:
- Amazon ([AWS SAM](https://github.com/aws/serverless-application-model))
- Anthropic ([Python SDK](https://github.com/anthropics/anthropic-sdk-python))
- [Apache Airflow](https://github.com/apache/airflow)
- AstraZeneca ([Magnus](https://github.com/AstraZeneca/magnus-core))
- Benchling ([Refac](https://github.com/benchling/refac))
@@ -354,26 +358,30 @@ Ruff is used by a number of major open-source projects and companies, including:
- [DVC](https://github.com/iterative/dvc)
- [Dagger](https://github.com/dagger/dagger)
- [Dagster](https://github.com/dagster-io/dagster)
- Databricks ([MLflow](https://github.com/mlflow/mlflow))
- [FastAPI](https://github.com/tiangolo/fastapi)
- [Gradio](https://github.com/gradio-app/gradio)
- [Great Expectations](https://github.com/great-expectations/great_expectations)
- [HTTPX](https://github.com/encode/httpx)
- Hugging Face ([Transformers](https://github.com/huggingface/transformers),
[Datasets](https://github.com/huggingface/datasets),
[Diffusers](https://github.com/huggingface/diffusers))
[Datasets](https://github.com/huggingface/datasets),
[Diffusers](https://github.com/huggingface/diffusers))
- [Hatch](https://github.com/pypa/hatch)
- [Home Assistant](https://github.com/home-assistant/core)
- ING Bank ([popmon](https://github.com/ing-bank/popmon), [probatus](https://github.com/ing-bank/probatus))
- [Ibis](https://github.com/ibis-project/ibis)
- [Jupyter](https://github.com/jupyter-server/jupyter_server)
- [LangChain](https://github.com/hwchase17/langchain)
- [LlamaIndex](https://github.com/jerryjliu/llama_index)
- Matrix ([Synapse](https://github.com/matrix-org/synapse))
- [MegaLinter](https://github.com/oxsecurity/megalinter)
- Meltano ([Meltano CLI](https://github.com/meltano/meltano), [Singer SDK](https://github.com/meltano/sdk))
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
[LightGBM](https://github.com/microsoft/LightGBM))
- Modern Treasury ([Python SDK](https://github.com/Modern-Treasury/modern-treasury-python-sdk))
- Mozilla ([Firefox](https://github.com/mozilla/gecko-dev))
- [MegaLinter](https://github.com/oxsecurity/megalinter)
- Microsoft ([Semantic Kernel](https://github.com/microsoft/semantic-kernel),
[ONNX Runtime](https://github.com/microsoft/onnxruntime),
[LightGBM](https://github.com/microsoft/LightGBM))
- [Mypy](https://github.com/python/mypy)
- Netflix ([Dispatch](https://github.com/Netflix/dispatch))
- [Neon](https://github.com/neondatabase/neon)
- [ONNX](https://github.com/onnx/onnx)
@@ -389,7 +397,7 @@ Ruff is used by a number of major open-source projects and companies, including:
- [PyTorch](https://github.com/pytorch/pytorch)
- [Pydantic](https://github.com/pydantic/pydantic)
- [Pylint](https://github.com/PyCQA/pylint)
- [Pynecone](https://github.com/pynecone-io/pynecone)
- [Reflex](https://github.com/reflex-dev/reflex)
- [Robyn](https://github.com/sansyrox/robyn)
- Scale AI ([Launch SDK](https://github.com/scaleapi/launch-python-client))
- Snowflake ([SnowCLI](https://github.com/Snowflake-Labs/snowcli))
@@ -409,19 +417,20 @@ Ruff is used by a number of major open-source projects and companies, including:
- [featuretools](https://github.com/alteryx/featuretools)
- [meson-python](https://github.com/mesonbuild/meson-python)
- [nox](https://github.com/wntrblm/nox)
- [pip](https://github.com/pypa/pip)
### Show Your Support
If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
```md
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
```
...or `README.rst`:
```rst
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Ruff
```
@@ -429,7 +438,7 @@ If you're using Ruff, consider adding the Ruff badge to project's `README.md`:
...or, as HTML:
```html
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff" style="max-width:100%;"></a>
```
## License
@@ -438,6 +447,6 @@ MIT
<div align="center">
<a target="_blank" href="https://astral.sh" style="background:none">
<img src="https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/svg/Astral.svg">
<img src="https://raw.githubusercontent.com/astral-sh/ruff/main/assets/svg/Astral.svg">
</a>
</div>

View File

@@ -2,7 +2,6 @@
extend-exclude = ["resources", "snapshots"]
[default.extend-words]
trivias = "trivias"
hel = "hel"
whos = "whos"
spawnve = "spawnve"

View File

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

View File

@@ -82,12 +82,12 @@ flake8-to-ruff path/to/.flake8 --plugin flake8-builtins --plugin flake8-quotes
## Limitations
1. Ruff only supports a subset of the Flake configuration options. `flake8-to-ruff` will warn on and
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
ignore unsupported options in the `.flake8` file (or equivalent). (Similarly, Ruff has a few
configuration options that don't exist in Flake8.)
1. Ruff will omit any rule codes that are unimplemented or unsupported by Ruff, including rule
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
codes from unsupported plugins. (See the
[documentation](https://beta.ruff.rs/docs/faq/#how-does-ruff-compare-to-flake8) for the complete
list of supported plugins.)
## License

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.0.274"
version = "0.0.280"
publish = false
authors = { workspace = true }
edition = { workspace = true }
@@ -17,14 +17,18 @@ name = "ruff"
[dependencies]
ruff_cache = { path = "../ruff_cache" }
ruff_diagnostics = { path = "../ruff_diagnostics", features = ["serde"] }
ruff_index = { path = "../ruff_index" }
ruff_macros = { path = "../ruff_macros" }
ruff_python_whitespace = { path = "../ruff_python_whitespace" }
ruff_python_ast = { path = "../ruff_python_ast", features = ["serde"] }
ruff_python_codegen = { path = "../ruff_python_codegen" }
ruff_python_index = { path = "../ruff_python_index" }
ruff_python_literal = { path = "../ruff_python_literal" }
ruff_python_semantic = { path = "../ruff_python_semantic" }
ruff_python_stdlib = { path = "../ruff_python_stdlib" }
ruff_rustpython = { path = "../ruff_rustpython" }
ruff_text_size = { workspace = true }
ruff_textwrap = { path = "../ruff_textwrap" }
ruff_python_trivia = { path = "../ruff_python_trivia" }
ruff_python_parser = { path = "../ruff_python_parser" }
ruff_source_file = { path = "../ruff_source_file", features = ["serde"] }
ruff_text_size = { path = "../ruff_text_size" }
annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true }
@@ -42,8 +46,8 @@ is-macro = { workspace = true }
itertools = { workspace = true }
libcst = { workspace = true }
log = { workspace = true }
memchr = { workspace = true }
natord = { version = "1.0.9" }
nohash-hasher = { workspace = true }
num-bigint = { workspace = true }
num-traits = { workspace = true }
once_cell = { workspace = true }
@@ -53,14 +57,13 @@ path-absolutize = { workspace = true, features = [
] }
pathdiff = { version = "0.2.1" }
pep440_rs = { version = "0.3.1", features = ["serde"] }
phf = { version = "0.11", features = ["macros"] }
pyproject-toml = { version = "0.6.0" }
quick-junit = { version = "0.3.2" }
regex = { workspace = true }
result-like = { version = "0.4.6" }
rustc-hash = { workspace = true }
rustpython-format = { workspace = true }
rustpython-parser = { workspace = true }
schemars = { workspace = true, optional = true }
semver = { version = "1.0.16" }
serde = { workspace = true }
@@ -71,11 +74,12 @@ shellexpand = { workspace = true }
smallvec = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = { version = "1.0.38" }
thiserror = { version = "1.0.43" }
toml = { workspace = true }
typed-arena = { version = "2.0.2" }
unicode-width = { version = "0.1.10" }
unicode_names2 = { version = "0.6.0", git = "https://github.com/youknowone/unicode_names2.git", rev = "4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde" }
wsl = { version = "0.1.0" }
[dev-dependencies]
insta = { workspace = true }
@@ -83,8 +87,10 @@ pretty_assertions = "1.3.0"
test-case = { workspace = true }
# Disable colored output in tests
colored = { workspace = true, features = ["no-color"] }
tempfile = "3.6.0"
[features]
default = []
schemars = ["dep:schemars"]
jupyter_notebook = []
# Enables the UnreachableCode rule
unreachable-code = []

View File

@@ -0,0 +1,11 @@
def func():
assert True
def func():
assert False
def func():
assert True, "oops"
def func():
assert False, "oops"

View File

@@ -0,0 +1,41 @@
def func():
async for i in range(5):
print(i)
def func():
async for i in range(20):
print(i)
else:
return 0
def func():
async for i in range(10):
if i == 5:
return 1
return 0
def func():
async for i in range(111):
if i == 5:
return 1
else:
return 0
return 2
def func():
async for i in range(12):
continue
def func():
async for i in range(1110):
if True:
continue
def func():
async for i in range(13):
break
def func():
async for i in range(1110):
if True:
break

View File

@@ -0,0 +1,41 @@
def func():
for i in range(5):
print(i)
def func():
for i in range(20):
print(i)
else:
return 0
def func():
for i in range(10):
if i == 5:
return 1
return 0
def func():
for i in range(111):
if i == 5:
return 1
else:
return 0
return 2
def func():
for i in range(12):
continue
def func():
for i in range(1110):
if True:
continue
def func():
for i in range(13):
break
def func():
for i in range(1110):
if True:
break

View File

@@ -0,0 +1,108 @@
def func():
if False:
return 0
return 1
def func():
if True:
return 1
return 0
def func():
if False:
return 0
else:
return 1
def func():
if True:
return 1
else:
return 0
def func():
if False:
return 0
else:
return 1
return "unreachable"
def func():
if True:
return 1
else:
return 0
return "unreachable"
def func():
if True:
if True:
return 1
return 2
else:
return 3
return "unreachable2"
def func():
if False:
return 0
def func():
if True:
return 1
def func():
if True:
return 1
elif False:
return 2
else:
return 0
def func():
if False:
return 1
elif True:
return 2
else:
return 0
def func():
if True:
if False:
return 0
elif True:
return 1
else:
return 2
return 3
elif True:
return 4
else:
return 5
return 6
def func():
if False:
return "unreached"
elif False:
return "also unreached"
return "reached"
# Test case found in the Bokeh repository that trigger a false positive.
def func(self, obj: BytesRep) -> bytes:
data = obj["data"]
if isinstance(data, str):
return base64.b64decode(data)
elif isinstance(data, Buffer):
buffer = data
else:
id = data["id"]
if id in self._buffers:
buffer = self._buffers[id]
else:
self.error(f"can't resolve buffer '{id}'")
return buffer.data

View File

@@ -0,0 +1,131 @@
def func(status):
match status:
case _:
return 0
return "unreachable"
def func(status):
match status:
case 1:
return 1
return 0
def func(status):
match status:
case 1:
return 1
case _:
return 0
def func(status):
match status:
case 1 | 2 | 3:
return 5
return 6
def func(status):
match status:
case 1 | 2 | 3:
return 5
case _:
return 10
return 0
def func(status):
match status:
case 0:
return 0
case 1:
return 1
case 1:
return "1 again"
case _:
return 3
def func(status):
i = 0
match status, i:
case _, _:
return 0
def func(status):
i = 0
match status, i:
case _, 0:
return 0
case _, 2:
return 0
def func(point):
match point:
case (0, 0):
print("Origin")
case _:
raise ValueError("oops")
def func(point):
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
def where_is(point):
class Point:
x: int
y: int
match point:
case Point(x=0, y=0):
print("Origin")
case Point(x=0, y=y):
print(f"Y={y}")
case Point(x=x, y=0):
print(f"X={x}")
case Point():
print("Somewhere else")
case _:
print("Not a point")
def func(points):
match points:
case []:
print("No points")
case [Point(0, 0)]:
print("The origin")
case [Point(x, y)]:
print(f"Single point {x}, {y}")
case [Point(0, y1), Point(0, y2)]:
print(f"Two on the Y axis at {y1}, {y2}")
case _:
print("Something else")
def func(point):
match point:
case Point(x, y) if x == y:
print(f"Y=X at {x}")
case Point(x, y):
print(f"Not on the diagonal")
def func():
from enum import Enum
class Color(Enum):
RED = 'red'
GREEN = 'green'
BLUE = 'blue'
color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")

View File

@@ -0,0 +1,5 @@
def func():
raise Exception
def func():
raise "a glass!"

View File

@@ -0,0 +1,23 @@
def func():
pass
def func():
pass
def func():
return
def func():
return 1
def func():
return 1
return "unreachable"
def func():
i = 0
def func():
i = 0
i += 2
return i

View File

@@ -0,0 +1,41 @@
def func():
try:
...
except Exception:
...
except OtherException as e:
...
else:
...
finally:
...
def func():
try:
...
except Exception:
...
def func():
try:
...
except Exception:
...
except OtherException as e:
...
def func():
try:
...
except Exception:
...
except OtherException as e:
...
else:
...
def func():
try:
...
finally:
...

View File

@@ -0,0 +1,121 @@
def func():
while False:
return "unreachable"
return 1
def func():
while False:
return "unreachable"
else:
return 1
def func():
while False:
return "unreachable"
else:
return 1
return "also unreachable"
def func():
while True:
return 1
return "unreachable"
def func():
while True:
return 1
else:
return "unreachable"
def func():
while True:
return 1
else:
return "unreachable"
return "also unreachable"
def func():
i = 0
while False:
i += 1
return i
def func():
i = 0
while True:
i += 1
return i
def func():
while True:
pass
return 1
def func():
i = 0
while True:
if True:
print("ok")
i += 1
return i
def func():
i = 0
while True:
if False:
print("ok")
i += 1
return i
def func():
while True:
if True:
return 1
return 0
def func():
while True:
continue
def func():
while False:
continue
def func():
while True:
break
def func():
while False:
break
def func():
while True:
if True:
continue
def func():
while True:
if True:
break
'''
TODO: because `try` statements aren't handled this triggers a false positive as
the last statement is reached, but the rules thinks it isn't (it doesn't
see/process the break statement).
# Test case found in the Bokeh repository that trigger a false positive.
def bokeh2(self, host: str = DEFAULT_HOST, port: int = DEFAULT_PORT) -> None:
self.stop_serving = False
while True:
try:
self.server = HTTPServer((host, port), HtmlOnlyHandler)
self.host = host
self.port = port
break
except OSError:
log.debug(f"port {port} is in use, trying to next one")
port += 1
self.thread = threading.Thread(target=self._run_web_server)
'''

View File

@@ -1,4 +1,4 @@
from typing import Any, Type
from typing import Annotated, Any, Optional, Type, Union
from typing_extensions import override
# Error
@@ -95,27 +95,27 @@ class Foo:
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
pass
# ANN401
# OK
@override
def foo(self: "Foo", a: Any, *params: str, **options: str) -> int:
pass
# ANN401
# OK
@override
def foo(self: "Foo", a: int, *params: str, **options: str) -> Any:
pass
# ANN401
# OK
@override
def foo(self: "Foo", a: int, *params: Any, **options: Any) -> int:
pass
# ANN401
# OK
@override
def foo(self: "Foo", a: int, *params: Any, **options: str) -> int:
pass
# ANN401
# OK
@override
def foo(self: "Foo", a: int, *params: str, **options: Any) -> int:
pass
@@ -137,3 +137,18 @@ class Foo:
# OK
def f(*args: *tuple[int]) -> None: ...
def f(a: object) -> None: ...
def f(a: str | bytes) -> None: ...
def f(a: Union[str, bytes]) -> None: ...
def f(a: Optional[str]) -> None: ...
def f(a: Annotated[str, ...]) -> None: ...
def f(a: "Union[str, bytes]") -> None: ...
def f(a: int + int) -> None: ...
# ANN401
def f(a: Any | int) -> None: ...
def f(a: int | Any) -> None: ...
def f(a: Union[str, bytes, Any]) -> None: ...
def f(a: Optional[Any]) -> None: ...
def f(a: Annotated[Any, ...]) -> None: ...
def f(a: "Union[str, bytes, Any]") -> None: ...

View File

@@ -0,0 +1,12 @@
import os
print(eval("1+1")) # S307
print(eval("os.getcwd()")) # S307
class Class(object):
def eval(self):
print("hi")
def foo(self):
self.eval() # OK

View File

@@ -1,6 +1,6 @@
"""
Should emit:
B002 - on lines 15 and 20
B002 - on lines 18, 19, and 24
"""
@@ -8,13 +8,17 @@ def this_is_all_fine(n):
x = n + 1
y = 1 + n
z = +x + y
return +z
a = n - 1
b = 1 - n
c = -a - b
return +z, -c
def this_is_buggy(n):
x = ++n
return x
y = --n
return x, y
def this_is_buggy_too(n):
return ++n
return ++n, --n

View File

@@ -177,6 +177,9 @@ def str_okay(value=str("foo")):
def bool_okay(value=bool("bar")):
pass
# Allow immutable bytes() value
def bytes_okay(value=bytes(1)):
pass
# Allow immutable int() value
def int_okay(value=int("12")):

View File

@@ -97,3 +97,10 @@ def f():
# variable name).
for line_ in range(self.header_lines):
fp.readline()
# Regression test: visitor didn't walk the elif test
for key, value in current_crawler_tags.items():
if key:
pass
elif wanted_tag_value != value:
pass

View File

@@ -23,6 +23,10 @@ class Foobar(unittest.TestCase):
with self.assertRaises(Exception):
raise Exception("Evil I say!")
def also_evil_raises(self) -> None:
with self.assertRaises(BaseException):
raise Exception("Evil I say!")
def context_manager_raises(self) -> None:
with self.assertRaises(Exception) as ex:
raise Exception("Context manager is good")
@@ -41,6 +45,9 @@ def test_pytest_raises():
with pytest.raises(Exception):
raise ValueError("Hello")
with pytest.raises(Exception), pytest.raises(ValueError):
raise ValueError("Hello")
with pytest.raises(Exception, "hello"):
raise ValueError("This is fine")

View File

@@ -0,0 +1,27 @@
import re
from re import sub
# B034
re.sub("a", "b", "aaa", re.IGNORECASE)
re.sub("a", "b", "aaa", 5)
re.sub("a", "b", "aaa", 5, re.IGNORECASE)
re.subn("a", "b", "aaa", re.IGNORECASE)
re.subn("a", "b", "aaa", 5)
re.subn("a", "b", "aaa", 5, re.IGNORECASE)
re.split(" ", "a a a a", re.I)
re.split(" ", "a a a a", 2)
re.split(" ", "a a a a", 2, re.I)
sub("a", "b", "aaa", re.IGNORECASE)
# OK
re.sub("a", "b", "aaa")
re.sub("a", "b", "aaa", flags=re.IGNORECASE)
re.sub("a", "b", "aaa", count=5)
re.sub("a", "b", "aaa", count=5, flags=re.IGNORECASE)
re.subn("a", "b", "aaa")
re.subn("a", "b", "aaa", flags=re.IGNORECASE)
re.subn("a", "b", "aaa", count=5)
re.subn("a", "b", "aaa", count=5, flags=re.IGNORECASE)
re.split(" ", "a a a a", flags=re.I)
re.split(" ", "a a a a", maxsplit=2)
re.split(" ", "a a a a", maxsplit=2, flags=re.I)

View File

@@ -14,9 +14,10 @@ except AssertionError:
except Exception as err:
assert err
raise Exception("No cause here...")
except BaseException as base_err:
# Might use this instead of bare raise with the `.with_traceback()` method
raise base_err
except BaseException as err:
raise err
except BaseException as err:
raise some_other_err
finally:
raise Exception("Nothing to chain from, so no warning here")

View File

@@ -17,3 +17,37 @@ from typing import TypedDict
class MyClass(TypedDict):
id: int
from threading import Event
class CustomEvent(Event):
def set(self) -> None:
...
def str(self) -> None:
...
from logging import Filter, LogRecord
class CustomFilter(Filter):
def filter(self, record: LogRecord) -> bool:
...
def str(self) -> None:
...
from typing_extensions import override
class MyClass:
@override
def str(self):
pass
def int(self):
pass

View File

@@ -12,7 +12,8 @@ set(reversed(x))
sorted(list(x))
sorted(tuple(x))
sorted(sorted(x))
sorted(sorted(x, key=lambda y: y))
sorted(sorted(x, key=foo, reverse=False), reverse=False, key=foo)
sorted(sorted(x, reverse=True), reverse=True)
sorted(reversed(x))
sorted(list(x), key=lambda y: y)
tuple(
@@ -21,3 +22,9 @@ tuple(
"o"]
)
)
# Nested sorts with differing keyword arguments. Not flagged.
sorted(sorted(x, key=lambda y: y))
sorted(sorted(x, key=lambda y: y), key=lambda x: x)
sorted(sorted(x), reverse=True)
sorted(sorted(x, reverse=False), reverse=True)

View File

@@ -25,10 +25,15 @@ map(lambda x=2, y=1: x + y, nums, nums)
set(map(lambda x, y: x, nums, nums))
def myfunc(arg1: int, arg2: int = 4):
def func(arg1: int, arg2: int = 4):
return 2 * arg1 + arg2
list(map(myfunc, nums))
# Non-error: `func` is not a lambda.
list(map(func, nums))
[x for x in nums]
# False positive: need to preserve the late-binding of `x` in the inner lambda.
map(lambda x: lambda: x, range(4))
# Error: the `x` is overridden by the inner lambda.
map(lambda x: lambda x: x, range(4))

View File

@@ -19,3 +19,6 @@ from datetime import datetime
# no args unqualified
datetime(2000, 1, 1, 0, 0, 0)
# uses `astimezone` method
datetime(2000, 1, 1, 0, 0, 0).astimezone()

View File

@@ -7,3 +7,6 @@ from datetime import datetime
# unqualified
datetime.today()
# uses `astimezone` method
datetime.today().astimezone()

View File

@@ -7,3 +7,6 @@ from datetime import datetime
# unqualified
datetime.utcnow()
# uses `astimezone` method
datetime.utcnow().astimezone()

View File

@@ -7,3 +7,6 @@ from datetime import datetime
# unqualified
datetime.utcfromtimestamp(1234)
# uses `astimezone` method
datetime.utcfromtimestamp(1234).astimezone()

View File

@@ -16,3 +16,6 @@ from datetime import datetime
# no args unqualified
datetime.now()
# uses `astimezone` method
datetime.now().astimezone()

View File

@@ -16,3 +16,6 @@ from datetime import datetime
# no args unqualified
datetime.fromtimestamp(1234)
# uses `astimezone` method
datetime.fromtimestamp(1234).astimezone()

View File

@@ -111,3 +111,19 @@ class PerfectlyFine(models.Model):
@property
def random_property(self):
return "%s" % self
class MultipleConsecutiveFields(models.Model):
"""Model that contains multiple out-of-order field definitions in a row."""
class Meta:
verbose_name = "test"
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
def get_absolute_url(self):
pass
middle_name = models.CharField(max_length=32)

View File

@@ -0,0 +1,2 @@
#!/usr/bin/env python

View File

@@ -50,3 +50,12 @@ _ = """a""" "b"
_ = 'a' "b"
_ = rf"a" rf"b"
# Single-line explicit concatenation should be ignored.
_ = "abc" + "def" + "ghi"
_ = foo + "abc" + "def"
_ = "abc" + foo + "def"
_ = "abc" + "def" + foo
_ = foo + bar + "abc"
_ = "abc" + foo + bar
_ = foo + "abc" + bar

View File

@@ -5,15 +5,18 @@ import matplotlib.pyplot # unconventional
import numpy # unconventional
import pandas # unconventional
import seaborn # unconventional
import tkinter # unconventional
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import numpy as nmp # unconventional
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import tkinter as tkr # unconventional
import altair as alt # conventional
import matplotlib.pyplot as plt # conventional
import numpy as np # conventional
import pandas as pd # conventional
import seaborn as sns # conventional
import tkinter as tk # conventional

View File

@@ -1,5 +1,10 @@
import logging
from distutils import log
from logging_setup import logger
logging.warn("Hello World!")
log.warn("Hello world!") # This shouldn't be considered as a logger candidate
logger.warn("Hello world!")
logging . warn("Hello World!")

View File

@@ -0,0 +1,6 @@
import sys
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info

View File

@@ -0,0 +1,6 @@
import sys
if sys.version == 'Python 2.7.10': ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if 'linux' == sys.platform: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if hasattr(sys, 'maxint'): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info
if sys.maxsize == 42: ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info

View File

@@ -0,0 +1,31 @@
import sys
if sys.version_info[0] == 2: ...
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:1] == (2,): ...
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
if sys.version_info < (3, 5): ...
if sys.version_info >= (3, 5): ...
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info

View File

@@ -0,0 +1,31 @@
import sys
if sys.version_info[0] == 2: ...
if sys.version_info[0] == True: ... # Y003 Unrecognized sys.version_info check # E712 comparison to True should be 'if cond is True:' or 'if cond:'
if sys.version_info[0.0] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[False] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0j] == 2: ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0] == (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[0] == '2': ... # Y003 Unrecognized sys.version_info check
if sys.version_info[1:] >= (7, 11): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[::-1] < (11, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:3] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:True] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:1] == (2,): ...
if sys.version_info[:1] == (True,): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:1] == (2, 7): ... # Y005 Version comparison must be against a length-1 tuple
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:2] == (2,): ... # Y005 Version comparison must be against a length-2 tuple
if sys.version_info[:2] == "lol": ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:2.0] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:2j] >= (3, 9): ... # Y003 Unrecognized sys.version_info check
if sys.version_info[:, :] >= (2, 7): ... # Y003 Unrecognized sys.version_info check
if sys.version_info < [3, 0]: ... # Y003 Unrecognized sys.version_info check
if sys.version_info < ('3', '0'): ... # Y003 Unrecognized sys.version_info check
if sys.version_info >= (3, 4, 3): ... # Y004 Version comparison must use only major and minor version
if sys.version_info == (3, 4): ... # Y006 Use only < and >= for version comparisons
if sys.version_info > (3, 0): ... # Y006 Use only < and >= for version comparisons
if sys.version_info <= (3, 0): ... # Y006 Use only < and >= for version comparisons
if sys.version_info < (3, 5): ...
if sys.version_info >= (3, 5): ...
if (2, 7) <= sys.version_info < (3, 5): ... # Y002 If test must be a simple comparison against sys.platform or sys.version_info

View File

@@ -0,0 +1,15 @@
import sys
from sys import version_info
if sys.version_info >= (3, 4, 3): ... # PYI004
if sys.version_info < (3, 4, 3): ... # PYI004
if sys.version_info == (3, 4, 3): ... # PYI004
if sys.version_info != (3, 4, 3): ... # PYI004
if sys.version_info[0] == 2: ...
if version_info[0] == 2: ...
if sys.version_info < (3, 5): ...
if version_info >= (3, 5): ...
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:1] == (2,): ...
if sys.platform == 'linux': ...

View File

@@ -0,0 +1,15 @@
import sys
from sys import version_info
if sys.version_info >= (3, 4, 3): ... # PYI004
if sys.version_info < (3, 4, 3): ... # PYI004
if sys.version_info == (3, 4, 3): ... # PYI004
if sys.version_info != (3, 4, 3): ... # PYI004
if sys.version_info[0] == 2: ...
if version_info[0] == 2: ...
if sys.version_info < (3, 5): ...
if version_info >= (3, 5): ...
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:1] == (2,): ...
if sys.platform == 'linux': ...

View File

@@ -0,0 +1,14 @@
import sys
from sys import platform, version_info
if sys.version_info[:1] == (2, 7): ... # Y005
if sys.version_info[:2] == (2,): ... # Y005
if sys.version_info[0] == 2: ...
if version_info[0] == 2: ...
if sys.version_info < (3, 5): ...
if version_info >= (3, 5): ...
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:1] == (2,): ...
if platform == 'linux': ...

View File

@@ -0,0 +1,14 @@
import sys
from sys import platform, version_info
if sys.version_info[:1] == (2, 7): ... # Y005
if sys.version_info[:2] == (2,): ... # Y005
if sys.version_info[0] == 2: ...
if version_info[0] == 2: ...
if sys.version_info < (3, 5): ...
if version_info >= (3, 5): ...
if sys.version_info[:2] == (2, 7): ...
if sys.version_info[:1] == (2,): ...
if platform == 'linux': ...

View File

@@ -91,3 +91,4 @@ field27 = list[str]
field28 = builtins.str
field29 = str
field30 = str | bytes | None
field31: typing.Final = field30

View File

@@ -98,3 +98,4 @@ field27 = list[str]
field28 = builtins.str
field29 = str
field30 = str | bytes | None
field31: typing.Final = field30

View File

@@ -1,3 +1,5 @@
import typing
# Shouldn't affect non-union field types.
field1: str
@@ -30,3 +32,45 @@ field10: (str | int) | str # PYI016: Duplicate union member `str`
# Should emit for nested unions.
field11: dict[int | int, str]
# Should emit for unions with more than two cases
field12: int | int | int # Error
field13: int | int | int | int # Error
# Should emit for unions with more than two cases, even if not directly adjacent
field14: int | int | str | int # Error
# Should emit for duplicate literal types; also covered by PYI030
field15: typing.Literal[1] | typing.Literal[1] # Error
# Shouldn't emit if in new parent type
field16: int | dict[int, str] # OK
# Shouldn't emit if not in a union parent
field17: dict[int, int] # OK
# Should emit in cases with newlines
field18: typing.Union[
set[
int # foo
],
set[
int # bar
],
] # Error, newline and comment will not be emitted in message
# Should emit in cases with `typing.Union` instead of `|`
field19: typing.Union[int, int] # Error
# Should emit in cases with nested `typing.Union`
field20: typing.Union[int, typing.Union[int, str]] # Error
# Should emit in cases with mixed `typing.Union` and `|`
field21: typing.Union[int, int | str] # Error
# Should emit only once in cases with multiple nested `typing.Union`
field22: typing.Union[int, typing.Union[int, typing.Union[int, int]]] # Error
# Should emit in cases with newlines
field23: set[ # foo
int] | set[int]

View File

@@ -0,0 +1,14 @@
var: int
a = var # OK
b = c = int # OK
a.b = int # OK
d, e = int, str # OK
f, g, h = int, str, TypeVar("T") # OK
i: TypeAlias = int | str # OK
j: TypeAlias = int # OK

View File

@@ -0,0 +1,14 @@
var: int
a = var # OK
b = c = int # PYI017
a.b = int # PYI017
d, e = int, str # PYI017
f, g, h = int, str, TypeVar("T") # PYI017
i: TypeAlias = int | str # OK
j: TypeAlias = int # OK

View File

@@ -0,0 +1,12 @@
import typing
from typing import TypeVar
_T = typing.TypeVar("_T")
_P = TypeVar("_P")
# OK
_UsedTypeVar = TypeVar("_UsedTypeVar")
def func(arg: _UsedTypeVar) -> _UsedTypeVar: ...
_A, _B = TypeVar("_A"), TypeVar("_B")
_C = _D = TypeVar("_C")

View File

@@ -0,0 +1,12 @@
import typing
from typing import TypeVar
_T = typing.TypeVar("_T")
_P = TypeVar("_P")
# OK
_UsedTypeVar = TypeVar("_UsedTypeVar")
def func(arg: _UsedTypeVar) -> _UsedTypeVar: ...
_A, _B = TypeVar("_A"), TypeVar("_B")
_C = _D = TypeVar("_C")

View File

@@ -0,0 +1,19 @@
import typing
from typing import TypeAlias, Literal, Any
NewAny = Any
OptionalStr = typing.Optional[str]
Foo = Literal["foo"]
IntOrStr = int | str
AliasNone = None
NewAny: typing.TypeAlias = Any
OptionalStr: TypeAlias = typing.Optional[str]
Foo: typing.TypeAlias = Literal["foo"]
IntOrStr: TypeAlias = int | str
IntOrFloat: Foo = int | float
AliasNone: typing.TypeAlias = None
# these are ok
VarAlias = str
AliasFoo = Foo

View File

@@ -0,0 +1,18 @@
from typing import Literal, Any
NewAny = Any
OptionalStr = typing.Optional[str]
Foo = Literal["foo"]
IntOrStr = int | str
AliasNone = None
NewAny: typing.TypeAlias = Any
OptionalStr: TypeAlias = typing.Optional[str]
Foo: typing.TypeAlias = Literal["foo"]
IntOrStr: TypeAlias = int | str
IntOrFloat: Foo = int | float
AliasNone: typing.TypeAlias = None
# these are ok
VarAlias = str
AliasFoo = Foo

View File

@@ -0,0 +1,24 @@
from typing import Literal
# Shouldn't emit for any cases in the non-stub file for compatibility with flake8-pyi.
# Note that this rule could be applied here in the future.
field1: Literal[1] # OK
field2: Literal[1] | Literal[2] # OK
def func1(arg1: Literal[1] | Literal[2]): # OK
print(arg1)
def func2() -> Literal[1] | Literal[2]: # OK
return "my Literal[1]ing"
field3: Literal[1] | Literal[2] | str # OK
field4: str | Literal[1] | Literal[2] # OK
field5: Literal[1] | str | Literal[2] # OK
field6: Literal[1] | bool | Literal[2] | str # OK
field7 = Literal[1] | Literal[2] # OK
field8: Literal[1] | (Literal[2] | str) # OK
field9: Literal[1] | (Literal[2] | str) # OK
field10: (Literal[1] | str) | Literal[2] # OK
field11: dict[Literal[1] | Literal[2], str] # OK

View File

@@ -0,0 +1,86 @@
import typing
import typing_extensions
from typing import Literal
# Shouldn't affect non-union field types.
field1: Literal[1] # OK
# Should emit for duplicate field types.
field2: Literal[1] | Literal[2] # Error
# Should emit for union types in arguments.
def func1(arg1: Literal[1] | Literal[2]): # Error
print(arg1)
# Should emit for unions in return types.
def func2() -> Literal[1] | Literal[2]: # Error
return "my Literal[1]ing"
# Should emit in longer unions, even if not directly adjacent.
field3: Literal[1] | Literal[2] | str # Error
field4: str | Literal[1] | Literal[2] # Error
field5: Literal[1] | str | Literal[2] # Error
field6: Literal[1] | bool | Literal[2] | str # Error
# Should emit for non-type unions.
field7 = Literal[1] | Literal[2] # Error
# Should emit for parenthesized unions.
field8: Literal[1] | (Literal[2] | str) # Error
# Should handle user parentheses when fixing.
field9: Literal[1] | (Literal[2] | str) # Error
field10: (Literal[1] | str) | Literal[2] # Error
# Should emit for union in generic parent type.
field11: dict[Literal[1] | Literal[2], str] # Error
# Should emit for unions with more than two cases
field12: Literal[1] | Literal[2] | Literal[3] # Error
field13: Literal[1] | Literal[2] | Literal[3] | Literal[4] # Error
# Should emit for unions with more than two cases, even if not directly adjacent
field14: Literal[1] | Literal[2] | str | Literal[3] # Error
# Should emit for unions with mixed literal internal types
field15: Literal[1] | Literal["foo"] | Literal[True] # Error
# Shouldn't emit for duplicate field types with same value; covered by Y016
field16: Literal[1] | Literal[1] # OK
# Shouldn't emit if in new parent type
field17: Literal[1] | dict[Literal[2], str] # OK
# Shouldn't emit if not in a union parent
field18: dict[Literal[1], Literal[2]] # OK
# Should respect name of literal type used
field19: typing.Literal[1] | typing.Literal[2] # Error
# Should emit in cases with newlines
field20: typing.Union[
Literal[
1 # test
],
Literal[2],
] # Error, newline and comment will not be emitted in message
# Should handle multiple unions with multiple members
field21: Literal[1, 2] | Literal[3, 4] # Error
# Should emit in cases with `typing.Union` instead of `|`
field22: typing.Union[Literal[1], Literal[2]] # Error
# Should emit in cases with `typing_extensions.Literal`
field23: typing_extensions.Literal[1] | typing_extensions.Literal[2] # Error
# Should emit in cases with nested `typing.Union`
field24: typing.Union[Literal[1], typing.Union[Literal[2], str]] # Error
# Should emit in cases with mixed `typing.Union` and `|`
field25: typing.Union[Literal[1], Literal[2] | str] # Error
# Should emit only once in cases with multiple nested `typing.Union`
field24: typing.Union[Literal[1], typing.Union[Literal[2], typing.Union[Literal[3], Literal[4]]]] # Error

View File

@@ -0,0 +1,75 @@
import builtins
import types
import typing
from collections.abc import Awaitable
from types import TracebackType
from typing import Any, Type
import _typeshed
import typing_extensions
from _typeshed import Unused
class GoodOne:
def __exit__(self, *args: object) -> None: ...
async def __aexit__(self, *args) -> str: ...
class GoodTwo:
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
class GoodThree:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
class GoodFour:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
class GoodFive:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
class GoodSix:
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
class GoodSeven:
def __exit__(self, *args: Unused) -> bool: ...
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
class GoodEight:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodNine:
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodTen:
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class BadOne:
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
async def __aexit__(self) -> None: ... # PYI036: Missing args
class BadTwo:
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ...# PYI036: Extra arg must have default
class BadThree:
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
class BadFour:
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
class BadFive:
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default

View File

@@ -0,0 +1,75 @@
import builtins
import types
import typing
from collections.abc import Awaitable
from types import TracebackType
from typing import Any, Type
import _typeshed
import typing_extensions
from _typeshed import Unused
class GoodOne:
def __exit__(self, *args: object) -> None: ...
async def __aexit__(self, *args) -> str: ...
class GoodTwo:
def __exit__(self, typ: type[builtins.BaseException] | None, *args: builtins.object) -> bool | None: ...
async def __aexit__(self, /, typ: Type[BaseException] | None, *args: object, **kwargs) -> bool: ...
class GoodThree:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: object) -> None: ...
async def __aexit__(self, typ: typing_extensions.Type[BaseException] | None, __exc: BaseException | None, *args: object) -> None: ...
class GoodFour:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: types.TracebackType | None, *args: list[None]) -> None: ...
class GoodFive:
def __exit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: int, **kwargs: str) -> None: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None) -> Awaitable[None]: ...
class GoodSix:
def __exit__(self, typ: object, exc: builtins.object, tb: object) -> None: ...
async def __aexit__(self, typ: object, exc: object, tb: builtins.object) -> None: ...
class GoodSeven:
def __exit__(self, *args: Unused) -> bool: ...
async def __aexit__(self, typ: Type[BaseException] | None, *args: _typeshed.Unused) -> Awaitable[None]: ...
class GoodEight:
def __exit__(self, __typ: typing.Type[BaseException] | None, exc: BaseException | None, *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None, weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodNine:
def __exit__(self, __typ: typing.Union[typing.Type[BaseException] , None], exc: typing.Union[BaseException , None], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Union[typing.Type[BaseException], None], exc: typing.Union[BaseException , None], tb: typing.Union[TracebackType , None], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class GoodTen:
def __exit__(self, __typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], *args: _typeshed.Unused) -> bool: ...
async def __aexit__(self, typ: typing.Optional[typing.Type[BaseException]], exc: typing.Optional[BaseException], tb: typing.Optional[TracebackType], weird_extra_arg: int = ..., *args: Unused, **kwargs: Unused) -> Awaitable[None]: ...
class BadOne:
def __exit__(self, *args: Any) -> None: ... # PYI036: Bad star-args annotation
async def __aexit__(self) -> None: ... # PYI036: Missing args
class BadTwo:
def __exit__(self, typ, exc, tb, weird_extra_arg) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg1, weird_extra_arg2) -> None: ...# PYI036: kwargs must have default
class BadThree:
def __exit__(self, typ: type[BaseException], exc: BaseException | None, tb: TracebackType | None) -> None: ... # PYI036: First arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException, __tb: TracebackType) -> bool | None: ... # PYI036: Second arg has bad annotation
class BadFour:
def __exit__(self, typ: typing.Optional[type[BaseException]], exc: typing.Union[BaseException, None], tb: TracebackType) -> None: ... # PYI036: Third arg has bad annotation
async def __aexit__(self, __typ: type[BaseException] | None, __exc: BaseException | None, __tb: typing.Union[TracebackType, None, int]) -> bool | None: ... # PYI036: Third arg has bad annotation
class BadFive:
def __exit__(self, typ: BaseException | None, *args: list[str]) -> bool: ... # PYI036: Bad star-args annotation
async def __aexit__(self, /, typ: type[BaseException] | None, *args: Any) -> Awaitable[None]: ... # PYI036: Bad star-args annotation
class BadSix:
def __exit__(self, typ, exc, tb, weird_extra_arg, extra_arg2 = None) -> None: ... # PYI036: Extra arg must have default
async def __aexit__(self, typ, exc, tb, *, weird_extra_arg) -> None: ... # PYI036: kwargs must have default

View File

@@ -0,0 +1,47 @@
from typing import (
Union,
)
from typing_extensions import (
TypeAlias,
)
TA0: TypeAlias = int
TA1: TypeAlias = int | float | bool
TA2: TypeAlias = Union[int, float, bool]
def good1(arg: int) -> int | bool:
...
def good2(arg: int, arg2: int | bool) -> None:
...
def f0(arg1: float | int) -> None:
...
def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None:
...
def f2(arg1: int, /, arg2: int | int | float) -> None:
...
def f3(arg1: int, *args: Union[int | int | float]) -> None:
...
async def f4(**kwargs: int | int | float) -> None:
...
class Foo:
def good(self, arg: int) -> None:
...
def bad(self, arg: int | float | complex) -> None:
...

View File

@@ -0,0 +1,39 @@
from typing import (
Union,
)
from typing_extensions import (
TypeAlias,
)
# Type aliases not flagged
TA0: TypeAlias = int
TA1: TypeAlias = int | float | bool
TA2: TypeAlias = Union[int, float, bool]
def good1(arg: int) -> int | bool: ...
def good2(arg: int, arg2: int | bool) -> None: ...
def f0(arg1: float | int) -> None: ... # PYI041
def f1(arg1: float, *, arg2: float | list[str] | type[bool] | complex) -> None: ... # PYI041
def f2(arg1: int, /, arg2: int | int | float) -> None: ... # PYI041
def f3(arg1: int, *args: Union[int | int | float]) -> None: ... # PYI041
async def f4(**kwargs: int | int | float) -> None: ... # PYI041
class Foo:
def good(self, arg: int) -> None: ...
def bad(self, arg: int | float | complex) -> None: ... # PYI041

View File

@@ -0,0 +1,18 @@
import typing
from typing import Protocol
class _Foo(Protocol):
bar: int
class _Bar(typing.Protocol):
bar: int
# OK
class _UsedPrivateProtocol(Protocol):
bar: int
def uses__UsedPrivateProtocol(arg: _UsedPrivateProtocol) -> None: ...

View File

@@ -0,0 +1,18 @@
import typing
from typing import Protocol
class _Foo(object, Protocol):
bar: int
class _Bar(typing.Protocol):
bar: int
# OK
class _UsedPrivateProtocol(Protocol):
bar: int
def uses__UsedPrivateProtocol(arg: _UsedPrivateProtocol) -> None: ...

View File

@@ -0,0 +1,22 @@
import typing
import sys
from typing import TypeAlias
_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str
# OK
_UsedPrivateTypeAlias: TypeAlias = int | None
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...
if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None
def func2(arg: _PrivateTypeAlias) -> None: ...

View File

@@ -0,0 +1,22 @@
import typing
import sys
from typing import TypeAlias
_UnusedPrivateTypeAlias: TypeAlias = int | None
_T: typing.TypeAlias = str
# OK
_UsedPrivateTypeAlias: TypeAlias = int | None
def func(arg: _UsedPrivateTypeAlias) -> _UsedPrivateTypeAlias:
...
if sys.version_info > (3, 9):
_PrivateTypeAlias: TypeAlias = str | None
else:
_PrivateTypeAlias: TypeAlias = float | None
def func2(arg: _PrivateTypeAlias) -> None: ...

View File

@@ -0,0 +1,18 @@
import typing
from typing import TypedDict
class _UnusedTypedDict(TypedDict):
foo: str
class _UnusedTypedDict2(typing.TypedDict):
bar: int
class _UsedTypedDict(TypedDict):
foo: bytes
class _CustomClass(_UsedTypedDict):
bar: list[int]

View File

@@ -0,0 +1,32 @@
import sys
import typing
from typing import TypedDict
class _UnusedTypedDict(TypedDict):
foo: str
class _UnusedTypedDict2(typing.TypedDict):
bar: int
# OK
class _UsedTypedDict(TypedDict):
foo: bytes
class _CustomClass(_UsedTypedDict):
bar: list[int]
if sys.version_info >= (3, 10):
class _UsedTypedDict2(TypedDict):
foo: int
else:
class _UsedTypedDict2(TypedDict):
foo: float
class _CustomClass2(_UsedTypedDict2):
bar: list[int]

View File

@@ -36,3 +36,11 @@ bar: str = "51 character stringgggggggggggggggggggggggggggggggg"
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg"
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff"
class Demo:
"""Docstrings are excluded from this rule. Some padding."""
def func() -> None:
"""Docstrings are excluded from this rule. Some padding."""

View File

@@ -28,3 +28,9 @@ bar: str = "51 character stringgggggggggggggggggggggggggggggggg" # Error: PYI05
baz: bytes = b"50 character byte stringgggggggggggggggggggggggggg" # OK
qux: bytes = b"51 character byte stringggggggggggggggggggggggggggg\xff" # Error: PYI053
class Demo:
"""Docstrings are excluded from this rule. Some padding.""" # OK
def func() -> None:
"""Docstrings are excluded from this rule. Some padding.""" # OK

View File

@@ -0,0 +1,12 @@
__all__ = ["A", "B", "C"]
# Errors
__all__.append("D")
__all__.extend(["E", "Foo"])
__all__.remove("A")
# OK
__all__ += ["D"]
foo = ["Hello"]
foo.append("World")
foo.bar.append("World")

View File

@@ -0,0 +1,12 @@
__all__ = ["A", "B", "C"]
# Errors
__all__.append("D")
__all__.extend(["E", "Foo"])
__all__.remove("A")
# OK
__all__ += ["D"]
foo = ["Hello"]
foo.append("World")
foo.bar.append("World")

View File

@@ -11,6 +11,10 @@ async def test_ok_trivial_with():
with context_manager_under_test():
pass
with pytest.raises(ValueError):
with context_manager_under_test():
raise ValueError
with pytest.raises(AttributeError):
async with context_manager_under_test():
pass
@@ -24,6 +28,16 @@ def test_ok_complex_single_call():
)
def test_ok_func_and_class():
with pytest.raises(AttributeError):
class A:
pass
with pytest.raises(AttributeError):
def f():
pass
def test_error_multiple_statements():
with pytest.raises(AttributeError):
len([])
@@ -47,13 +61,10 @@ async def test_error_complex_statement():
while True:
[].size
with pytest.raises(AttributeError):
with context_manager_under_test():
[].size
with pytest.raises(AttributeError):
async with context_manager_under_test():
[].size
if True:
raise Exception
def test_error_try():

View File

@@ -29,6 +29,26 @@ raise TypeError(
# Hello, world!
)
# OK
raise AssertionError
# OK
raise AttributeError("test message")
def return_error():
return ValueError("Something")
# OK
raise return_error()
class Class:
@staticmethod
def error():
return ValueError("Something")
# OK
raise Class.error()

View File

@@ -100,6 +100,14 @@ if node.module0123456789:
):
print("Bad module!")
# SIM102
# Regression test for https://github.com/apache/airflow/blob/145b16caaa43f0c42bffd97344df916c602cddde/airflow/configuration.py#L1161
if a:
if b:
if c:
print("if")
elif d:
print("elif")
# OK
if a:
@@ -148,3 +156,12 @@ if False:
if True:
if a:
pass
# SIM102
def f():
if a:
pass
elif b:
if c:
d

View File

@@ -14,6 +14,12 @@ try:
except (ValueError, OSError):
pass
# SIM105
try:
foo()
except (ValueError, OSError) as e:
pass
# SIM105
try:
foo()
@@ -94,3 +100,13 @@ def with_comment():
foo()
except (ValueError, OSError):
pass # Trailing comment.
try:
print()
except ("not", "an", "exception"):
pass
try:
print()
except "not an exception":
pass

View File

@@ -23,7 +23,7 @@ elif a:
else:
b = 2
# OK (false negative)
# SIM108
if True:
pass
else:

View File

@@ -94,3 +94,23 @@ if result.eofs == "F":
errors = 1
else:
errors = 1
if a:
# Ignore branches with diverging comments because it means we're repeating
# the bodies because we have different reasons for each branch
x = 1
elif c:
x = 1
def foo():
a = True
b = False
if a > b: # end-of-line
return 3
elif a == b:
return 3
elif a < b: # end-of-line
return 4
elif b is None:
return 4

View File

@@ -1,7 +1,15 @@
import contextlib
import pathlib
import pathlib as pl
from pathlib import Path
from pathlib import Path as P
# SIM115
f = open("foo.txt")
f = Path("foo.txt").open()
f = pathlib.Path("foo.txt").open()
f = pl.Path("foo.txt").open()
f = P("foo.txt").open()
data = f.read()
f.close()

View File

@@ -84,3 +84,15 @@ elif func_name == "remove":
return "D"
elif func_name == "move":
return "MV"
# OK
def no_return_in_else(platform):
if platform == "linux":
return "auditwheel repair -w {dest_dir} {wheel}"
elif platform == "macos":
return "delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}"
elif platform == "windows":
return ""
else:
msg = f"Unknown platform: {platform!r}"
raise ValueError(msg)

View File

@@ -1,11 +1,19 @@
key in obj.keys() # SIM118
key not in obj.keys() # SIM118
foo["bar"] in obj.keys() # SIM118
foo["bar"] not in obj.keys() # SIM118
foo['bar'] in obj.keys() # SIM118
foo['bar'] not in obj.keys() # SIM118
foo() in obj.keys() # SIM118
foo() not in obj.keys() # SIM118
for key in obj.keys(): # SIM118
pass
@@ -22,3 +30,13 @@ for key in list(obj.keys()):
(k for k in obj.keys()) # SIM118
key in (obj or {}).keys() # SIM118
from typing import KeysView
class Foo:
def keys(self) -> KeysView[object]:
...
def __contains__(self, key: object) -> bool:
return key in self.keys() # OK

View File

@@ -38,6 +38,15 @@ if key in a_dict:
else:
vars[idx] = "defaultß9💣26789ß9💣26789ß9💣26789ß9💣26789ß9💣26789"
# SIM401
if foo():
pass
else:
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"
###
# Negative cases
###
@@ -105,12 +114,3 @@ elif key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"
# OK (false negative for nested else)
if foo():
pass
else:
if key in a_dict:
vars[idx] = a_dict[key]
else:
vars[idx] = "default"

View File

@@ -4,3 +4,10 @@ class Bad(str): # SLOT000
class Good(str): # Ok
__slots__ = ["foo"]
from enum import Enum
class Fine(str, Enum): # Ok
__slots__ = ["foo"]

View File

@@ -1,6 +1,12 @@
# T002 - accepted
# TODO (evanrittenhouse): this has an author
# TODO(evanrittenhouse): this also has an author
# TODO(evanrittenhouse): this has an author
# TODO (evanrittenhouse) and more: this has an author
# TODO(evanrittenhouse) and more: this has an author
# TODO@mayrholu: this has an author
# TODO @mayrholu: this has an author
# TODO@mayrholu and more: this has an author
# TODO @mayrholu and more: this has an author
# T002 - errors
# TODO: this has no author
# FIXME: neither does this

View File

@@ -0,0 +1,15 @@
from pathlib import Path, PurePath
from pathlib import Path as pth
# match
_ = Path(".")
_ = pth(".")
_ = PurePath(".")
_ = Path("")
# no match
_ = Path()
print(".")
Path("file.txt")
Path(".", "folder")
PurePath(".", "folder")

View File

@@ -0,0 +1,14 @@
import os.path
from pathlib import Path
from os.path import getsize
os.path.getsize("filename")
os.path.getsize(b"filename")
os.path.getsize(Path("filename"))
os.path.getsize(__file__)
getsize("filename")
getsize(b"filename")
getsize(Path("filename"))
getsize(__file__)

View File

@@ -0,0 +1,12 @@
import os.path
from pathlib import Path
from os.path import getatime
os.path.getatime("filename")
os.path.getatime(b"filename")
os.path.getatime(Path("filename"))
getatime("filename")
getatime(b"filename")
getatime(Path("filename"))

View File

@@ -0,0 +1,13 @@
import os.path
from pathlib import Path
from os.path import getmtime
os.path.getmtime("filename")
os.path.getmtime(b"filename")
os.path.getmtime(Path("filename"))
getmtime("filename")
getmtime(b"filename")
getmtime(Path("filename"))

Some files were not shown because too many files have changed in this diff Show More