Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ecd263b4d | ||
|
|
6acf07c5c4 | ||
|
|
38b9fb8bbd | ||
|
|
e4f57434a2 | ||
|
|
6a64f2289b | ||
|
|
3bf1c66cda | ||
|
|
eaada0345c | ||
|
|
a39dd76d95 | ||
|
|
e257c5af32 | ||
|
|
887a47cad9 | ||
|
|
a2758513de | ||
|
|
1b9fed8397 | ||
|
|
55d6fd53cd | ||
|
|
d33618062e | ||
|
|
c7703e205d |
2
.github/workflows/docs.yaml
vendored
2
.github/workflows/docs.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
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
|
||||
uses: cloudflare/wrangler-action@3.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
2
.github/workflows/playground.yaml
vendored
2
.github/workflows/playground.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
working-directory: playground
|
||||
- name: "Deploy to Cloudflare Pages"
|
||||
if: ${{ env.CF_API_TOKEN_EXISTS == 'true' }}
|
||||
uses: cloudflare/wrangler-action@2.0.0
|
||||
uses: cloudflare/wrangler-action@3.0.0
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_API_TOKEN }}
|
||||
accountId: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Breaking Changes
|
||||
|
||||
## 0.0.283
|
||||
## 0.0.283 / 0.284
|
||||
|
||||
### The target Python version now defaults to 3.8 instead of 3.10 ([#6397](https://github.com/astral-sh/ruff/pull/6397))
|
||||
|
||||
@@ -8,6 +8,8 @@ Previously, when a target Python version was not specified, Ruff would use a def
|
||||
|
||||
(We still support Python 3.7 but since [it has reached EOL](https://devguide.python.org/versions/#unsupported-versions) we've decided not to make it the default here.)
|
||||
|
||||
Note this change was announced in 0.0.283 but not active until 0.0.284.
|
||||
|
||||
## 0.0.277
|
||||
|
||||
### `.ipynb_checkpoints`, `.pyenv`, `.pytest_cache`, and `.vscode` are now excluded by default ([#5513](https://github.com/astral-sh/ruff/pull/5513))
|
||||
|
||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -800,7 +800,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2042,7 +2042,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -2141,7 +2141,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
|
||||
@@ -140,7 +140,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com) hook:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
description = """
|
||||
Convert Flake8 configuration files to Ruff configuration files.
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -14,3 +14,19 @@ with open("/dev/shm/unit/test", "w") as f:
|
||||
# not ok by config
|
||||
with open("/foo/bar", "w") as f:
|
||||
f.write("def")
|
||||
|
||||
# Using `tempfile` module should be ok
|
||||
import tempfile
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
with tempfile.NamedTemporaryFile(dir="/tmp") as f:
|
||||
f.write(b"def")
|
||||
|
||||
with tempfile.NamedTemporaryFile(dir="/var/tmp") as f:
|
||||
f.write(b"def")
|
||||
|
||||
with tempfile.TemporaryDirectory(dir="/dev/shm") as d:
|
||||
pass
|
||||
|
||||
with TemporaryDirectory(dir="/tmp") as d:
|
||||
pass
|
||||
|
||||
@@ -92,3 +92,10 @@ match *0, 1, *2:
|
||||
case 0,:
|
||||
import x
|
||||
import y
|
||||
|
||||
|
||||
# Test: access a sub-importation via an alias.
|
||||
import foo.bar as bop
|
||||
import foo.bar.baz
|
||||
|
||||
print(bop.baz.read_csv("test.csv"))
|
||||
|
||||
@@ -70,3 +70,13 @@ import requests_mock as rm
|
||||
|
||||
def requests_mock(requests_mock: rm.Mocker):
|
||||
print(rm.ANY)
|
||||
|
||||
|
||||
import sklearn.base
|
||||
import mlflow.sklearn
|
||||
|
||||
|
||||
def f():
|
||||
import sklearn
|
||||
|
||||
mlflow
|
||||
|
||||
@@ -1229,13 +1229,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
||||
}
|
||||
}
|
||||
if checker.enabled(Rule::HardcodedTempFile) {
|
||||
if let Some(diagnostic) = flake8_bandit::rules::hardcoded_tmp_directory(
|
||||
expr,
|
||||
value,
|
||||
&checker.settings.flake8_bandit.hardcoded_tmp_directory,
|
||||
) {
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
flake8_bandit::rules::hardcoded_tmp_directory(checker, expr, value);
|
||||
}
|
||||
if checker.enabled(Rule::UnicodeKindPrefix) {
|
||||
pyupgrade::rules::unicode_kind_prefix(checker, expr, kind.as_deref());
|
||||
|
||||
@@ -571,11 +571,11 @@ print("after empty cells")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_line_magics() -> Result<()> {
|
||||
let path = "line_magics.ipynb".to_string();
|
||||
fn test_ipy_escape_command() -> Result<()> {
|
||||
let path = "ipy_escape_command.ipynb".to_string();
|
||||
let (diagnostics, source_kind, _) = test_notebook_path(
|
||||
&path,
|
||||
Path::new("line_magics_expected.ipynb"),
|
||||
Path::new("ipy_escape_command_expected.ipynb"),
|
||||
&settings::Settings::for_rule(Rule::UnusedImport),
|
||||
)?;
|
||||
assert_messages!(diagnostics, path, source_kind);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/jupyter/notebook.rs
|
||||
---
|
||||
line_magics.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
ipy_escape_command.ipynb:cell 1:5:8: F401 [*] `os` imported but unused
|
||||
|
|
||||
3 | %matplotlib inline
|
||||
4 |
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::num::NonZeroU8;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use ruff_macros::CacheKey;
|
||||
@@ -83,7 +84,7 @@ impl LineWidth {
|
||||
}
|
||||
|
||||
fn update(mut self, chars: impl Iterator<Item = char>) -> Self {
|
||||
let tab_size: usize = self.tab_size.into();
|
||||
let tab_size: usize = self.tab_size.as_usize();
|
||||
for c in chars {
|
||||
match c {
|
||||
'\t' => {
|
||||
@@ -144,22 +145,22 @@ impl PartialOrd<LineLength> for LineWidth {
|
||||
/// The size of a tab.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, CacheKey)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct TabSize(pub u8);
|
||||
pub struct TabSize(pub NonZeroU8);
|
||||
|
||||
impl TabSize {
|
||||
fn as_usize(self) -> usize {
|
||||
self.0.get() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TabSize {
|
||||
fn default() -> Self {
|
||||
Self(4)
|
||||
Self(NonZeroU8::new(4).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for TabSize {
|
||||
fn from(tab_size: u8) -> Self {
|
||||
impl From<NonZeroU8> for TabSize {
|
||||
fn from(tab_size: NonZeroU8) -> Self {
|
||||
Self(tab_size)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TabSize> for usize {
|
||||
fn from(tab_size: TabSize) -> Self {
|
||||
tab_size.0 as usize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,12 +293,10 @@ impl Display for MessageCodeFrame<'_> {
|
||||
}
|
||||
|
||||
fn replace_whitespace(source: &str, annotation_range: TextRange) -> SourceCode {
|
||||
static TAB_SIZE: TabSize = TabSize(4); // TODO(jonathan): use `tab-size`
|
||||
|
||||
let mut result = String::new();
|
||||
let mut last_end = 0;
|
||||
let mut range = annotation_range;
|
||||
let mut line_width = LineWidth::new(TAB_SIZE);
|
||||
let mut line_width = LineWidth::new(TabSize::default());
|
||||
|
||||
for (index, c) in source.char_indices() {
|
||||
let old_width = line_width.get();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_python_ast::{self as ast, Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for the use of hardcoded temporary file or directory paths.
|
||||
///
|
||||
@@ -49,19 +51,33 @@ impl Violation for HardcodedTempFile {
|
||||
}
|
||||
|
||||
/// S108
|
||||
pub(crate) fn hardcoded_tmp_directory(
|
||||
expr: &Expr,
|
||||
value: &str,
|
||||
prefixes: &[String],
|
||||
) -> Option<Diagnostic> {
|
||||
if prefixes.iter().any(|prefix| value.starts_with(prefix)) {
|
||||
Some(Diagnostic::new(
|
||||
HardcodedTempFile {
|
||||
string: value.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
pub(crate) fn hardcoded_tmp_directory(checker: &mut Checker, expr: &Expr, value: &str) {
|
||||
if !checker
|
||||
.settings
|
||||
.flake8_bandit
|
||||
.hardcoded_tmp_directory
|
||||
.iter()
|
||||
.any(|prefix| value.starts_with(prefix))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(Expr::Call(ast::ExprCall { func, .. })) =
|
||||
checker.semantic().current_expression_parent()
|
||||
{
|
||||
if checker
|
||||
.semantic()
|
||||
.resolve_call_path(func)
|
||||
.is_some_and(|call_path| matches!(call_path.as_slice(), ["tempfile", ..]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
HardcodedTempFile {
|
||||
string: value.to_string(),
|
||||
},
|
||||
expr.range(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ mod tests {
|
||||
#[test_case(Rule::UselessComparison, Path::new("B015.py"))]
|
||||
#[test_case(Rule::UselessContextlibSuppress, Path::new("B022.py"))]
|
||||
#[test_case(Rule::UselessExpression, Path::new("B018.py"))]
|
||||
#[test_case(Rule::ZipWithoutExplicitStrict, Path::new("B905.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
@@ -60,6 +59,17 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zip_without_explicit_strict() -> Result<()> {
|
||||
let snapshot = "B905.py";
|
||||
let diagnostics = test_path(
|
||||
Path::new("flake8_bugbear").join(snapshot).as_path(),
|
||||
&Settings::for_rule(Rule::ZipWithoutExplicitStrict),
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_immutable_calls() -> Result<()> {
|
||||
let snapshot = "extend_immutable_calls".to_string();
|
||||
@@ -72,7 +82,7 @@ mod tests {
|
||||
"fastapi.Query".to_string(),
|
||||
],
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::FunctionCallInDefaultArgument])
|
||||
..Settings::for_rule(Rule::FunctionCallInDefaultArgument)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
|
||||
@@ -1,4 +1,25 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI050.py:13:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
13 | def foo_no_return(arg: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
14 | ...
|
||||
|
|
||||
|
||||
PYI050.py:23:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
23 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
24 | ...
|
||||
|
|
||||
|
||||
PYI050.py:27:47: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
27 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
|
||||
| ^^^^^^^^ PYI050
|
||||
28 | ...
|
||||
|
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:6:24: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
4 | def foo(arg): ...
|
||||
5 | def foo_int(arg: int): ...
|
||||
@@ -11,7 +11,7 @@ PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for arg
|
||||
8 | arg: typing_extensions.NoReturn,
|
||||
|
|
||||
|
||||
PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:10:44: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
8 | arg: typing_extensions.NoReturn,
|
||||
9 | ): ... # Error: PYI050
|
||||
@@ -21,7 +21,7 @@ PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for ar
|
||||
12 | def foo_never(arg: Never): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:11:47: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
PYI050.pyi:11:47: PYI050 Prefer `typing.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
9 | ): ... # Error: PYI050
|
||||
10 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
|
||||
@@ -14,9 +14,8 @@ if_elif_else.py:6:1: I001 [*] Import block is un-sorted or un-formatted
|
||||
3 3 | elif "setuptools" in sys.modules:
|
||||
4 4 | from setuptools.command.sdist import sdist as _sdist
|
||||
5 5 | else:
|
||||
6 |- from setuptools.command.sdist import sdist as _sdist
|
||||
7 6 | from distutils.command.sdist import sdist as _sdist
|
||||
7 |+
|
||||
8 |+ from setuptools.command.sdist import sdist as _sdist
|
||||
6 |+ from distutils.command.sdist import sdist as _sdist
|
||||
6 7 | from setuptools.command.sdist import sdist as _sdist
|
||||
7 |- from distutils.command.sdist import sdist as _sdist
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ mod tests {
|
||||
|
||||
use crate::assert_messages;
|
||||
use crate::registry::Rule;
|
||||
use crate::settings::types::PythonVersion;
|
||||
use crate::settings::Settings;
|
||||
use crate::test::test_path;
|
||||
|
||||
@@ -22,7 +23,7 @@ mod tests {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("perflint").join(path).as_path(),
|
||||
&Settings::for_rule(rule_code),
|
||||
&Settings::for_rule(rule_code).with_target_version(PythonVersion::Py310),
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
|
||||
@@ -6,6 +6,7 @@ pub(crate) mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU8;
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
@@ -204,7 +205,7 @@ mod tests {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pycodestyle/E501_2.py"),
|
||||
&settings::Settings {
|
||||
tab_size: tab_size.into(),
|
||||
tab_size: NonZeroU8::new(tab_size).unwrap().into(),
|
||||
..settings::Settings::for_rule(Rule::LineTooLong)
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -151,6 +151,8 @@ F401_0.py:93:16: F401 [*] `x` imported but unused
|
||||
92 92 | case 0,:
|
||||
93 |- import x
|
||||
94 93 | import y
|
||||
95 94 |
|
||||
96 95 |
|
||||
|
||||
F401_0.py:94:16: F401 [*] `y` imported but unused
|
||||
|
|
||||
@@ -166,5 +168,27 @@ F401_0.py:94:16: F401 [*] `y` imported but unused
|
||||
92 92 | case 0,:
|
||||
93 93 | import x
|
||||
94 |- import y
|
||||
95 94 |
|
||||
96 95 |
|
||||
97 96 | # Test: access a sub-importation via an alias.
|
||||
|
||||
F401_0.py:99:8: F401 [*] `foo.bar.baz` imported but unused
|
||||
|
|
||||
97 | # Test: access a sub-importation via an alias.
|
||||
98 | import foo.bar as bop
|
||||
99 | import foo.bar.baz
|
||||
| ^^^^^^^^^^^ F401
|
||||
100 |
|
||||
101 | print(bop.baz.read_csv("test.csv"))
|
||||
|
|
||||
= help: Remove unused import: `foo.bar.baz`
|
||||
|
||||
ℹ Fix
|
||||
96 96 |
|
||||
97 97 | # Test: access a sub-importation via an alias.
|
||||
98 98 | import foo.bar as bop
|
||||
99 |-import foo.bar.baz
|
||||
100 99 |
|
||||
101 100 | print(bop.baz.read_csv("test.csv"))
|
||||
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ mod tests {
|
||||
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint").join(path).as_path(),
|
||||
&Settings::for_rules(vec![rule_code]),
|
||||
&Settings::for_rule(rule_code),
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
@@ -140,10 +140,8 @@ mod tests {
|
||||
fn repeated_isinstance_calls() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/repeated_isinstance_calls.py"),
|
||||
&Settings {
|
||||
target_version: PythonVersion::Py39,
|
||||
..Settings::for_rules(vec![Rule::RepeatedIsinstanceCalls])
|
||||
},
|
||||
&Settings::for_rule(Rule::RepeatedIsinstanceCalls)
|
||||
.with_target_version(PythonVersion::Py39),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
@@ -153,10 +151,7 @@ mod tests {
|
||||
fn continue_in_finally() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pylint/continue_in_finally.py"),
|
||||
&Settings {
|
||||
target_version: PythonVersion::Py37,
|
||||
..Settings::for_rules(vec![Rule::ContinueInFinally])
|
||||
},
|
||||
&Settings::for_rule(Rule::ContinueInFinally).with_target_version(PythonVersion::Py37),
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
@@ -171,7 +166,7 @@ mod tests {
|
||||
allow_magic_value_types: vec![pylint::settings::ConstantType::Int],
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::MagicValueComparison])
|
||||
..Settings::for_rule(Rule::MagicValueComparison)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -187,7 +182,7 @@ mod tests {
|
||||
max_args: 4,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::TooManyArguments])
|
||||
..Settings::for_rule(Rule::TooManyArguments)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -200,7 +195,7 @@ mod tests {
|
||||
Path::new("pylint/too_many_arguments_params.py"),
|
||||
&Settings {
|
||||
dummy_variable_rgx: Regex::new(r"skip_.*").unwrap(),
|
||||
..Settings::for_rules(vec![Rule::TooManyArguments])
|
||||
..Settings::for_rule(Rule::TooManyArguments)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -216,7 +211,7 @@ mod tests {
|
||||
max_branches: 1,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::TooManyBranches])
|
||||
..Settings::for_rule(Rule::TooManyBranches)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -232,7 +227,7 @@ mod tests {
|
||||
max_statements: 1,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::TooManyStatements])
|
||||
..Settings::for_rule(Rule::TooManyStatements)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
@@ -248,7 +243,7 @@ mod tests {
|
||||
max_returns: 1,
|
||||
..pylint::settings::Settings::default()
|
||||
},
|
||||
..Settings::for_rules(vec![Rule::TooManyReturnStatements])
|
||||
..Settings::for_rule(Rule::TooManyReturnStatements)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
|
||||
@@ -19,8 +19,7 @@ use crate::checkers::ast::Checker;
|
||||
/// Checks for unsupported format types in format strings.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// The format string is not checked at compile time, so it is easy to
|
||||
/// introduce bugs by mistyping the format string.
|
||||
/// An invalid format string character will result in an error at runtime.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
@@ -41,6 +40,7 @@ impl Violation for BadStringFormatCharacter {
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE1300
|
||||
/// Ex) `"{:z}".format("1")`
|
||||
pub(crate) fn call(checker: &mut Checker, string: &str, range: TextRange) {
|
||||
if let Ok(format_string) = FormatString::from_str(string) {
|
||||
@@ -64,6 +64,7 @@ pub(crate) fn call(checker: &mut Checker, string: &str, range: TextRange) {
|
||||
}
|
||||
}
|
||||
|
||||
/// PLE1300
|
||||
/// Ex) `"%z" % "1"`
|
||||
pub(crate) fn percent(checker: &mut Checker, expr: &Expr) {
|
||||
// Grab each string segment (in case there's an implicit concatenation).
|
||||
|
||||
@@ -77,6 +77,7 @@ mod tests {
|
||||
#[test_case(Rule::UselessObjectInheritance, Path::new("UP004.py"))]
|
||||
#[test_case(Rule::YieldInForLoop, Path::new("UP028_0.py"))]
|
||||
#[test_case(Rule::YieldInForLoop, Path::new("UP028_1.py"))]
|
||||
#[test_case(Rule::NonPEP695TypeAlias, Path::new("UP040.py"))]
|
||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||
let snapshot = path.to_string_lossy().to_string();
|
||||
let diagnostics = test_path(
|
||||
@@ -100,19 +101,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_pep695_type_alias_py312() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
Path::new("pyupgrade/UP040.py"),
|
||||
&settings::Settings {
|
||||
target_version: PythonVersion::Py312,
|
||||
..settings::Settings::for_rule(Rule::NonPEP695TypeAlias)
|
||||
},
|
||||
)?;
|
||||
assert_messages!(diagnostics);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_annotations_keep_runtime_typing_p37() -> Result<()> {
|
||||
let diagnostics = test_path(
|
||||
|
||||
@@ -130,16 +130,18 @@ impl<'a> Visitor<'a> for TypeVarReferenceVisitor<'a> {
|
||||
fn visit_expr(&mut self, expr: &'a Expr) {
|
||||
match expr {
|
||||
Expr::Name(name) if name.ctx.is_load() => {
|
||||
let Some(Stmt::Assign(StmtAssign { value, .. })) =
|
||||
self.semantic.lookup_symbol(name.id.as_str())
|
||||
.and_then(|binding_id| {
|
||||
self.semantic
|
||||
.binding(binding_id)
|
||||
.source
|
||||
.map(|node_id| self.semantic.statement(node_id))
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
let Some(Stmt::Assign(StmtAssign { value, .. })) = self
|
||||
.semantic
|
||||
.lookup_symbol(name.id.as_str())
|
||||
.and_then(|binding_id| {
|
||||
self.semantic
|
||||
.binding(binding_id)
|
||||
.source
|
||||
.map(|node_id| self.semantic.statement(node_id))
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
match value.as_ref() {
|
||||
Expr::Subscript(ExprSubscript {
|
||||
|
||||
@@ -974,4 +974,19 @@ UP035.py:76:1: UP035 [*] Import from `collections.abc` instead: `Generator`
|
||||
78 78 | # OK
|
||||
79 79 | from a import b
|
||||
|
||||
UP035.py:88:1: UP035 [*] Import from `typing` instead: `dataclass_transform`
|
||||
|
|
||||
87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
||||
88 | from typing_extensions import dataclass_transform
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP035
|
||||
|
|
||||
= help: Import from `typing`
|
||||
|
||||
ℹ Suggested fix
|
||||
85 85 | from typing_extensions import NamedTuple
|
||||
86 86 |
|
||||
87 87 | # Ok: `typing_extensions` supports `frozen_default` (backported from 3.12).
|
||||
88 |-from typing_extensions import dataclass_transform
|
||||
88 |+from typing import dataclass_transform
|
||||
|
||||
|
||||
|
||||
@@ -60,10 +60,8 @@ mod tests {
|
||||
);
|
||||
let diagnostics = test_path(
|
||||
Path::new("ruff").join(path).as_path(),
|
||||
&settings::Settings {
|
||||
target_version: PythonVersion::Py39,
|
||||
..settings::Settings::for_rule(Rule::ImplicitOptional)
|
||||
},
|
||||
&settings::Settings::for_rule(Rule::ImplicitOptional)
|
||||
.with_target_version(PythonVersion::Py39),
|
||||
)?;
|
||||
assert_messages!(snapshot, diagnostics);
|
||||
Ok(())
|
||||
|
||||
@@ -653,13 +653,13 @@ impl<'stmt> BasicBlocksBuilder<'stmt> {
|
||||
| Expr::Await(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::YieldFrom(_) => self.unconditional_next_block(after),
|
||||
Expr::LineMagic(_) => todo!(),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
// The tough branches are done, here is an easy one.
|
||||
Stmt::Return(_) => NextBlock::Terminate,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::LineMagic(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
};
|
||||
|
||||
// Include any statements in the block that don't divert the control flow.
|
||||
@@ -903,7 +903,7 @@ fn needs_next_block(stmts: &[Stmt]) -> bool {
|
||||
| Stmt::TryStar(_)
|
||||
| Stmt::Assert(_) => true,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::LineMagic(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -936,7 +936,7 @@ fn is_control_flow_stmt(stmt: &Stmt) -> bool {
|
||||
| Stmt::Break(_)
|
||||
| Stmt::Continue(_) => true,
|
||||
Stmt::TypeAlias(_) => todo!(),
|
||||
Stmt::LineMagic(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ pub const PREFIXES: &[RuleSelector] = &[
|
||||
RuleSelector::Linter(Linter::Pyflakes),
|
||||
];
|
||||
|
||||
pub const TARGET_VERSION: PythonVersion = PythonVersion::Py310;
|
||||
|
||||
pub const TASK_TAGS: &[&str] = &["TODO", "FIXME", "XXX"];
|
||||
|
||||
pub static DUMMY_VARIABLE_RGX: Lazy<Regex> =
|
||||
@@ -91,7 +89,7 @@ impl Default for Settings {
|
||||
respect_gitignore: true,
|
||||
src: vec![path_dedot::CWD.clone()],
|
||||
tab_size: TabSize::default(),
|
||||
target_version: TARGET_VERSION,
|
||||
target_version: PythonVersion::default(),
|
||||
task_tags: TASK_TAGS.iter().map(ToString::to_string).collect(),
|
||||
typing_modules: vec![],
|
||||
flake8_annotations: flake8_annotations::settings::Settings::default(),
|
||||
|
||||
@@ -183,7 +183,7 @@ impl Settings {
|
||||
.src
|
||||
.unwrap_or_else(|| vec![project_root.to_path_buf()]),
|
||||
project_root: project_root.to_path_buf(),
|
||||
target_version: config.target_version.unwrap_or(defaults::TARGET_VERSION),
|
||||
target_version: config.target_version.unwrap_or_default(),
|
||||
task_tags: config.task_tags.unwrap_or_else(|| {
|
||||
defaults::TASK_TAGS
|
||||
.iter()
|
||||
@@ -298,6 +298,7 @@ impl Settings {
|
||||
pub fn for_rule(rule_code: Rule) -> Self {
|
||||
Self {
|
||||
rules: RuleTable::from_iter([rule_code]),
|
||||
target_version: PythonVersion::latest(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
@@ -305,9 +306,17 @@ impl Settings {
|
||||
pub fn for_rules(rules: impl IntoIterator<Item = Rule>) -> Self {
|
||||
Self {
|
||||
rules: RuleTable::from_iter(rules),
|
||||
target_version: PythonVersion::latest(),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the [`Settings`] after updating the target [`PythonVersion`].
|
||||
#[must_use]
|
||||
pub fn with_target_version(mut self, target_version: PythonVersion) -> Self {
|
||||
self.target_version = target_version;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Configuration> for RuleTable {
|
||||
|
||||
@@ -312,13 +312,13 @@ pub struct Options {
|
||||
"#
|
||||
)]
|
||||
/// The line length to use when enforcing long-lines violations (like
|
||||
/// `E501`).
|
||||
/// `E501`). Must be greater than `0`.
|
||||
pub line_length: Option<LineLength>,
|
||||
#[option(
|
||||
default = "4",
|
||||
value_type = "int",
|
||||
example = r#"
|
||||
tab_size = 8
|
||||
tab-size = 8
|
||||
"#
|
||||
)]
|
||||
/// The tabulation size to calculate line length.
|
||||
@@ -456,7 +456,7 @@ pub struct Options {
|
||||
/// contained an `__init__.py` file.
|
||||
pub namespace_packages: Option<Vec<String>>,
|
||||
#[option(
|
||||
default = r#""py310""#,
|
||||
default = r#""py38""#,
|
||||
value_type = r#""py37" | "py38" | "py39" | "py310" | "py311" | "py312""#,
|
||||
example = r#"
|
||||
# Always generate Python 3.7-compatible code.
|
||||
|
||||
@@ -19,13 +19,25 @@ use crate::registry::RuleSet;
|
||||
use crate::rule_selector::RuleSelector;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, CacheKey, EnumIter,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
CacheKey,
|
||||
EnumIter,
|
||||
)]
|
||||
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum PythonVersion {
|
||||
Py37,
|
||||
#[default]
|
||||
Py38,
|
||||
Py39,
|
||||
Py310,
|
||||
@@ -41,6 +53,11 @@ impl From<PythonVersion> for Pep440Version {
|
||||
}
|
||||
|
||||
impl PythonVersion {
|
||||
/// Return the latest supported Python version.
|
||||
pub const fn latest() -> Self {
|
||||
Self::Py312
|
||||
}
|
||||
|
||||
pub const fn as_tuple(&self) -> (u32, u32) {
|
||||
match self {
|
||||
Self::Py37 => (3, 7),
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::borrow::Cow;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::num::NonZeroU8;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -205,6 +206,13 @@ impl CacheKey for i8 {
|
||||
}
|
||||
}
|
||||
|
||||
impl CacheKey for NonZeroU8 {
|
||||
#[inline]
|
||||
fn cache_key(&self, state: &mut CacheKeyHasher) {
|
||||
state.write_u8(self.get());
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_cache_key_tuple {
|
||||
() => (
|
||||
impl CacheKey for () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_cli"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -672,8 +672,8 @@ pub struct ExprSlice<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ExprLineMagic<'a> {
|
||||
kind: ast::MagicKind,
|
||||
pub struct ExprIpyEscapeCommand<'a> {
|
||||
kind: ast::IpyEscapeKind,
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
@@ -706,7 +706,7 @@ pub enum ComparableExpr<'a> {
|
||||
List(ExprList<'a>),
|
||||
Tuple(ExprTuple<'a>),
|
||||
Slice(ExprSlice<'a>),
|
||||
LineMagic(ExprLineMagic<'a>),
|
||||
IpyEscapeCommand(ExprIpyEscapeCommand<'a>),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Box<ast::Expr>> for Box<ComparableExpr<'a>> {
|
||||
@@ -936,11 +936,11 @@ impl<'a> From<&'a ast::Expr> for ComparableExpr<'a> {
|
||||
upper: upper.as_ref().map(Into::into),
|
||||
step: step.as_ref().map(Into::into),
|
||||
}),
|
||||
ast::Expr::LineMagic(ast::ExprLineMagic {
|
||||
ast::Expr::IpyEscapeCommand(ast::ExprIpyEscapeCommand {
|
||||
kind,
|
||||
value,
|
||||
range: _,
|
||||
}) => Self::LineMagic(ExprLineMagic {
|
||||
}) => Self::IpyEscapeCommand(ExprIpyEscapeCommand {
|
||||
kind: *kind,
|
||||
value: value.as_str(),
|
||||
}),
|
||||
@@ -1165,8 +1165,8 @@ pub struct StmtExpr<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StmtLineMagic<'a> {
|
||||
kind: ast::MagicKind,
|
||||
pub struct StmtIpyEscapeCommand<'a> {
|
||||
kind: ast::IpyEscapeKind,
|
||||
value: &'a str,
|
||||
}
|
||||
|
||||
@@ -1193,7 +1193,7 @@ pub enum ComparableStmt<'a> {
|
||||
ImportFrom(StmtImportFrom<'a>),
|
||||
Global(StmtGlobal<'a>),
|
||||
Nonlocal(StmtNonlocal<'a>),
|
||||
LineMagic(StmtLineMagic<'a>),
|
||||
IpyEscapeCommand(StmtIpyEscapeCommand<'a>),
|
||||
Expr(StmtExpr<'a>),
|
||||
Pass,
|
||||
Break,
|
||||
@@ -1394,11 +1394,11 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
|
||||
names: names.iter().map(ast::Identifier::as_str).collect(),
|
||||
})
|
||||
}
|
||||
ast::Stmt::LineMagic(ast::StmtLineMagic {
|
||||
ast::Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand {
|
||||
kind,
|
||||
value,
|
||||
range: _,
|
||||
}) => Self::LineMagic(StmtLineMagic {
|
||||
}) => Self::IpyEscapeCommand(StmtIpyEscapeCommand {
|
||||
kind: *kind,
|
||||
value: value.as_str(),
|
||||
}),
|
||||
|
||||
@@ -108,7 +108,7 @@ where
|
||||
| Expr::Subscript(_)
|
||||
| Expr::Yield(_)
|
||||
| Expr::YieldFrom(_)
|
||||
| Expr::LineMagic(_)
|
||||
| Expr::IpyEscapeCommand(_)
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -247,7 +247,7 @@ where
|
||||
.is_some_and(|value| any_over_expr(value, func))
|
||||
}
|
||||
Expr::Name(_) | Expr::Constant(_) => false,
|
||||
Expr::LineMagic(_) => false,
|
||||
Expr::IpyEscapeCommand(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +534,7 @@ where
|
||||
Stmt::Nonlocal(_) => false,
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => any_over_expr(value, func),
|
||||
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) => false,
|
||||
Stmt::LineMagic(_) => false,
|
||||
Stmt::IpyEscapeCommand(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ pub enum AnyNode {
|
||||
StmtPass(ast::StmtPass),
|
||||
StmtBreak(ast::StmtBreak),
|
||||
StmtContinue(ast::StmtContinue),
|
||||
StmtLineMagic(ast::StmtLineMagic),
|
||||
StmtIpyEscapeCommand(ast::StmtIpyEscapeCommand),
|
||||
ExprBoolOp(ast::ExprBoolOp),
|
||||
ExprNamedExpr(ast::ExprNamedExpr),
|
||||
ExprBinOp(ast::ExprBinOp),
|
||||
@@ -76,7 +76,7 @@ pub enum AnyNode {
|
||||
ExprList(ast::ExprList),
|
||||
ExprTuple(ast::ExprTuple),
|
||||
ExprSlice(ast::ExprSlice),
|
||||
ExprLineMagic(ast::ExprLineMagic),
|
||||
ExprIpyEscapeCommand(ast::ExprIpyEscapeCommand),
|
||||
ExceptHandlerExceptHandler(ast::ExceptHandlerExceptHandler),
|
||||
PatternMatchValue(ast::PatternMatchValue),
|
||||
PatternMatchSingleton(ast::PatternMatchSingleton),
|
||||
@@ -131,7 +131,7 @@ impl AnyNode {
|
||||
AnyNode::StmtPass(node) => Some(Stmt::Pass(node)),
|
||||
AnyNode::StmtBreak(node) => Some(Stmt::Break(node)),
|
||||
AnyNode::StmtContinue(node) => Some(Stmt::Continue(node)),
|
||||
AnyNode::StmtLineMagic(node) => Some(Stmt::LineMagic(node)),
|
||||
AnyNode::StmtIpyEscapeCommand(node) => Some(Stmt::IpyEscapeCommand(node)),
|
||||
|
||||
AnyNode::ModModule(_)
|
||||
| AnyNode::ModExpression(_)
|
||||
@@ -162,7 +162,7 @@ impl AnyNode {
|
||||
| AnyNode::ExprList(_)
|
||||
| AnyNode::ExprTuple(_)
|
||||
| AnyNode::ExprSlice(_)
|
||||
| AnyNode::ExprLineMagic(_)
|
||||
| AnyNode::ExprIpyEscapeCommand(_)
|
||||
| AnyNode::ExceptHandlerExceptHandler(_)
|
||||
| AnyNode::PatternMatchValue(_)
|
||||
| AnyNode::PatternMatchSingleton(_)
|
||||
@@ -219,7 +219,7 @@ impl AnyNode {
|
||||
AnyNode::ExprList(node) => Some(Expr::List(node)),
|
||||
AnyNode::ExprTuple(node) => Some(Expr::Tuple(node)),
|
||||
AnyNode::ExprSlice(node) => Some(Expr::Slice(node)),
|
||||
AnyNode::ExprLineMagic(node) => Some(Expr::LineMagic(node)),
|
||||
AnyNode::ExprIpyEscapeCommand(node) => Some(Expr::IpyEscapeCommand(node)),
|
||||
|
||||
AnyNode::ModModule(_)
|
||||
| AnyNode::ModExpression(_)
|
||||
@@ -248,7 +248,7 @@ impl AnyNode {
|
||||
| AnyNode::StmtPass(_)
|
||||
| AnyNode::StmtBreak(_)
|
||||
| AnyNode::StmtContinue(_)
|
||||
| AnyNode::StmtLineMagic(_)
|
||||
| AnyNode::StmtIpyEscapeCommand(_)
|
||||
| AnyNode::ExceptHandlerExceptHandler(_)
|
||||
| AnyNode::PatternMatchValue(_)
|
||||
| AnyNode::PatternMatchSingleton(_)
|
||||
@@ -306,7 +306,7 @@ impl AnyNode {
|
||||
| AnyNode::StmtPass(_)
|
||||
| AnyNode::StmtBreak(_)
|
||||
| AnyNode::StmtContinue(_)
|
||||
| AnyNode::StmtLineMagic(_)
|
||||
| AnyNode::StmtIpyEscapeCommand(_)
|
||||
| AnyNode::ExprBoolOp(_)
|
||||
| AnyNode::ExprNamedExpr(_)
|
||||
| AnyNode::ExprBinOp(_)
|
||||
@@ -334,7 +334,7 @@ impl AnyNode {
|
||||
| AnyNode::ExprList(_)
|
||||
| AnyNode::ExprTuple(_)
|
||||
| AnyNode::ExprSlice(_)
|
||||
| AnyNode::ExprLineMagic(_)
|
||||
| AnyNode::ExprIpyEscapeCommand(_)
|
||||
| AnyNode::ExceptHandlerExceptHandler(_)
|
||||
| AnyNode::PatternMatchValue(_)
|
||||
| AnyNode::PatternMatchSingleton(_)
|
||||
@@ -400,7 +400,7 @@ impl AnyNode {
|
||||
| AnyNode::StmtPass(_)
|
||||
| AnyNode::StmtBreak(_)
|
||||
| AnyNode::StmtContinue(_)
|
||||
| AnyNode::StmtLineMagic(_)
|
||||
| AnyNode::StmtIpyEscapeCommand(_)
|
||||
| AnyNode::ExprBoolOp(_)
|
||||
| AnyNode::ExprNamedExpr(_)
|
||||
| AnyNode::ExprBinOp(_)
|
||||
@@ -428,7 +428,7 @@ impl AnyNode {
|
||||
| AnyNode::ExprList(_)
|
||||
| AnyNode::ExprTuple(_)
|
||||
| AnyNode::ExprSlice(_)
|
||||
| AnyNode::ExprLineMagic(_)
|
||||
| AnyNode::ExprIpyEscapeCommand(_)
|
||||
| AnyNode::ExceptHandlerExceptHandler(_)
|
||||
| AnyNode::Comprehension(_)
|
||||
| AnyNode::Arguments(_)
|
||||
@@ -479,7 +479,7 @@ impl AnyNode {
|
||||
| AnyNode::StmtPass(_)
|
||||
| AnyNode::StmtBreak(_)
|
||||
| AnyNode::StmtContinue(_)
|
||||
| AnyNode::StmtLineMagic(_)
|
||||
| AnyNode::StmtIpyEscapeCommand(_)
|
||||
| AnyNode::ExprBoolOp(_)
|
||||
| AnyNode::ExprNamedExpr(_)
|
||||
| AnyNode::ExprBinOp(_)
|
||||
@@ -507,7 +507,7 @@ impl AnyNode {
|
||||
| AnyNode::ExprList(_)
|
||||
| AnyNode::ExprTuple(_)
|
||||
| AnyNode::ExprSlice(_)
|
||||
| AnyNode::ExprLineMagic(_)
|
||||
| AnyNode::ExprIpyEscapeCommand(_)
|
||||
| AnyNode::PatternMatchValue(_)
|
||||
| AnyNode::PatternMatchSingleton(_)
|
||||
| AnyNode::PatternMatchSequence(_)
|
||||
@@ -583,7 +583,7 @@ impl AnyNode {
|
||||
Self::StmtPass(node) => AnyNodeRef::StmtPass(node),
|
||||
Self::StmtBreak(node) => AnyNodeRef::StmtBreak(node),
|
||||
Self::StmtContinue(node) => AnyNodeRef::StmtContinue(node),
|
||||
Self::StmtLineMagic(node) => AnyNodeRef::StmtLineMagic(node),
|
||||
Self::StmtIpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node),
|
||||
Self::ExprBoolOp(node) => AnyNodeRef::ExprBoolOp(node),
|
||||
Self::ExprNamedExpr(node) => AnyNodeRef::ExprNamedExpr(node),
|
||||
Self::ExprBinOp(node) => AnyNodeRef::ExprBinOp(node),
|
||||
@@ -611,7 +611,7 @@ impl AnyNode {
|
||||
Self::ExprList(node) => AnyNodeRef::ExprList(node),
|
||||
Self::ExprTuple(node) => AnyNodeRef::ExprTuple(node),
|
||||
Self::ExprSlice(node) => AnyNodeRef::ExprSlice(node),
|
||||
Self::ExprLineMagic(node) => AnyNodeRef::ExprLineMagic(node),
|
||||
Self::ExprIpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node),
|
||||
Self::ExceptHandlerExceptHandler(node) => AnyNodeRef::ExceptHandlerExceptHandler(node),
|
||||
Self::PatternMatchValue(node) => AnyNodeRef::PatternMatchValue(node),
|
||||
Self::PatternMatchSingleton(node) => AnyNodeRef::PatternMatchSingleton(node),
|
||||
@@ -1429,12 +1429,12 @@ impl AstNode for ast::StmtContinue {
|
||||
AnyNode::from(self)
|
||||
}
|
||||
}
|
||||
impl AstNode for ast::StmtLineMagic {
|
||||
impl AstNode for ast::StmtIpyEscapeCommand {
|
||||
fn cast(kind: AnyNode) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let AnyNode::StmtLineMagic(node) = kind {
|
||||
if let AnyNode::StmtIpyEscapeCommand(node) = kind {
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
@@ -1442,7 +1442,7 @@ impl AstNode for ast::StmtLineMagic {
|
||||
}
|
||||
|
||||
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
|
||||
if let AnyNodeRef::StmtLineMagic(node) = kind {
|
||||
if let AnyNodeRef::StmtIpyEscapeCommand(node) = kind {
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
@@ -2213,12 +2213,12 @@ impl AstNode for ast::ExprSlice {
|
||||
AnyNode::from(self)
|
||||
}
|
||||
}
|
||||
impl AstNode for ast::ExprLineMagic {
|
||||
impl AstNode for ast::ExprIpyEscapeCommand {
|
||||
fn cast(kind: AnyNode) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if let AnyNode::ExprLineMagic(node) = kind {
|
||||
if let AnyNode::ExprIpyEscapeCommand(node) = kind {
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
@@ -2226,7 +2226,7 @@ impl AstNode for ast::ExprLineMagic {
|
||||
}
|
||||
|
||||
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
|
||||
if let AnyNodeRef::ExprLineMagic(node) = kind {
|
||||
if let AnyNodeRef::ExprIpyEscapeCommand(node) = kind {
|
||||
Some(node)
|
||||
} else {
|
||||
None
|
||||
@@ -2915,7 +2915,7 @@ impl From<Stmt> for AnyNode {
|
||||
Stmt::Pass(node) => AnyNode::StmtPass(node),
|
||||
Stmt::Break(node) => AnyNode::StmtBreak(node),
|
||||
Stmt::Continue(node) => AnyNode::StmtContinue(node),
|
||||
Stmt::LineMagic(node) => AnyNode::StmtLineMagic(node),
|
||||
Stmt::IpyEscapeCommand(node) => AnyNode::StmtIpyEscapeCommand(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2950,7 +2950,7 @@ impl From<Expr> for AnyNode {
|
||||
Expr::List(node) => AnyNode::ExprList(node),
|
||||
Expr::Tuple(node) => AnyNode::ExprTuple(node),
|
||||
Expr::Slice(node) => AnyNode::ExprSlice(node),
|
||||
Expr::LineMagic(node) => AnyNode::ExprLineMagic(node),
|
||||
Expr::IpyEscapeCommand(node) => AnyNode::ExprIpyEscapeCommand(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3155,9 +3155,9 @@ impl From<ast::StmtContinue> for AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::StmtLineMagic> for AnyNode {
|
||||
fn from(node: ast::StmtLineMagic) -> Self {
|
||||
AnyNode::StmtLineMagic(node)
|
||||
impl From<ast::StmtIpyEscapeCommand> for AnyNode {
|
||||
fn from(node: ast::StmtIpyEscapeCommand) -> Self {
|
||||
AnyNode::StmtIpyEscapeCommand(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3323,9 +3323,9 @@ impl From<ast::ExprSlice> for AnyNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ast::ExprLineMagic> for AnyNode {
|
||||
fn from(node: ast::ExprLineMagic) -> Self {
|
||||
AnyNode::ExprLineMagic(node)
|
||||
impl From<ast::ExprIpyEscapeCommand> for AnyNode {
|
||||
fn from(node: ast::ExprIpyEscapeCommand) -> Self {
|
||||
AnyNode::ExprIpyEscapeCommand(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3486,7 +3486,7 @@ impl Ranged for AnyNode {
|
||||
AnyNode::StmtPass(node) => node.range(),
|
||||
AnyNode::StmtBreak(node) => node.range(),
|
||||
AnyNode::StmtContinue(node) => node.range(),
|
||||
AnyNode::StmtLineMagic(node) => node.range(),
|
||||
AnyNode::StmtIpyEscapeCommand(node) => node.range(),
|
||||
AnyNode::ExprBoolOp(node) => node.range(),
|
||||
AnyNode::ExprNamedExpr(node) => node.range(),
|
||||
AnyNode::ExprBinOp(node) => node.range(),
|
||||
@@ -3514,7 +3514,7 @@ impl Ranged for AnyNode {
|
||||
AnyNode::ExprList(node) => node.range(),
|
||||
AnyNode::ExprTuple(node) => node.range(),
|
||||
AnyNode::ExprSlice(node) => node.range(),
|
||||
AnyNode::ExprLineMagic(node) => node.range(),
|
||||
AnyNode::ExprIpyEscapeCommand(node) => node.range(),
|
||||
AnyNode::ExceptHandlerExceptHandler(node) => node.range(),
|
||||
AnyNode::PatternMatchValue(node) => node.range(),
|
||||
AnyNode::PatternMatchSingleton(node) => node.range(),
|
||||
@@ -3572,7 +3572,7 @@ pub enum AnyNodeRef<'a> {
|
||||
StmtPass(&'a ast::StmtPass),
|
||||
StmtBreak(&'a ast::StmtBreak),
|
||||
StmtContinue(&'a ast::StmtContinue),
|
||||
StmtLineMagic(&'a ast::StmtLineMagic),
|
||||
StmtIpyEscapeCommand(&'a ast::StmtIpyEscapeCommand),
|
||||
ExprBoolOp(&'a ast::ExprBoolOp),
|
||||
ExprNamedExpr(&'a ast::ExprNamedExpr),
|
||||
ExprBinOp(&'a ast::ExprBinOp),
|
||||
@@ -3600,7 +3600,7 @@ pub enum AnyNodeRef<'a> {
|
||||
ExprList(&'a ast::ExprList),
|
||||
ExprTuple(&'a ast::ExprTuple),
|
||||
ExprSlice(&'a ast::ExprSlice),
|
||||
ExprLineMagic(&'a ast::ExprLineMagic),
|
||||
ExprIpyEscapeCommand(&'a ast::ExprIpyEscapeCommand),
|
||||
ExceptHandlerExceptHandler(&'a ast::ExceptHandlerExceptHandler),
|
||||
PatternMatchValue(&'a ast::PatternMatchValue),
|
||||
PatternMatchSingleton(&'a ast::PatternMatchSingleton),
|
||||
@@ -3657,7 +3657,7 @@ impl AnyNodeRef<'_> {
|
||||
AnyNodeRef::StmtPass(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::StmtBreak(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::StmtContinue(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::StmtLineMagic(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::StmtIpyEscapeCommand(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprBoolOp(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprNamedExpr(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprBinOp(node) => NonNull::from(*node).cast(),
|
||||
@@ -3685,7 +3685,7 @@ impl AnyNodeRef<'_> {
|
||||
AnyNodeRef::ExprList(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprTuple(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprSlice(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprLineMagic(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExprIpyEscapeCommand(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::ExceptHandlerExceptHandler(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::PatternMatchValue(node) => NonNull::from(*node).cast(),
|
||||
AnyNodeRef::PatternMatchSingleton(node) => NonNull::from(*node).cast(),
|
||||
@@ -3748,7 +3748,7 @@ impl AnyNodeRef<'_> {
|
||||
AnyNodeRef::StmtPass(_) => NodeKind::StmtPass,
|
||||
AnyNodeRef::StmtBreak(_) => NodeKind::StmtBreak,
|
||||
AnyNodeRef::StmtContinue(_) => NodeKind::StmtContinue,
|
||||
AnyNodeRef::StmtLineMagic(_) => NodeKind::StmtLineMagic,
|
||||
AnyNodeRef::StmtIpyEscapeCommand(_) => NodeKind::StmtIpyEscapeCommand,
|
||||
AnyNodeRef::ExprBoolOp(_) => NodeKind::ExprBoolOp,
|
||||
AnyNodeRef::ExprNamedExpr(_) => NodeKind::ExprNamedExpr,
|
||||
AnyNodeRef::ExprBinOp(_) => NodeKind::ExprBinOp,
|
||||
@@ -3776,7 +3776,7 @@ impl AnyNodeRef<'_> {
|
||||
AnyNodeRef::ExprList(_) => NodeKind::ExprList,
|
||||
AnyNodeRef::ExprTuple(_) => NodeKind::ExprTuple,
|
||||
AnyNodeRef::ExprSlice(_) => NodeKind::ExprSlice,
|
||||
AnyNodeRef::ExprLineMagic(_) => NodeKind::ExprLineMagic,
|
||||
AnyNodeRef::ExprIpyEscapeCommand(_) => NodeKind::ExprIpyEscapeCommand,
|
||||
AnyNodeRef::ExceptHandlerExceptHandler(_) => NodeKind::ExceptHandlerExceptHandler,
|
||||
AnyNodeRef::PatternMatchValue(_) => NodeKind::PatternMatchValue,
|
||||
AnyNodeRef::PatternMatchSingleton(_) => NodeKind::PatternMatchSingleton,
|
||||
@@ -3831,7 +3831,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::StmtPass(_)
|
||||
| AnyNodeRef::StmtBreak(_)
|
||||
| AnyNodeRef::StmtContinue(_)
|
||||
| AnyNodeRef::StmtLineMagic(_) => true,
|
||||
| AnyNodeRef::StmtIpyEscapeCommand(_) => true,
|
||||
|
||||
AnyNodeRef::ModModule(_)
|
||||
| AnyNodeRef::ModExpression(_)
|
||||
@@ -3862,7 +3862,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::ExprList(_)
|
||||
| AnyNodeRef::ExprTuple(_)
|
||||
| AnyNodeRef::ExprSlice(_)
|
||||
| AnyNodeRef::ExprLineMagic(_)
|
||||
| AnyNodeRef::ExprIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExceptHandlerExceptHandler(_)
|
||||
| AnyNodeRef::PatternMatchValue(_)
|
||||
| AnyNodeRef::PatternMatchSingleton(_)
|
||||
@@ -3919,7 +3919,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::ExprList(_)
|
||||
| AnyNodeRef::ExprTuple(_)
|
||||
| AnyNodeRef::ExprSlice(_)
|
||||
| AnyNodeRef::ExprLineMagic(_) => true,
|
||||
| AnyNodeRef::ExprIpyEscapeCommand(_) => true,
|
||||
|
||||
AnyNodeRef::ModModule(_)
|
||||
| AnyNodeRef::ModExpression(_)
|
||||
@@ -3948,7 +3948,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::StmtPass(_)
|
||||
| AnyNodeRef::StmtBreak(_)
|
||||
| AnyNodeRef::StmtContinue(_)
|
||||
| AnyNodeRef::StmtLineMagic(_)
|
||||
| AnyNodeRef::StmtIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExceptHandlerExceptHandler(_)
|
||||
| AnyNodeRef::PatternMatchValue(_)
|
||||
| AnyNodeRef::PatternMatchSingleton(_)
|
||||
@@ -4005,7 +4005,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::StmtPass(_)
|
||||
| AnyNodeRef::StmtBreak(_)
|
||||
| AnyNodeRef::StmtContinue(_)
|
||||
| AnyNodeRef::StmtLineMagic(_)
|
||||
| AnyNodeRef::StmtIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExprBoolOp(_)
|
||||
| AnyNodeRef::ExprNamedExpr(_)
|
||||
| AnyNodeRef::ExprBinOp(_)
|
||||
@@ -4033,7 +4033,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::ExprList(_)
|
||||
| AnyNodeRef::ExprTuple(_)
|
||||
| AnyNodeRef::ExprSlice(_)
|
||||
| AnyNodeRef::ExprLineMagic(_)
|
||||
| AnyNodeRef::ExprIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExceptHandlerExceptHandler(_)
|
||||
| AnyNodeRef::PatternMatchValue(_)
|
||||
| AnyNodeRef::PatternMatchSingleton(_)
|
||||
@@ -4099,7 +4099,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::StmtPass(_)
|
||||
| AnyNodeRef::StmtBreak(_)
|
||||
| AnyNodeRef::StmtContinue(_)
|
||||
| AnyNodeRef::StmtLineMagic(_)
|
||||
| AnyNodeRef::StmtIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExprBoolOp(_)
|
||||
| AnyNodeRef::ExprNamedExpr(_)
|
||||
| AnyNodeRef::ExprBinOp(_)
|
||||
@@ -4127,7 +4127,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::ExprList(_)
|
||||
| AnyNodeRef::ExprTuple(_)
|
||||
| AnyNodeRef::ExprSlice(_)
|
||||
| AnyNodeRef::ExprLineMagic(_)
|
||||
| AnyNodeRef::ExprIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExceptHandlerExceptHandler(_)
|
||||
| AnyNodeRef::Comprehension(_)
|
||||
| AnyNodeRef::Arguments(_)
|
||||
@@ -4178,7 +4178,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::StmtPass(_)
|
||||
| AnyNodeRef::StmtBreak(_)
|
||||
| AnyNodeRef::StmtContinue(_)
|
||||
| AnyNodeRef::StmtLineMagic(_)
|
||||
| AnyNodeRef::StmtIpyEscapeCommand(_)
|
||||
| AnyNodeRef::ExprBoolOp(_)
|
||||
| AnyNodeRef::ExprNamedExpr(_)
|
||||
| AnyNodeRef::ExprBinOp(_)
|
||||
@@ -4206,7 +4206,7 @@ impl AnyNodeRef<'_> {
|
||||
| AnyNodeRef::ExprList(_)
|
||||
| AnyNodeRef::ExprTuple(_)
|
||||
| AnyNodeRef::ExprSlice(_)
|
||||
| AnyNodeRef::ExprLineMagic(_)
|
||||
| AnyNodeRef::ExprIpyEscapeCommand(_)
|
||||
| AnyNodeRef::PatternMatchValue(_)
|
||||
| AnyNodeRef::PatternMatchSingleton(_)
|
||||
| AnyNodeRef::PatternMatchSequence(_)
|
||||
@@ -4429,9 +4429,9 @@ impl<'a> From<&'a ast::StmtContinue> for AnyNodeRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::StmtLineMagic> for AnyNodeRef<'a> {
|
||||
fn from(node: &'a ast::StmtLineMagic) -> Self {
|
||||
AnyNodeRef::StmtLineMagic(node)
|
||||
impl<'a> From<&'a ast::StmtIpyEscapeCommand> for AnyNodeRef<'a> {
|
||||
fn from(node: &'a ast::StmtIpyEscapeCommand) -> Self {
|
||||
AnyNodeRef::StmtIpyEscapeCommand(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4597,9 +4597,9 @@ impl<'a> From<&'a ast::ExprSlice> for AnyNodeRef<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ast::ExprLineMagic> for AnyNodeRef<'a> {
|
||||
fn from(node: &'a ast::ExprLineMagic) -> Self {
|
||||
AnyNodeRef::ExprLineMagic(node)
|
||||
impl<'a> From<&'a ast::ExprIpyEscapeCommand> for AnyNodeRef<'a> {
|
||||
fn from(node: &'a ast::ExprIpyEscapeCommand) -> Self {
|
||||
AnyNodeRef::ExprIpyEscapeCommand(node)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4714,7 +4714,7 @@ impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
|
||||
Stmt::Pass(node) => AnyNodeRef::StmtPass(node),
|
||||
Stmt::Break(node) => AnyNodeRef::StmtBreak(node),
|
||||
Stmt::Continue(node) => AnyNodeRef::StmtContinue(node),
|
||||
Stmt::LineMagic(node) => AnyNodeRef::StmtLineMagic(node),
|
||||
Stmt::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4749,7 +4749,7 @@ impl<'a> From<&'a Expr> for AnyNodeRef<'a> {
|
||||
Expr::List(node) => AnyNodeRef::ExprList(node),
|
||||
Expr::Tuple(node) => AnyNodeRef::ExprTuple(node),
|
||||
Expr::Slice(node) => AnyNodeRef::ExprSlice(node),
|
||||
Expr::LineMagic(node) => AnyNodeRef::ExprLineMagic(node),
|
||||
Expr::IpyEscapeCommand(node) => AnyNodeRef::ExprIpyEscapeCommand(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4874,7 +4874,7 @@ impl Ranged for AnyNodeRef<'_> {
|
||||
AnyNodeRef::StmtPass(node) => node.range(),
|
||||
AnyNodeRef::StmtBreak(node) => node.range(),
|
||||
AnyNodeRef::StmtContinue(node) => node.range(),
|
||||
AnyNodeRef::StmtLineMagic(node) => node.range(),
|
||||
AnyNodeRef::StmtIpyEscapeCommand(node) => node.range(),
|
||||
AnyNodeRef::ExprBoolOp(node) => node.range(),
|
||||
AnyNodeRef::ExprNamedExpr(node) => node.range(),
|
||||
AnyNodeRef::ExprBinOp(node) => node.range(),
|
||||
@@ -4902,7 +4902,7 @@ impl Ranged for AnyNodeRef<'_> {
|
||||
AnyNodeRef::ExprList(node) => node.range(),
|
||||
AnyNodeRef::ExprTuple(node) => node.range(),
|
||||
AnyNodeRef::ExprSlice(node) => node.range(),
|
||||
AnyNodeRef::ExprLineMagic(node) => node.range(),
|
||||
AnyNodeRef::ExprIpyEscapeCommand(node) => node.range(),
|
||||
AnyNodeRef::ExceptHandlerExceptHandler(node) => node.range(),
|
||||
AnyNodeRef::PatternMatchValue(node) => node.range(),
|
||||
AnyNodeRef::PatternMatchSingleton(node) => node.range(),
|
||||
@@ -4958,7 +4958,7 @@ pub enum NodeKind {
|
||||
StmtImportFrom,
|
||||
StmtGlobal,
|
||||
StmtNonlocal,
|
||||
StmtLineMagic,
|
||||
StmtIpyEscapeCommand,
|
||||
StmtExpr,
|
||||
StmtPass,
|
||||
StmtBreak,
|
||||
@@ -4990,7 +4990,7 @@ pub enum NodeKind {
|
||||
ExprList,
|
||||
ExprTuple,
|
||||
ExprSlice,
|
||||
ExprLineMagic,
|
||||
ExprIpyEscapeCommand,
|
||||
ExceptHandlerExceptHandler,
|
||||
PatternMatchValue,
|
||||
PatternMatchSingleton,
|
||||
|
||||
@@ -95,20 +95,20 @@ pub enum Stmt {
|
||||
Continue(StmtContinue),
|
||||
|
||||
// Jupyter notebook specific
|
||||
#[is(name = "line_magic_stmt")]
|
||||
LineMagic(StmtLineMagic),
|
||||
#[is(name = "ipy_escape_command_stmt")]
|
||||
IpyEscapeCommand(StmtIpyEscapeCommand),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct StmtLineMagic {
|
||||
pub struct StmtIpyEscapeCommand {
|
||||
pub range: TextRange,
|
||||
pub kind: MagicKind,
|
||||
pub kind: IpyEscapeKind,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<StmtLineMagic> for Stmt {
|
||||
fn from(payload: StmtLineMagic) -> Self {
|
||||
Stmt::LineMagic(payload)
|
||||
impl From<StmtIpyEscapeCommand> for Stmt {
|
||||
fn from(payload: StmtIpyEscapeCommand) -> Self {
|
||||
Stmt::IpyEscapeCommand(payload)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,20 +570,20 @@ pub enum Expr {
|
||||
Slice(ExprSlice),
|
||||
|
||||
// Jupyter notebook specific
|
||||
#[is(name = "line_magic_expr")]
|
||||
LineMagic(ExprLineMagic),
|
||||
#[is(name = "ipy_escape_command_expr")]
|
||||
IpyEscapeCommand(ExprIpyEscapeCommand),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ExprLineMagic {
|
||||
pub struct ExprIpyEscapeCommand {
|
||||
pub range: TextRange,
|
||||
pub kind: MagicKind,
|
||||
pub kind: IpyEscapeKind,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl From<ExprLineMagic> for Expr {
|
||||
fn from(payload: ExprLineMagic) -> Self {
|
||||
Expr::LineMagic(payload)
|
||||
impl From<ExprIpyEscapeCommand> for Expr {
|
||||
fn from(payload: ExprIpyEscapeCommand) -> Self {
|
||||
Expr::IpyEscapeCommand(payload)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2253,103 +2253,103 @@ impl Parameters {
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of magic command as defined in [IPython Syntax] in the IPython codebase.
|
||||
/// The kind of escape command as defined in [IPython Syntax] in the IPython codebase.
|
||||
///
|
||||
/// [IPython Syntax]: https://github.com/ipython/ipython/blob/635815e8f1ded5b764d66cacc80bbe25e9e2587f/IPython/core/inputtransformer2.py#L335-L343
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Hash, Copy)]
|
||||
pub enum MagicKind {
|
||||
/// Send line to underlying system shell.
|
||||
pub enum IpyEscapeKind {
|
||||
/// Send line to underlying system shell (`!`).
|
||||
Shell,
|
||||
/// Send line to system shell and capture output.
|
||||
/// Send line to system shell and capture output (`!!`).
|
||||
ShCap,
|
||||
/// Show help on object.
|
||||
/// Show help on object (`?`).
|
||||
Help,
|
||||
/// Show help on object, with extra verbosity.
|
||||
/// Show help on object, with extra verbosity (`??`).
|
||||
Help2,
|
||||
/// Call magic function.
|
||||
/// Call magic function (`%`).
|
||||
Magic,
|
||||
/// Call cell magic function.
|
||||
/// Call cell magic function (`%%`).
|
||||
Magic2,
|
||||
/// Call first argument with rest of line as arguments after splitting on whitespace
|
||||
/// and quote each as string.
|
||||
/// and quote each as string (`,`).
|
||||
Quote,
|
||||
/// Call first argument with rest of line as an argument quoted as a single string.
|
||||
/// Call first argument with rest of line as an argument quoted as a single string (`;`).
|
||||
Quote2,
|
||||
/// Call first argument with rest of line as arguments.
|
||||
/// Call first argument with rest of line as arguments (`/`).
|
||||
Paren,
|
||||
}
|
||||
|
||||
impl TryFrom<char> for MagicKind {
|
||||
impl TryFrom<char> for IpyEscapeKind {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(ch: char) -> Result<Self, Self::Error> {
|
||||
match ch {
|
||||
'!' => Ok(MagicKind::Shell),
|
||||
'?' => Ok(MagicKind::Help),
|
||||
'%' => Ok(MagicKind::Magic),
|
||||
',' => Ok(MagicKind::Quote),
|
||||
';' => Ok(MagicKind::Quote2),
|
||||
'/' => Ok(MagicKind::Paren),
|
||||
'!' => Ok(IpyEscapeKind::Shell),
|
||||
'?' => Ok(IpyEscapeKind::Help),
|
||||
'%' => Ok(IpyEscapeKind::Magic),
|
||||
',' => Ok(IpyEscapeKind::Quote),
|
||||
';' => Ok(IpyEscapeKind::Quote2),
|
||||
'/' => Ok(IpyEscapeKind::Paren),
|
||||
_ => Err(format!("Unexpected magic escape: {ch}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<[char; 2]> for MagicKind {
|
||||
impl TryFrom<[char; 2]> for IpyEscapeKind {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(ch: [char; 2]) -> Result<Self, Self::Error> {
|
||||
match ch {
|
||||
['!', '!'] => Ok(MagicKind::ShCap),
|
||||
['?', '?'] => Ok(MagicKind::Help2),
|
||||
['%', '%'] => Ok(MagicKind::Magic2),
|
||||
['!', '!'] => Ok(IpyEscapeKind::ShCap),
|
||||
['?', '?'] => Ok(IpyEscapeKind::Help2),
|
||||
['%', '%'] => Ok(IpyEscapeKind::Magic2),
|
||||
[c1, c2] => Err(format!("Unexpected magic escape: {c1}{c2}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MagicKind {
|
||||
impl fmt::Display for IpyEscapeKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl MagicKind {
|
||||
/// Returns the length of the magic command prefix.
|
||||
impl IpyEscapeKind {
|
||||
/// Returns the length of the escape kind token.
|
||||
pub fn prefix_len(self) -> TextSize {
|
||||
let len = match self {
|
||||
MagicKind::Shell
|
||||
| MagicKind::Magic
|
||||
| MagicKind::Help
|
||||
| MagicKind::Quote
|
||||
| MagicKind::Quote2
|
||||
| MagicKind::Paren => 1,
|
||||
MagicKind::ShCap | MagicKind::Magic2 | MagicKind::Help2 => 2,
|
||||
IpyEscapeKind::Shell
|
||||
| IpyEscapeKind::Magic
|
||||
| IpyEscapeKind::Help
|
||||
| IpyEscapeKind::Quote
|
||||
| IpyEscapeKind::Quote2
|
||||
| IpyEscapeKind::Paren => 1,
|
||||
IpyEscapeKind::ShCap | IpyEscapeKind::Magic2 | IpyEscapeKind::Help2 => 2,
|
||||
};
|
||||
len.into()
|
||||
}
|
||||
|
||||
/// Returns `true` if the kind is a help command i.e., `?` or `??`.
|
||||
/// Returns `true` if the escape kind is help i.e., `?` or `??`.
|
||||
pub const fn is_help(self) -> bool {
|
||||
matches!(self, MagicKind::Help | MagicKind::Help2)
|
||||
matches!(self, IpyEscapeKind::Help | IpyEscapeKind::Help2)
|
||||
}
|
||||
|
||||
/// Returns `true` if the kind is a magic command i.e., `%` or `%%`.
|
||||
/// Returns `true` if the escape kind is magic i.e., `%` or `%%`.
|
||||
pub const fn is_magic(self) -> bool {
|
||||
matches!(self, MagicKind::Magic | MagicKind::Magic2)
|
||||
matches!(self, IpyEscapeKind::Magic | IpyEscapeKind::Magic2)
|
||||
}
|
||||
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
MagicKind::Shell => "!",
|
||||
MagicKind::ShCap => "!!",
|
||||
MagicKind::Help => "?",
|
||||
MagicKind::Help2 => "??",
|
||||
MagicKind::Magic => "%",
|
||||
MagicKind::Magic2 => "%%",
|
||||
MagicKind::Quote => ",",
|
||||
MagicKind::Quote2 => ";",
|
||||
MagicKind::Paren => "/",
|
||||
IpyEscapeKind::Shell => "!",
|
||||
IpyEscapeKind::ShCap => "!!",
|
||||
IpyEscapeKind::Help => "?",
|
||||
IpyEscapeKind::Help2 => "??",
|
||||
IpyEscapeKind::Magic => "%",
|
||||
IpyEscapeKind::Magic2 => "%%",
|
||||
IpyEscapeKind::Quote => ",",
|
||||
IpyEscapeKind::Quote2 => ";",
|
||||
IpyEscapeKind::Paren => "/",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2686,7 +2686,7 @@ impl Ranged for crate::nodes::StmtContinue {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
impl Ranged for StmtLineMagic {
|
||||
impl Ranged for StmtIpyEscapeCommand {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
@@ -2719,7 +2719,7 @@ impl Ranged for crate::Stmt {
|
||||
Self::Pass(node) => node.range(),
|
||||
Self::Break(node) => node.range(),
|
||||
Self::Continue(node) => node.range(),
|
||||
Stmt::LineMagic(node) => node.range(),
|
||||
Stmt::IpyEscapeCommand(node) => node.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2859,7 +2859,7 @@ impl Ranged for crate::nodes::ExprSlice {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
impl Ranged for ExprLineMagic {
|
||||
impl Ranged for ExprIpyEscapeCommand {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
@@ -2894,7 +2894,7 @@ impl Ranged for crate::Expr {
|
||||
Self::List(node) => node.range(),
|
||||
Self::Tuple(node) => node.range(),
|
||||
Self::Slice(node) => node.range(),
|
||||
Expr::LineMagic(node) => node.range(),
|
||||
Expr::IpyEscapeCommand(node) => node.range(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ pub fn relocate_expr(expr: &mut Expr, location: TextRange) {
|
||||
relocate_expr(expr, location);
|
||||
}
|
||||
}
|
||||
Expr::LineMagic(nodes::ExprLineMagic { range, .. }) => {
|
||||
Expr::IpyEscapeCommand(nodes::ExprIpyEscapeCommand { range, .. }) => {
|
||||
*range = location;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
|
||||
Stmt::Global(_) => {}
|
||||
Stmt::Nonlocal(_) => {}
|
||||
Stmt::Expr(ast::StmtExpr { value, range: _ }) => visitor.visit_expr(value),
|
||||
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::LineMagic(_) => {}
|
||||
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::IpyEscapeCommand(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +543,7 @@ pub fn walk_expr<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, expr: &'a Expr) {
|
||||
visitor.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
Expr::LineMagic(_) => {}
|
||||
Expr::IpyEscapeCommand(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -656,7 +656,7 @@ impl<'a> Generator<'a> {
|
||||
self.p("continue");
|
||||
});
|
||||
}
|
||||
Stmt::LineMagic(ast::StmtLineMagic { kind, value, .. }) => {
|
||||
Stmt::IpyEscapeCommand(ast::StmtIpyEscapeCommand { kind, value, .. }) => {
|
||||
statement!({
|
||||
self.p(&format!("{kind}{value}"));
|
||||
});
|
||||
@@ -1184,7 +1184,7 @@ impl<'a> Generator<'a> {
|
||||
self.unparse_expr(step, precedence::SLICE);
|
||||
}
|
||||
}
|
||||
Expr::LineMagic(ast::ExprLineMagic { kind, value, .. }) => {
|
||||
Expr::IpyEscapeCommand(ast::ExprIpyEscapeCommand { kind, value, .. }) => {
|
||||
self.p(&format!("{kind}{value}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,3 +371,67 @@ def f( # first
|
||||
# third
|
||||
):
|
||||
...
|
||||
|
||||
# Handle comments on empty tuple return types.
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
# comment
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
1
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
1, 2
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
(1, 2)
|
||||
): ...
|
||||
|
||||
def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197
|
||||
self, m: Match[str], data: str
|
||||
) -> Union[Tuple[None, None, None], Tuple[Element, int, int]]:
|
||||
...
|
||||
|
||||
def double(a: int # Hello
|
||||
) -> (int):
|
||||
return 2 * a
|
||||
|
||||
def double(a: int) -> ( # Hello
|
||||
int
|
||||
):
|
||||
return 2*a
|
||||
|
||||
def double(a: int) -> ( # Hello
|
||||
):
|
||||
return 2*a
|
||||
|
||||
# Breaking over parameters and return types. (Black adds a trailing comma when the
|
||||
# function arguments break here with a single argument; we do not.)
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, a) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> a:
|
||||
...
|
||||
|
||||
def f(a) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> a:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> a:
|
||||
...
|
||||
|
||||
@@ -3,7 +3,6 @@ use ruff_python_ast::Ranged;
|
||||
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::comments::{dangling_comments, trailing_comments, SourceComment};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::prelude::*;
|
||||
use crate::MagicTrailingComma;
|
||||
@@ -209,64 +208,3 @@ impl<'fmt, 'ast, 'buf> JoinCommaSeparatedBuilder<'fmt, 'ast, 'buf> {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Format comments inside empty parentheses, brackets or curly braces.
|
||||
///
|
||||
/// 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.
|
||||
pub(crate) fn empty_parenthesized_with_dangling_comments(
|
||||
opening: StaticText,
|
||||
comments: &[SourceComment],
|
||||
closing: StaticText,
|
||||
) -> EmptyWithDanglingComments {
|
||||
EmptyWithDanglingComments {
|
||||
opening,
|
||||
comments,
|
||||
closing,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct EmptyWithDanglingComments<'a> {
|
||||
opening: StaticText,
|
||||
comments: &'a [SourceComment],
|
||||
closing: StaticText,
|
||||
}
|
||||
|
||||
impl<'ast> Format<PyFormatContext<'ast>> for EmptyWithDanglingComments<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||
let end_of_line_split = self
|
||||
.comments
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
debug_assert!(self.comments[end_of_line_split..]
|
||||
.iter()
|
||||
.all(|comment| comment.line_position().is_own_line()));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
self.opening,
|
||||
// end-of-line comments
|
||||
trailing_comments(&self.comments[..end_of_line_split]),
|
||||
// Avoid unstable formatting with
|
||||
// ```python
|
||||
// x = () - (#
|
||||
// )
|
||||
// ```
|
||||
// Without this the comment would go after the empty tuple first, but still expand
|
||||
// the bin op. In the second formatting pass they are trailing bin op comments
|
||||
// so the bin op collapse. Suboptimally we keep parentheses around the bin op in
|
||||
// either case.
|
||||
(!self.comments[..end_of_line_split].is_empty()).then_some(hard_line_break()),
|
||||
// own line comments, which need to be indented
|
||||
soft_block_indent(&dangling_comments(&self.comments[end_of_line_split..])),
|
||||
self.closing
|
||||
])]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,10 @@ pub(super) fn place_comment<'a>(
|
||||
handle_module_level_own_line_comment_before_class_or_function_comment(comment, locator)
|
||||
}
|
||||
AnyNodeRef::WithItem(_) => handle_with_item_comment(comment, locator),
|
||||
AnyNodeRef::StmtFunctionDef(_) => handle_leading_function_with_decorators_comment(comment),
|
||||
AnyNodeRef::StmtFunctionDef(function_def) => {
|
||||
handle_leading_function_with_decorators_comment(comment)
|
||||
.or_else(|comment| handle_leading_returns_comment(comment, function_def))
|
||||
}
|
||||
AnyNodeRef::StmtClassDef(class_def) => {
|
||||
handle_leading_class_with_decorators_comment(comment, class_def)
|
||||
}
|
||||
@@ -783,6 +786,40 @@ fn handle_leading_function_with_decorators_comment(comment: DecoratedComment) ->
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles end-of-line comments between function parameters and the return type annotation,
|
||||
/// attaching them as dangling comments to the function instead of making them trailing
|
||||
/// parameter comments.
|
||||
///
|
||||
/// ```python
|
||||
/// def double(a: int) -> ( # Hello
|
||||
/// int
|
||||
/// ):
|
||||
/// return 2*a
|
||||
/// ```
|
||||
fn handle_leading_returns_comment<'a>(
|
||||
comment: DecoratedComment<'a>,
|
||||
function_def: &'a ast::StmtFunctionDef,
|
||||
) -> CommentPlacement<'a> {
|
||||
let parameters = function_def.parameters.as_ref();
|
||||
let Some(returns) = function_def.returns.as_deref() else {
|
||||
return CommentPlacement::Default(comment);
|
||||
};
|
||||
|
||||
let is_preceding_parameters = comment
|
||||
.preceding_node()
|
||||
.is_some_and(|node| node == parameters.into());
|
||||
|
||||
let is_following_returns = comment
|
||||
.following_node()
|
||||
.is_some_and(|node| node == returns.into());
|
||||
|
||||
if comment.line_position().is_end_of_line() && is_preceding_parameters && is_following_returns {
|
||||
CommentPlacement::dangling(comment.enclosing_node(), comment)
|
||||
} else {
|
||||
CommentPlacement::Default(comment)
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle comments between decorators and the decorated node.
|
||||
///
|
||||
/// For example, given:
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use std::iter::Peekable;
|
||||
|
||||
use ruff_python_ast::{
|
||||
Alias, Arguments, Comprehension, Decorator, ElifElseClause, ExceptHandler, Expr, Keyword,
|
||||
MatchCase, Mod, Parameter, ParameterWithDefault, Parameters, Pattern, Ranged, Stmt, TypeParam,
|
||||
TypeParams, WithItem,
|
||||
};
|
||||
use ruff_python_ast::{Mod, Ranged, Stmt};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use ruff_formatter::{SourceCode, SourceCodeSlice};
|
||||
@@ -48,14 +44,22 @@ impl<'a> CommentsVisitor<'a> {
|
||||
self.finish()
|
||||
}
|
||||
|
||||
fn start_node<N>(&mut self, node: N) -> TraversalSignal
|
||||
where
|
||||
N: Into<AnyNodeRef<'a>>,
|
||||
{
|
||||
self.start_node_impl(node.into())
|
||||
// Try to skip the subtree if
|
||||
// * there are no comments
|
||||
// * if the next comment comes after this node (meaning, this nodes subtree contains no comments)
|
||||
fn can_skip(&mut self, node_end: TextSize) -> bool {
|
||||
self.comment_ranges
|
||||
.peek()
|
||||
.map_or(true, |next_comment| next_comment.start() >= node_end)
|
||||
}
|
||||
|
||||
fn start_node_impl(&mut self, node: AnyNodeRef<'a>) -> TraversalSignal {
|
||||
fn finish(self) -> CommentsMap<'a> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
|
||||
fn enter_node(&mut self, node: AnyNodeRef<'ast>) -> TraversalSignal {
|
||||
let node_range = node.range();
|
||||
|
||||
let enclosing_node = self.parents.last().copied().unwrap_or(node);
|
||||
@@ -95,23 +99,7 @@ impl<'a> CommentsVisitor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to skip the subtree if
|
||||
// * there are no comments
|
||||
// * if the next comment comes after this node (meaning, this nodes subtree contains no comments)
|
||||
fn can_skip(&mut self, node_end: TextSize) -> bool {
|
||||
self.comment_ranges
|
||||
.peek()
|
||||
.map_or(true, |next_comment| next_comment.start() >= node_end)
|
||||
}
|
||||
|
||||
fn finish_node<N>(&mut self, node: N)
|
||||
where
|
||||
N: Into<AnyNodeRef<'a>>,
|
||||
{
|
||||
self.finish_node_impl(node.into());
|
||||
}
|
||||
|
||||
fn finish_node_impl(&mut self, node: AnyNodeRef<'a>) {
|
||||
fn leave_node(&mut self, node: AnyNodeRef<'ast>) {
|
||||
// We are leaving this node, pop it from the parent stack.
|
||||
self.parents.pop();
|
||||
|
||||
@@ -146,19 +134,6 @@ impl<'a> CommentsVisitor<'a> {
|
||||
self.preceding_node = Some(node);
|
||||
}
|
||||
|
||||
fn finish(self) -> CommentsMap<'a> {
|
||||
self.builder.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
|
||||
fn visit_mod(&mut self, module: &'ast Mod) {
|
||||
if self.start_node(module).is_traverse() {
|
||||
walk_module(self, module);
|
||||
}
|
||||
self.finish_node(module);
|
||||
}
|
||||
|
||||
fn visit_body(&mut self, body: &'ast [Stmt]) {
|
||||
match body {
|
||||
[] => {
|
||||
@@ -178,140 +153,6 @@ impl<'ast> PreorderVisitor<'ast> for CommentsVisitor<'ast> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, stmt: &'ast Stmt) {
|
||||
if self.start_node(stmt).is_traverse() {
|
||||
walk_stmt(self, stmt);
|
||||
}
|
||||
self.finish_node(stmt);
|
||||
}
|
||||
|
||||
fn visit_annotation(&mut self, expr: &'ast Expr) {
|
||||
if self.start_node(expr).is_traverse() {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
self.finish_node(expr);
|
||||
}
|
||||
|
||||
fn visit_decorator(&mut self, decorator: &'ast Decorator) {
|
||||
if self.start_node(decorator).is_traverse() {
|
||||
walk_decorator(self, decorator);
|
||||
}
|
||||
self.finish_node(decorator);
|
||||
}
|
||||
|
||||
fn visit_expr(&mut self, expr: &'ast Expr) {
|
||||
if self.start_node(expr).is_traverse() {
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
self.finish_node(expr);
|
||||
}
|
||||
|
||||
fn visit_comprehension(&mut self, comprehension: &'ast Comprehension) {
|
||||
if self.start_node(comprehension).is_traverse() {
|
||||
walk_comprehension(self, comprehension);
|
||||
}
|
||||
self.finish_node(comprehension);
|
||||
}
|
||||
|
||||
fn visit_except_handler(&mut self, except_handler: &'ast ExceptHandler) {
|
||||
if self.start_node(except_handler).is_traverse() {
|
||||
walk_except_handler(self, except_handler);
|
||||
}
|
||||
self.finish_node(except_handler);
|
||||
}
|
||||
|
||||
fn visit_format_spec(&mut self, format_spec: &'ast Expr) {
|
||||
if self.start_node(format_spec).is_traverse() {
|
||||
walk_expr(self, format_spec);
|
||||
}
|
||||
self.finish_node(format_spec);
|
||||
}
|
||||
|
||||
fn visit_arguments(&mut self, arguments: &'ast Arguments) {
|
||||
if self.start_node(arguments).is_traverse() {
|
||||
walk_arguments(self, arguments);
|
||||
}
|
||||
self.finish_node(arguments);
|
||||
}
|
||||
|
||||
fn visit_parameters(&mut self, parameters: &'ast Parameters) {
|
||||
if self.start_node(parameters).is_traverse() {
|
||||
walk_parameters(self, parameters);
|
||||
}
|
||||
self.finish_node(parameters);
|
||||
}
|
||||
|
||||
fn visit_parameter(&mut self, arg: &'ast Parameter) {
|
||||
if self.start_node(arg).is_traverse() {
|
||||
walk_parameter(self, arg);
|
||||
}
|
||||
self.finish_node(arg);
|
||||
}
|
||||
|
||||
fn visit_parameter_with_default(&mut self, parameter_with_default: &'ast ParameterWithDefault) {
|
||||
if self.start_node(parameter_with_default).is_traverse() {
|
||||
walk_parameter_with_default(self, parameter_with_default);
|
||||
}
|
||||
self.finish_node(parameter_with_default);
|
||||
}
|
||||
|
||||
fn visit_keyword(&mut self, keyword: &'ast Keyword) {
|
||||
if self.start_node(keyword).is_traverse() {
|
||||
walk_keyword(self, keyword);
|
||||
}
|
||||
self.finish_node(keyword);
|
||||
}
|
||||
|
||||
fn visit_alias(&mut self, alias: &'ast Alias) {
|
||||
if self.start_node(alias).is_traverse() {
|
||||
walk_alias(self, alias);
|
||||
}
|
||||
self.finish_node(alias);
|
||||
}
|
||||
|
||||
fn visit_with_item(&mut self, with_item: &'ast WithItem) {
|
||||
if self.start_node(with_item).is_traverse() {
|
||||
walk_with_item(self, with_item);
|
||||
}
|
||||
|
||||
self.finish_node(with_item);
|
||||
}
|
||||
|
||||
fn visit_match_case(&mut self, match_case: &'ast MatchCase) {
|
||||
if self.start_node(match_case).is_traverse() {
|
||||
walk_match_case(self, match_case);
|
||||
}
|
||||
self.finish_node(match_case);
|
||||
}
|
||||
|
||||
fn visit_pattern(&mut self, pattern: &'ast Pattern) {
|
||||
if self.start_node(pattern).is_traverse() {
|
||||
walk_pattern(self, pattern);
|
||||
}
|
||||
self.finish_node(pattern);
|
||||
}
|
||||
|
||||
fn visit_elif_else_clause(&mut self, elif_else_clause: &'ast ElifElseClause) {
|
||||
if self.start_node(elif_else_clause).is_traverse() {
|
||||
walk_elif_else_clause(self, elif_else_clause);
|
||||
}
|
||||
self.finish_node(elif_else_clause);
|
||||
}
|
||||
|
||||
fn visit_type_params(&mut self, type_params: &'ast TypeParams) {
|
||||
if self.start_node(type_params).is_traverse() {
|
||||
walk_type_params(self, type_params);
|
||||
}
|
||||
self.finish_node(type_params);
|
||||
}
|
||||
|
||||
fn visit_type_param(&mut self, type_param: &'ast TypeParam) {
|
||||
if self.start_node(type_param).is_traverse() {
|
||||
walk_type_param(self, type_param);
|
||||
}
|
||||
self.finish_node(type_param);
|
||||
}
|
||||
}
|
||||
|
||||
fn text_position(comment_range: TextRange, source_code: SourceCode) -> CommentLinePosition {
|
||||
@@ -663,18 +504,6 @@ impl<'a> CommentPlacement<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum TraversalSignal {
|
||||
Traverse,
|
||||
Skip,
|
||||
}
|
||||
|
||||
impl TraversalSignal {
|
||||
const fn is_traverse(self) -> bool {
|
||||
matches!(self, TraversalSignal::Traverse)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct CommentsBuilder<'a> {
|
||||
comments: CommentsMap<'a>,
|
||||
|
||||
@@ -4,9 +4,10 @@ use ruff_python_ast::Ranged;
|
||||
use ruff_python_ast::{Expr, ExprDict};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::builders::empty_parenthesized_with_dangling_comments;
|
||||
use crate::comments::leading_comments;
|
||||
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
|
||||
use crate::expression::parentheses::{
|
||||
empty_parenthesized, parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
@@ -69,8 +70,7 @@ impl FormatNodeRule<ExprDict> for FormatExprDict {
|
||||
let dangling = comments.dangling_comments(item);
|
||||
|
||||
if values.is_empty() {
|
||||
return empty_parenthesized_with_dangling_comments(text("{"), dangling, text("}"))
|
||||
.fmt(f);
|
||||
return empty_parenthesized("{", dangling, "}").fmt(f);
|
||||
}
|
||||
|
||||
let format_pairs = format_with(|f| {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::{verbatim_text, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::ExprIpyEscapeCommand;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprIpyEscapeCommand;
|
||||
|
||||
impl FormatNodeRule<ExprIpyEscapeCommand> for FormatExprIpyEscapeCommand {
|
||||
fn fmt_fields(&self, item: &ExprIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [verbatim_text(item)])
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use crate::{verbatim_text, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::ExprLineMagic;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatExprLineMagic;
|
||||
|
||||
impl FormatNodeRule<ExprLineMagic> for FormatExprLineMagic {
|
||||
fn fmt_fields(&self, item: &ExprLineMagic, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [verbatim_text(item)])
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
use ruff_formatter::prelude::{format_with, text};
|
||||
use ruff_formatter::prelude::format_with;
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::{ExprList, Ranged};
|
||||
|
||||
use crate::builders::empty_parenthesized_with_dangling_comments;
|
||||
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
|
||||
use crate::expression::parentheses::{
|
||||
empty_parenthesized, parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
@@ -22,8 +23,7 @@ impl FormatNodeRule<ExprList> for FormatExprList {
|
||||
let dangling = comments.dangling_comments(item);
|
||||
|
||||
if elts.is_empty() {
|
||||
return empty_parenthesized_with_dangling_comments(text("["), dangling, text("]"))
|
||||
.fmt(f);
|
||||
return empty_parenthesized("[", dangling, "]").fmt(f);
|
||||
}
|
||||
|
||||
let items = format_with(|f| {
|
||||
|
||||
@@ -4,8 +4,10 @@ use ruff_python_ast::ExprTuple;
|
||||
use ruff_python_ast::{Expr, Ranged};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::builders::{empty_parenthesized_with_dangling_comments, parenthesize_if_expands};
|
||||
use crate::expression::parentheses::{parenthesized, NeedsParentheses, OptionalParentheses};
|
||||
use crate::builders::parenthesize_if_expands;
|
||||
use crate::expression::parentheses::{
|
||||
empty_parenthesized, parenthesized, NeedsParentheses, OptionalParentheses,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Default)]
|
||||
@@ -117,8 +119,7 @@ impl FormatNodeRule<ExprTuple> for FormatExprTuple {
|
||||
// In all other cases comments get assigned to a list element
|
||||
match elts.as_slice() {
|
||||
[] => {
|
||||
return empty_parenthesized_with_dangling_comments(text("("), dangling, text(")"))
|
||||
.fmt(f);
|
||||
return empty_parenthesized("(", dangling, ")").fmt(f);
|
||||
}
|
||||
[single] => match self.parentheses {
|
||||
TupleParentheses::Preserve
|
||||
|
||||
@@ -31,8 +31,8 @@ pub(crate) mod expr_f_string;
|
||||
pub(crate) mod expr_formatted_value;
|
||||
pub(crate) mod expr_generator_exp;
|
||||
pub(crate) mod expr_if_exp;
|
||||
pub(crate) mod expr_ipy_escape_command;
|
||||
pub(crate) mod expr_lambda;
|
||||
pub(crate) mod expr_line_magic;
|
||||
pub(crate) mod expr_list;
|
||||
pub(crate) mod expr_list_comp;
|
||||
pub(crate) mod expr_name;
|
||||
@@ -102,7 +102,7 @@ impl FormatRule<Expr, PyFormatContext<'_>> for FormatExpr {
|
||||
Expr::List(expr) => expr.format().fmt(f),
|
||||
Expr::Tuple(expr) => expr.format().fmt(f),
|
||||
Expr::Slice(expr) => expr.format().fmt(f),
|
||||
Expr::LineMagic(_) => todo!(),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
});
|
||||
|
||||
let parenthesize = match parentheses {
|
||||
@@ -240,7 +240,7 @@ impl NeedsParentheses for Expr {
|
||||
Expr::List(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::Tuple(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::Slice(expr) => expr.needs_parentheses(parent, context),
|
||||
Expr::LineMagic(_) => todo!(),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@ impl<'input> CanOmitOptionalParenthesesVisitor<'input> {
|
||||
| Expr::Starred(_)
|
||||
| Expr::Name(_)
|
||||
| Expr::Slice(_) => {}
|
||||
Expr::LineMagic(_) => todo!(),
|
||||
Expr::IpyEscapeCommand(_) => todo!(),
|
||||
};
|
||||
|
||||
walk_expr(self, expr);
|
||||
|
||||
@@ -4,7 +4,9 @@ use ruff_python_ast::node::AnyNodeRef;
|
||||
use ruff_python_ast::Ranged;
|
||||
use ruff_python_trivia::{first_non_trivia_token, SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
||||
|
||||
use crate::comments::{dangling_open_parenthesis_comments, SourceComment};
|
||||
use crate::comments::{
|
||||
dangling_comments, dangling_open_parenthesis_comments, trailing_comments, SourceComment,
|
||||
};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::prelude::*;
|
||||
|
||||
@@ -307,6 +309,67 @@ impl<'ast> Format<PyFormatContext<'ast>> for FormatInParenthesesOnlyGroup<'_, 'a
|
||||
}
|
||||
}
|
||||
|
||||
/// Format comments inside empty parentheses, brackets or curly braces.
|
||||
///
|
||||
/// 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.
|
||||
pub(crate) fn empty_parenthesized<'content>(
|
||||
left: &'static str,
|
||||
comments: &'content [SourceComment],
|
||||
right: &'static str,
|
||||
) -> FormatEmptyParenthesized<'content> {
|
||||
FormatEmptyParenthesized {
|
||||
left,
|
||||
comments,
|
||||
right,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FormatEmptyParenthesized<'content> {
|
||||
left: &'static str,
|
||||
comments: &'content [SourceComment],
|
||||
right: &'static str,
|
||||
}
|
||||
|
||||
impl Format<PyFormatContext<'_>> for FormatEmptyParenthesized<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<PyFormatContext>) -> FormatResult<()> {
|
||||
let end_of_line_split = self
|
||||
.comments
|
||||
.partition_point(|comment| comment.line_position().is_end_of_line());
|
||||
debug_assert!(self.comments[end_of_line_split..]
|
||||
.iter()
|
||||
.all(|comment| comment.line_position().is_own_line()));
|
||||
write!(
|
||||
f,
|
||||
[group(&format_args![
|
||||
text(self.left),
|
||||
// end-of-line comments
|
||||
trailing_comments(&self.comments[..end_of_line_split]),
|
||||
// Avoid unstable formatting with
|
||||
// ```python
|
||||
// x = () - (#
|
||||
// )
|
||||
// ```
|
||||
// Without this the comment would go after the empty tuple first, but still expand
|
||||
// the bin op. In the second formatting pass they are trailing bin op comments
|
||||
// so the bin op collapse. Suboptimally we keep parentheses around the bin op in
|
||||
// either case.
|
||||
(!self.comments[..end_of_line_split].is_empty()).then_some(hard_line_break()),
|
||||
// own line comments, which need to be indented
|
||||
soft_block_indent(&dangling_comments(&self.comments[end_of_line_split..])),
|
||||
text(self.right)
|
||||
])]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use ruff_python_ast::node::AnyNodeRef;
|
||||
|
||||
@@ -930,38 +930,38 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::StmtContinue {
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatRule<ast::StmtLineMagic, PyFormatContext<'_>>
|
||||
for crate::statement::stmt_line_magic::FormatStmtLineMagic
|
||||
impl FormatRule<ast::StmtIpyEscapeCommand, PyFormatContext<'_>>
|
||||
for crate::statement::stmt_ipy_escape_command::FormatStmtIpyEscapeCommand
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, node: &ast::StmtLineMagic, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
FormatNodeRule::<ast::StmtLineMagic>::fmt(self, node, f)
|
||||
fn fmt(&self, node: &ast::StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
FormatNodeRule::<ast::StmtIpyEscapeCommand>::fmt(self, node, f)
|
||||
}
|
||||
}
|
||||
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::StmtLineMagic {
|
||||
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::StmtIpyEscapeCommand {
|
||||
type Format<'a> = FormatRefWithRule<
|
||||
'a,
|
||||
ast::StmtLineMagic,
|
||||
crate::statement::stmt_line_magic::FormatStmtLineMagic,
|
||||
ast::StmtIpyEscapeCommand,
|
||||
crate::statement::stmt_ipy_escape_command::FormatStmtIpyEscapeCommand,
|
||||
PyFormatContext<'ast>,
|
||||
>;
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatRefWithRule::new(
|
||||
self,
|
||||
crate::statement::stmt_line_magic::FormatStmtLineMagic::default(),
|
||||
crate::statement::stmt_ipy_escape_command::FormatStmtIpyEscapeCommand::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::StmtLineMagic {
|
||||
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::StmtIpyEscapeCommand {
|
||||
type Format = FormatOwnedWithRule<
|
||||
ast::StmtLineMagic,
|
||||
crate::statement::stmt_line_magic::FormatStmtLineMagic,
|
||||
ast::StmtIpyEscapeCommand,
|
||||
crate::statement::stmt_ipy_escape_command::FormatStmtIpyEscapeCommand,
|
||||
PyFormatContext<'ast>,
|
||||
>;
|
||||
fn into_format(self) -> Self::Format {
|
||||
FormatOwnedWithRule::new(
|
||||
self,
|
||||
crate::statement::stmt_line_magic::FormatStmtLineMagic::default(),
|
||||
crate::statement::stmt_ipy_escape_command::FormatStmtIpyEscapeCommand::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1930,38 +1930,38 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::ExprSlice {
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatRule<ast::ExprLineMagic, PyFormatContext<'_>>
|
||||
for crate::expression::expr_line_magic::FormatExprLineMagic
|
||||
impl FormatRule<ast::ExprIpyEscapeCommand, PyFormatContext<'_>>
|
||||
for crate::expression::expr_ipy_escape_command::FormatExprIpyEscapeCommand
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, node: &ast::ExprLineMagic, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
FormatNodeRule::<ast::ExprLineMagic>::fmt(self, node, f)
|
||||
fn fmt(&self, node: &ast::ExprIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
FormatNodeRule::<ast::ExprIpyEscapeCommand>::fmt(self, node, f)
|
||||
}
|
||||
}
|
||||
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::ExprLineMagic {
|
||||
impl<'ast> AsFormat<PyFormatContext<'ast>> for ast::ExprIpyEscapeCommand {
|
||||
type Format<'a> = FormatRefWithRule<
|
||||
'a,
|
||||
ast::ExprLineMagic,
|
||||
crate::expression::expr_line_magic::FormatExprLineMagic,
|
||||
ast::ExprIpyEscapeCommand,
|
||||
crate::expression::expr_ipy_escape_command::FormatExprIpyEscapeCommand,
|
||||
PyFormatContext<'ast>,
|
||||
>;
|
||||
fn format(&self) -> Self::Format<'_> {
|
||||
FormatRefWithRule::new(
|
||||
self,
|
||||
crate::expression::expr_line_magic::FormatExprLineMagic::default(),
|
||||
crate::expression::expr_ipy_escape_command::FormatExprIpyEscapeCommand::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::ExprLineMagic {
|
||||
impl<'ast> IntoFormat<PyFormatContext<'ast>> for ast::ExprIpyEscapeCommand {
|
||||
type Format = FormatOwnedWithRule<
|
||||
ast::ExprLineMagic,
|
||||
crate::expression::expr_line_magic::FormatExprLineMagic,
|
||||
ast::ExprIpyEscapeCommand,
|
||||
crate::expression::expr_ipy_escape_command::FormatExprIpyEscapeCommand,
|
||||
PyFormatContext<'ast>,
|
||||
>;
|
||||
fn into_format(self) -> Self::Format {
|
||||
FormatOwnedWithRule::new(
|
||||
self,
|
||||
crate::expression::expr_line_magic::FormatExprLineMagic::default(),
|
||||
crate::expression::expr_ipy_escape_command::FormatExprIpyEscapeCommand::default(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,8 @@ use ruff_python_ast::{Arguments, Expr, Ranged};
|
||||
use ruff_python_trivia::{SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::builders::empty_parenthesized_with_dangling_comments;
|
||||
use crate::expression::expr_generator_exp::GeneratorExpParentheses;
|
||||
use crate::expression::parentheses::{parenthesized, Parentheses};
|
||||
use crate::expression::parentheses::{empty_parenthesized, parenthesized, Parentheses};
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
@@ -24,14 +23,8 @@ impl FormatNodeRule<Arguments> for FormatArguments {
|
||||
// ```
|
||||
if item.args.is_empty() && item.keywords.is_empty() {
|
||||
let comments = f.context().comments().clone();
|
||||
return write!(
|
||||
f,
|
||||
[empty_parenthesized_with_dangling_comments(
|
||||
text("("),
|
||||
comments.dangling_comments(item),
|
||||
text(")"),
|
||||
)]
|
||||
);
|
||||
let dangling = comments.dangling_comments(item);
|
||||
return write!(f, [empty_parenthesized("(", dangling, ")")]);
|
||||
}
|
||||
|
||||
let all_arguments = format_with(|f: &mut PyFormatter| {
|
||||
|
||||
@@ -6,16 +6,16 @@ use ruff_python_ast::{Parameters, Ranged};
|
||||
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
|
||||
use crate::builders::empty_parenthesized_with_dangling_comments;
|
||||
use crate::comments::{
|
||||
leading_comments, leading_node_comments, trailing_comments, CommentLinePosition, SourceComment,
|
||||
dangling_open_parenthesis_comments, leading_comments, leading_node_comments, trailing_comments,
|
||||
CommentLinePosition, SourceComment,
|
||||
};
|
||||
use crate::context::{NodeLevel, WithNodeLevel};
|
||||
use crate::expression::parentheses::parenthesized;
|
||||
use crate::expression::parentheses::empty_parenthesized;
|
||||
use crate::prelude::*;
|
||||
use crate::FormatNodeRule;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Default)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub enum ParametersParentheses {
|
||||
/// By default, parameters will always preserve their surrounding parentheses.
|
||||
#[default]
|
||||
@@ -245,19 +245,19 @@ impl FormatNodeRule<Parameters> for FormatParameters {
|
||||
write!(f, [group(&format_inner)])
|
||||
} else if num_parameters == 0 {
|
||||
// No parameters, format any dangling comments between `()`
|
||||
write!(
|
||||
f,
|
||||
[empty_parenthesized_with_dangling_comments(
|
||||
text("("),
|
||||
dangling,
|
||||
text(")"),
|
||||
)]
|
||||
)
|
||||
write!(f, [empty_parenthesized("(", dangling, ")")])
|
||||
} else {
|
||||
// Intentionally avoid `parenthesized`, which groups the entire formatted contents.
|
||||
// We want parameters to be grouped alongside return types, one level up, so we
|
||||
// format them "inline" here.
|
||||
write!(
|
||||
f,
|
||||
[parenthesized("(", &group(&format_inner), ")")
|
||||
.with_dangling_comments(parenthesis_dangling)]
|
||||
[
|
||||
text("("),
|
||||
dangling_open_parenthesis_comments(parenthesis_dangling),
|
||||
soft_block_indent(&group(&format_inner)),
|
||||
text(")")
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ pub(crate) mod stmt_global;
|
||||
pub(crate) mod stmt_if;
|
||||
pub(crate) mod stmt_import;
|
||||
pub(crate) mod stmt_import_from;
|
||||
pub(crate) mod stmt_line_magic;
|
||||
pub(crate) mod stmt_ipy_escape_command;
|
||||
pub(crate) mod stmt_match;
|
||||
pub(crate) mod stmt_nonlocal;
|
||||
pub(crate) mod stmt_pass;
|
||||
@@ -61,7 +61,7 @@ impl FormatRule<Stmt, PyFormatContext<'_>> for FormatStmt {
|
||||
Stmt::Break(x) => x.format().fmt(f),
|
||||
Stmt::Continue(x) => x.format().fmt(f),
|
||||
Stmt::TypeAlias(x) => x.format().fmt(f),
|
||||
Stmt::LineMagic(_) => todo!(),
|
||||
Stmt::IpyEscapeCommand(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,21 +58,28 @@ impl FormatNodeRule<StmtFunctionDef> for FormatStmtFunctionDef {
|
||||
write!(f, [type_params.format()])?;
|
||||
}
|
||||
|
||||
write!(f, [item.parameters.format()])?;
|
||||
let format_inner = format_with(|f: &mut PyFormatter| {
|
||||
write!(f, [item.parameters.format()])?;
|
||||
if let Some(return_annotation) = item.returns.as_ref() {
|
||||
write!(f, [space(), text("->"), space()])?;
|
||||
if return_annotation.is_tuple_expr() {
|
||||
write!(
|
||||
f,
|
||||
[return_annotation.format().with_options(Parentheses::Never)]
|
||||
)?;
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
[optional_parentheses(
|
||||
&return_annotation.format().with_options(Parentheses::Never),
|
||||
)]
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Some(return_annotation) = item.returns.as_ref() {
|
||||
write!(
|
||||
f,
|
||||
[
|
||||
space(),
|
||||
text("->"),
|
||||
space(),
|
||||
optional_parentheses(
|
||||
&return_annotation.format().with_options(Parentheses::Never)
|
||||
)
|
||||
]
|
||||
)?;
|
||||
}
|
||||
write!(f, [group(&format_inner)])?;
|
||||
|
||||
write!(
|
||||
f,
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
use crate::{verbatim_text, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::StmtIpyEscapeCommand;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtIpyEscapeCommand;
|
||||
|
||||
impl FormatNodeRule<StmtIpyEscapeCommand> for FormatStmtIpyEscapeCommand {
|
||||
fn fmt_fields(&self, item: &StmtIpyEscapeCommand, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [verbatim_text(item)])
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
use crate::{verbatim_text, FormatNodeRule, PyFormatter};
|
||||
use ruff_formatter::{write, Buffer, FormatResult};
|
||||
use ruff_python_ast::StmtLineMagic;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FormatStmtLineMagic;
|
||||
|
||||
impl FormatNodeRule<StmtLineMagic> for FormatStmtLineMagic {
|
||||
fn fmt_fields(&self, item: &StmtLineMagic, f: &mut PyFormatter) -> FormatResult<()> {
|
||||
write!(f, [verbatim_text(item)])
|
||||
}
|
||||
}
|
||||
@@ -100,18 +100,20 @@ def foo() -> tuple[int, int, int,]:
|
||||
```diff
|
||||
--- Black
|
||||
+++ Ruff
|
||||
@@ -26,7 +26,9 @@
|
||||
@@ -26,7 +26,11 @@
|
||||
return 2 * a
|
||||
|
||||
|
||||
-def double(a: int) -> int: # Hello
|
||||
+def double(a: int) -> (
|
||||
+def double(
|
||||
+ a: int
|
||||
+) -> (
|
||||
+ int # Hello
|
||||
+):
|
||||
return 2 * a
|
||||
|
||||
|
||||
@@ -54,7 +56,9 @@
|
||||
@@ -54,7 +58,9 @@
|
||||
a: int,
|
||||
b: int,
|
||||
c: int,
|
||||
@@ -155,7 +157,9 @@ def double(a: int) -> int: # Hello
|
||||
return 2 * a
|
||||
|
||||
|
||||
def double(a: int) -> (
|
||||
def double(
|
||||
a: int
|
||||
) -> (
|
||||
int # Hello
|
||||
):
|
||||
return 2 * a
|
||||
|
||||
@@ -377,6 +377,70 @@ def f( # first
|
||||
# third
|
||||
):
|
||||
...
|
||||
|
||||
# Handle comments on empty tuple return types.
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
# comment
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
1
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
1, 2
|
||||
): ...
|
||||
|
||||
def zrevrangebylex(self, name: _Key, max: _Value, min: _Value, start: int | None = None, num: int | None = None) -> ( # type: ignore[override]
|
||||
(1, 2)
|
||||
): ...
|
||||
|
||||
def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197
|
||||
self, m: Match[str], data: str
|
||||
) -> Union[Tuple[None, None, None], Tuple[Element, int, int]]:
|
||||
...
|
||||
|
||||
def double(a: int # Hello
|
||||
) -> (int):
|
||||
return 2 * a
|
||||
|
||||
def double(a: int) -> ( # Hello
|
||||
int
|
||||
):
|
||||
return 2*a
|
||||
|
||||
def double(a: int) -> ( # Hello
|
||||
):
|
||||
return 2*a
|
||||
|
||||
# Breaking over parameters and return types. (Black adds a trailing comma when the
|
||||
# function arguments break here with a single argument; we do not.)
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, a) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> a:
|
||||
...
|
||||
|
||||
def f(a) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> a:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) -> a:
|
||||
...
|
||||
```
|
||||
|
||||
## Output
|
||||
@@ -905,6 +969,141 @@ def f( # first
|
||||
/, # second
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
# Handle comments on empty tuple return types.
|
||||
def zrevrangebylex(
|
||||
self,
|
||||
name: _Key,
|
||||
max: _Value,
|
||||
min: _Value,
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
self,
|
||||
name: _Key,
|
||||
max: _Value,
|
||||
min: _Value,
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
# comment
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
self,
|
||||
name: _Key,
|
||||
max: _Value,
|
||||
min: _Value,
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> 1: # type: ignore[override]
|
||||
...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
self,
|
||||
name: _Key,
|
||||
max: _Value,
|
||||
min: _Value,
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> ( # type: ignore[override]
|
||||
1,
|
||||
2,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def zrevrangebylex(
|
||||
self,
|
||||
name: _Key,
|
||||
max: _Value,
|
||||
min: _Value,
|
||||
start: int | None = None,
|
||||
num: int | None = None,
|
||||
) -> (1, 2): # type: ignore[override]
|
||||
...
|
||||
|
||||
|
||||
def handleMatch( # type: ignore[override] # https://github.com/python/mypy/issues/10197
|
||||
self, m: Match[str], data: str
|
||||
) -> Union[Tuple[None, None, None], Tuple[Element, int, int]]:
|
||||
...
|
||||
|
||||
|
||||
def double(
|
||||
a: int, # Hello
|
||||
) -> int:
|
||||
return 2 * a
|
||||
|
||||
|
||||
def double(a: int) -> int: # Hello
|
||||
return 2 * a
|
||||
|
||||
|
||||
def double(
|
||||
a: int
|
||||
) -> ( # Hello
|
||||
):
|
||||
return 2 * a
|
||||
|
||||
|
||||
# Breaking over parameters and return types. (Black adds a trailing comma when the
|
||||
# function arguments break here with a single argument; we do not.)
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, a
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
|
||||
def f(
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
) -> a:
|
||||
...
|
||||
|
||||
|
||||
def f(
|
||||
a
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa]() -> (
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def f[
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
]() -> a:
|
||||
...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
) -> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:
|
||||
...
|
||||
|
||||
|
||||
def f[aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa](
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
) -> a:
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ use std::{char, cmp::Ordering, str::FromStr};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{Num, Zero};
|
||||
use ruff_python_ast::MagicKind;
|
||||
use ruff_python_ast::IpyEscapeKind;
|
||||
use ruff_text_size::{TextLen, TextRange, TextSize};
|
||||
use unic_emoji_char::is_emoji_presentation;
|
||||
use unic_ucd_ident::{is_xid_continue, is_xid_start};
|
||||
@@ -398,8 +398,8 @@ impl<'source> Lexer<'source> {
|
||||
Tok::Comment(self.token_text().to_string())
|
||||
}
|
||||
|
||||
/// Lex a single magic command.
|
||||
fn lex_magic_command(&mut self, kind: MagicKind) -> Tok {
|
||||
/// Lex a single IPython escape command.
|
||||
fn lex_ipython_escape_command(&mut self, escape_kind: IpyEscapeKind) -> Tok {
|
||||
let mut value = String::new();
|
||||
|
||||
loop {
|
||||
@@ -457,7 +457,7 @@ impl<'source> Lexer<'source> {
|
||||
// Now, the whitespace and empty value check also makes sure that an empty
|
||||
// command (e.g. `%?` or `? ??`, no value after/between the escape tokens)
|
||||
// is not recognized as a help end escape command. So, `%?` and `? ??` are
|
||||
// `MagicKind::Magic` and `MagicKind::Help` because of the initial `%` and `??`
|
||||
// `IpyEscapeKind::Magic` and `IpyEscapeKind::Help` because of the initial `%` and `??`
|
||||
// tokens.
|
||||
if question_count > 2
|
||||
|| value.chars().last().map_or(true, is_python_whitespace)
|
||||
@@ -471,31 +471,34 @@ impl<'source> Lexer<'source> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if kind.is_help() {
|
||||
if escape_kind.is_help() {
|
||||
// If we've recognize this as a help end escape command, then
|
||||
// any question mark token / whitespaces at the start are not
|
||||
// considered as part of the value.
|
||||
//
|
||||
// For example, `??foo?` is recognized as `MagicKind::Help` and
|
||||
// For example, `??foo?` is recognized as `IpyEscapeKind::Help` and
|
||||
// `value` is `foo` instead of `??foo`.
|
||||
value = value.trim_start_matches([' ', '?']).to_string();
|
||||
} else if kind.is_magic() {
|
||||
} else if escape_kind.is_magic() {
|
||||
// Between `%` and `?` (at the end), the `?` takes priority
|
||||
// over the `%` so `%foo?` is recognized as `MagicKind::Help`
|
||||
// over the `%` so `%foo?` is recognized as `IpyEscapeKind::Help`
|
||||
// and `value` is `%foo` instead of `foo`. So, we need to
|
||||
// insert the magic escape token at the start.
|
||||
value.insert_str(0, kind.as_str());
|
||||
value.insert_str(0, escape_kind.as_str());
|
||||
}
|
||||
|
||||
let kind = match question_count {
|
||||
1 => MagicKind::Help,
|
||||
2 => MagicKind::Help2,
|
||||
1 => IpyEscapeKind::Help,
|
||||
2 => IpyEscapeKind::Help2,
|
||||
_ => unreachable!("`question_count` is always 1 or 2"),
|
||||
};
|
||||
return Tok::MagicCommand { kind, value };
|
||||
return Tok::IpyEscapeCommand { kind, value };
|
||||
}
|
||||
'\n' | '\r' | EOF_CHAR => {
|
||||
return Tok::MagicCommand { kind, value };
|
||||
return Tok::IpyEscapeCommand {
|
||||
kind: escape_kind,
|
||||
value,
|
||||
};
|
||||
}
|
||||
c => {
|
||||
self.cursor.bump();
|
||||
@@ -763,23 +766,26 @@ impl<'source> Lexer<'source> {
|
||||
&& self.state.is_after_equal()
|
||||
&& self.nesting == 0 =>
|
||||
{
|
||||
// SAFETY: Safe because `c` has been matched against one of the possible magic command prefix
|
||||
self.lex_magic_command(MagicKind::try_from(c).unwrap())
|
||||
// SAFETY: Safe because `c` has been matched against one of the possible escape command token
|
||||
self.lex_ipython_escape_command(IpyEscapeKind::try_from(c).unwrap())
|
||||
}
|
||||
|
||||
c @ ('%' | '!' | '?' | '/' | ';' | ',')
|
||||
if self.mode == Mode::Jupyter && self.state.is_new_logical_line() =>
|
||||
{
|
||||
let kind = if let Ok(kind) = MagicKind::try_from([c, self.cursor.first()]) {
|
||||
let kind = if let Ok(kind) = IpyEscapeKind::try_from([c, self.cursor.first()]) {
|
||||
self.cursor.bump();
|
||||
kind
|
||||
} else {
|
||||
// SAFETY: Safe because `c` has been matched against one of the possible magic command prefix
|
||||
MagicKind::try_from(c).unwrap()
|
||||
// SAFETY: Safe because `c` has been matched against one of the possible escape command token
|
||||
IpyEscapeKind::try_from(c).unwrap()
|
||||
};
|
||||
|
||||
self.lex_magic_command(kind)
|
||||
self.lex_ipython_escape_command(kind)
|
||||
}
|
||||
|
||||
'?' if self.mode == Mode::Jupyter => Tok::Question,
|
||||
|
||||
'/' => {
|
||||
if self.cursor.eat_char('=') {
|
||||
Tok::SlashEqual
|
||||
@@ -1205,7 +1211,7 @@ const fn is_python_whitespace(c: char) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::MagicKind;
|
||||
use ruff_python_ast::IpyEscapeKind;
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -1239,15 +1245,15 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_jupyter_magic_line_continuation_with_eol(eol: &str) {
|
||||
fn assert_ipython_escape_command_line_continuation_with_eol(eol: &str) {
|
||||
let source = format!("%matplotlib \\{eol} --inline");
|
||||
let tokens = lex_jupyter_source(&source);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "matplotlib --inline".to_string(),
|
||||
kind: MagicKind::Magic
|
||||
kind: IpyEscapeKind::Magic
|
||||
},
|
||||
Tok::Newline
|
||||
]
|
||||
@@ -1255,29 +1261,29 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_unix_eol() {
|
||||
assert_jupyter_magic_line_continuation_with_eol(UNIX_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_unix_eol() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol(UNIX_EOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_mac_eol() {
|
||||
assert_jupyter_magic_line_continuation_with_eol(MAC_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_mac_eol() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol(MAC_EOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_windows_eol() {
|
||||
assert_jupyter_magic_line_continuation_with_eol(WINDOWS_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_windows_eol() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol(WINDOWS_EOL);
|
||||
}
|
||||
|
||||
fn assert_jupyter_magic_line_continuation_with_eol_and_eof(eol: &str) {
|
||||
fn assert_ipython_escape_command_line_continuation_with_eol_and_eof(eol: &str) {
|
||||
let source = format!("%matplotlib \\{eol}");
|
||||
let tokens = lex_jupyter_source(&source);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "matplotlib ".to_string(),
|
||||
kind: MagicKind::Magic
|
||||
kind: IpyEscapeKind::Magic
|
||||
},
|
||||
Tok::Newline
|
||||
]
|
||||
@@ -1285,70 +1291,70 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_unix_eol_and_eof() {
|
||||
assert_jupyter_magic_line_continuation_with_eol_and_eof(UNIX_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_unix_eol_and_eof() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol_and_eof(UNIX_EOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_mac_eol_and_eof() {
|
||||
assert_jupyter_magic_line_continuation_with_eol_and_eof(MAC_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_mac_eol_and_eof() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol_and_eof(MAC_EOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_line_continuation_windows_eol_and_eof() {
|
||||
assert_jupyter_magic_line_continuation_with_eol_and_eof(WINDOWS_EOL);
|
||||
fn test_ipython_escape_command_line_continuation_windows_eol_and_eof() {
|
||||
assert_ipython_escape_command_line_continuation_with_eol_and_eof(WINDOWS_EOL);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_jupyter_magic() {
|
||||
fn test_empty_ipython_escape_command() {
|
||||
let source = "%\n%%\n!\n!!\n?\n??\n/\n,\n;";
|
||||
let tokens = lex_jupyter_source(source);
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Magic2,
|
||||
kind: IpyEscapeKind::Magic2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Shell,
|
||||
kind: IpyEscapeKind::Shell,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::ShCap,
|
||||
kind: IpyEscapeKind::ShCap,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Paren,
|
||||
kind: IpyEscapeKind::Paren,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Quote,
|
||||
kind: IpyEscapeKind::Quote,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: String::new(),
|
||||
kind: MagicKind::Quote2,
|
||||
kind: IpyEscapeKind::Quote2,
|
||||
},
|
||||
Tok::Newline,
|
||||
]
|
||||
@@ -1356,7 +1362,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic() {
|
||||
fn test_ipython_escape_command() {
|
||||
let source = r"
|
||||
?foo
|
||||
??foo
|
||||
@@ -1377,59 +1383,59 @@ mod tests {
|
||||
assert_eq!(
|
||||
tokens,
|
||||
vec![
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "timeit a = b".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "timeit a % 3".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "matplotlib --inline".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "pwd && ls -a | sed 's/^/\\\\ /'".to_string(),
|
||||
kind: MagicKind::Shell,
|
||||
kind: IpyEscapeKind::Shell,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "cd /Users/foo/Library/Application\\ Support/".to_string(),
|
||||
kind: MagicKind::ShCap,
|
||||
kind: IpyEscapeKind::ShCap,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo 1 2".to_string(),
|
||||
kind: MagicKind::Paren,
|
||||
kind: IpyEscapeKind::Paren,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo 1 2".to_string(),
|
||||
kind: MagicKind::Quote,
|
||||
kind: IpyEscapeKind::Quote,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo 1 2".to_string(),
|
||||
kind: MagicKind::Quote2,
|
||||
kind: IpyEscapeKind::Quote2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "ls".to_string(),
|
||||
kind: MagicKind::Shell,
|
||||
kind: IpyEscapeKind::Shell,
|
||||
},
|
||||
Tok::Newline,
|
||||
]
|
||||
@@ -1437,7 +1443,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_help_end() {
|
||||
fn test_ipython_help_end_escape_command() {
|
||||
let source = r"
|
||||
?foo?
|
||||
?? foo?
|
||||
@@ -1462,84 +1468,84 @@ mod tests {
|
||||
assert_eq!(
|
||||
tokens,
|
||||
[
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: " foo ?".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo???".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "?foo???".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: " ?".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "??".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "%foo".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "%foo".to_string(),
|
||||
kind: MagicKind::Help2,
|
||||
kind: IpyEscapeKind::Help2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "foo???".to_string(),
|
||||
kind: MagicKind::Magic2,
|
||||
kind: IpyEscapeKind::Magic2,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "pwd".to_string(),
|
||||
kind: MagicKind::Help,
|
||||
kind: IpyEscapeKind::Help,
|
||||
},
|
||||
Tok::Newline,
|
||||
]
|
||||
@@ -1547,7 +1553,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_indentation() {
|
||||
fn test_ipython_escape_command_indentation() {
|
||||
let source = r"
|
||||
if True:
|
||||
%matplotlib \
|
||||
@@ -1562,9 +1568,9 @@ if True:
|
||||
Tok::Colon,
|
||||
Tok::Newline,
|
||||
Tok::Indent,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "matplotlib --inline".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::Dedent,
|
||||
@@ -1573,7 +1579,7 @@ if True:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_assignment() {
|
||||
fn test_ipython_escape_command_assignment() {
|
||||
let source = r"
|
||||
pwd = !pwd
|
||||
foo = %timeit a = b
|
||||
@@ -1589,54 +1595,54 @@ baz = %matplotlib \
|
||||
name: "pwd".to_string()
|
||||
},
|
||||
Tok::Equal,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "pwd".to_string(),
|
||||
kind: MagicKind::Shell,
|
||||
kind: IpyEscapeKind::Shell,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::Name {
|
||||
name: "foo".to_string()
|
||||
},
|
||||
Tok::Equal,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "timeit a = b".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::Name {
|
||||
name: "bar".to_string()
|
||||
},
|
||||
Tok::Equal,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "timeit a % 3".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
Tok::Name {
|
||||
name: "baz".to_string()
|
||||
},
|
||||
Tok::Equal,
|
||||
Tok::MagicCommand {
|
||||
Tok::IpyEscapeCommand {
|
||||
value: "matplotlib inline".to_string(),
|
||||
kind: MagicKind::Magic,
|
||||
kind: IpyEscapeKind::Magic,
|
||||
},
|
||||
Tok::Newline,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_no_jupyter_magic(tokens: &[Tok]) {
|
||||
fn assert_no_ipython_escape_command(tokens: &[Tok]) {
|
||||
for tok in tokens {
|
||||
if let Tok::MagicCommand { .. } = tok {
|
||||
panic!("Unexpected magic command token: {tok:?}")
|
||||
if let Tok::IpyEscapeCommand { .. } = tok {
|
||||
panic!("Unexpected escape command token: {tok:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_not_an_assignment() {
|
||||
fn test_ipython_escape_command_not_an_assignment() {
|
||||
let source = r"
|
||||
# Other magic kinds are not valid here (can't test `foo = ?str` because '?' is not a valid token)
|
||||
# Other escape kinds are not valid here (can't test `foo = ?str` because '?' is not a valid token)
|
||||
foo = /func
|
||||
foo = ;func
|
||||
foo = ,func
|
||||
@@ -1647,7 +1653,7 @@ def f(arg=%timeit a = b):
|
||||
pass"
|
||||
.trim();
|
||||
let tokens = lex_jupyter_source(source);
|
||||
assert_no_jupyter_magic(&tokens);
|
||||
assert_no_ipython_escape_command(&tokens);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -117,7 +117,7 @@ pub fn parse_expression_starts_at(
|
||||
///
|
||||
/// This function is the most general function to parse Python code. Based on the [`Mode`] supplied,
|
||||
/// it can be used to parse a single expression, a full Python program, an interactive expression
|
||||
/// or a Python program containing Jupyter magics.
|
||||
/// or a Python program containing IPython escape commands.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -146,7 +146,7 @@ pub fn parse_expression_starts_at(
|
||||
/// assert!(program.is_ok());
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, we can parse a Python program containing Jupyter magics:
|
||||
/// Additionally, we can parse a Python program containing IPython escapes:
|
||||
///
|
||||
/// ```
|
||||
/// use ruff_python_parser::{Mode, parse};
|
||||
@@ -1122,7 +1122,7 @@ class Abcd:
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic() {
|
||||
fn test_ipython_escape_commands() {
|
||||
let parse_ast = parse(
|
||||
r#"
|
||||
# Normal Python code
|
||||
@@ -1169,7 +1169,7 @@ def foo():
|
||||
;foo 1 2
|
||||
,foo 1 2
|
||||
|
||||
# Indented magic
|
||||
# Indented escape commands
|
||||
for a in range(5):
|
||||
!ls
|
||||
|
||||
@@ -1180,6 +1180,15 @@ foo = %foo \
|
||||
|
||||
% foo
|
||||
foo = %foo # comment
|
||||
|
||||
# Help end line magics
|
||||
foo?
|
||||
foo.bar??
|
||||
foo.bar.baz?
|
||||
foo[0]??
|
||||
foo[0][1]?
|
||||
foo.bar[0].baz[1]??
|
||||
foo.bar[0].baz[2].egg??
|
||||
"#
|
||||
.trim(),
|
||||
Mode::Jupyter,
|
||||
@@ -1190,7 +1199,7 @@ foo = %foo # comment
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jupyter_magic_parse_error() {
|
||||
fn test_ipython_escape_command_parse_error() {
|
||||
let source = r#"
|
||||
a = 1
|
||||
%timeit a == 1
|
||||
@@ -1200,7 +1209,7 @@ a = 1
|
||||
let parse_err = parse_tokens(lxr, Mode::Module, "<test>").unwrap_err();
|
||||
assert_eq!(
|
||||
parse_err.to_string(),
|
||||
"line magics are only allowed in Jupyter mode at byte offset 6".to_string()
|
||||
"IPython escape commands are only allowed in Jupyter mode at byte offset 6".to_string()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use ruff_text_size::TextSize;
|
||||
use ruff_python_ast::{self as ast, Ranged, MagicKind};
|
||||
use ruff_python_ast::{self as ast, Ranged, IpyEscapeKind};
|
||||
use crate::{
|
||||
Mode,
|
||||
lexer::{LexicalError, LexicalErrorType},
|
||||
@@ -14,6 +14,7 @@ use crate::{
|
||||
string::parse_strings,
|
||||
token::{self, StringKind},
|
||||
};
|
||||
use lalrpop_util::ParseError;
|
||||
|
||||
grammar(mode: Mode);
|
||||
|
||||
@@ -88,7 +89,8 @@ SmallStatement: ast::Stmt = {
|
||||
NonlocalStatement,
|
||||
AssertStatement,
|
||||
TypeAliasStatement,
|
||||
LineMagicStatement,
|
||||
IpyEscapeCommandStatement,
|
||||
IpyHelpEndEscapeCommandStatement,
|
||||
};
|
||||
|
||||
PassStatement: ast::Stmt = {
|
||||
@@ -153,7 +155,7 @@ ExpressionStatement: ast::Stmt = {
|
||||
|
||||
AssignSuffix: ast::Expr = {
|
||||
"=" <e:TestListOrYieldExpr> => e,
|
||||
"=" <e:LineMagicExpr> => e
|
||||
"=" <e:IpyEscapeCommandExpr> => e
|
||||
};
|
||||
|
||||
TestListOrYieldExpr: ast::Expr = {
|
||||
@@ -321,51 +323,123 @@ AssertStatement: ast::Stmt = {
|
||||
},
|
||||
};
|
||||
|
||||
LineMagicStatement: ast::Stmt = {
|
||||
<location:@L> <m:line_magic> <end_location:@R> =>? {
|
||||
IpyEscapeCommandStatement: ast::Stmt = {
|
||||
<location:@L> <c:ipy_escape_command> <end_location:@R> =>? {
|
||||
if mode == Mode::Jupyter {
|
||||
Ok(ast::Stmt::LineMagic(
|
||||
ast::StmtLineMagic {
|
||||
kind: m.0,
|
||||
value: m.1,
|
||||
Ok(ast::Stmt::IpyEscapeCommand(
|
||||
ast::StmtIpyEscapeCommand {
|
||||
kind: c.0,
|
||||
value: c.1,
|
||||
range: (location..end_location).into()
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(LexicalError {
|
||||
error: LexicalErrorType::OtherError("line magics are only allowed in Jupyter mode".to_string()),
|
||||
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
|
||||
location,
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LineMagicExpr: ast::Expr = {
|
||||
<location:@L> <m:line_magic> <end_location:@R> =>? {
|
||||
IpyEscapeCommandExpr: ast::Expr = {
|
||||
<location:@L> <c:ipy_escape_command> <end_location:@R> =>? {
|
||||
if mode == Mode::Jupyter {
|
||||
// This should never occur as the lexer won't allow it.
|
||||
if !matches!(m.0, MagicKind::Magic | MagicKind::Shell) {
|
||||
if !matches!(c.0, IpyEscapeKind::Magic | IpyEscapeKind::Shell) {
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::OtherError("expr line magics are only allowed for % and !".to_string()),
|
||||
error: LexicalErrorType::OtherError("IPython escape command expr is only allowed for % and !".to_string()),
|
||||
location,
|
||||
})?;
|
||||
}
|
||||
Ok(ast::Expr::LineMagic(
|
||||
ast::ExprLineMagic {
|
||||
kind: m.0,
|
||||
value: m.1,
|
||||
Ok(ast::Expr::IpyEscapeCommand(
|
||||
ast::ExprIpyEscapeCommand {
|
||||
kind: c.0,
|
||||
value: c.1,
|
||||
range: (location..end_location).into()
|
||||
}
|
||||
))
|
||||
} else {
|
||||
Err(LexicalError {
|
||||
error: LexicalErrorType::OtherError("line magics are only allowed in Jupyter mode".to_string()),
|
||||
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
|
||||
location,
|
||||
})?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpyHelpEndEscapeCommandStatement: ast::Stmt = {
|
||||
// We are permissive than the original implementation because we would allow whitespace
|
||||
// between the expression and the suffix while the IPython implementation doesn't allow it.
|
||||
// For example, `foo ?` would be valid in our case but invalid from IPython.
|
||||
<location:@L> <e:Expression<"All">> <suffix:("?")+> <end_location:@R> =>? {
|
||||
fn unparse_expr(expr: &ast::Expr, buffer: &mut String) -> Result<(), LexicalError> {
|
||||
match expr {
|
||||
ast::Expr::Name(ast::ExprName { id, .. }) => {
|
||||
buffer.push_str(id.as_str());
|
||||
},
|
||||
ast::Expr::Subscript(ast::ExprSubscript { value, slice, range, .. }) => {
|
||||
let ast::Expr::Constant(ast::ExprConstant { value: ast::Constant::Int(integer), .. }) = slice.as_ref() else {
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::OtherError("only integer constants are allowed in Subscript expressions in help end escape command".to_string()),
|
||||
location: range.start(),
|
||||
});
|
||||
};
|
||||
unparse_expr(value, buffer)?;
|
||||
buffer.push('[');
|
||||
buffer.push_str(&format!("{}", integer));
|
||||
buffer.push(']');
|
||||
},
|
||||
ast::Expr::Attribute(ast::ExprAttribute { value, attr, .. }) => {
|
||||
unparse_expr(value, buffer)?;
|
||||
buffer.push('.');
|
||||
buffer.push_str(attr.as_str());
|
||||
},
|
||||
_ => {
|
||||
return Err(LexicalError {
|
||||
error: LexicalErrorType::OtherError("only Name, Subscript and Attribute expressions are allowed in help end escape command".to_string()),
|
||||
location: expr.range().start(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
if mode != Mode::Jupyter {
|
||||
return Err(ParseError::User {
|
||||
error: LexicalError {
|
||||
error: LexicalErrorType::OtherError("IPython escape commands are only allowed in Jupyter mode".to_string()),
|
||||
location,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let kind = match suffix.len() {
|
||||
1 => IpyEscapeKind::Help,
|
||||
2 => IpyEscapeKind::Help2,
|
||||
_ => {
|
||||
return Err(ParseError::User {
|
||||
error: LexicalError {
|
||||
error: LexicalErrorType::OtherError("maximum of 2 `?` tokens are allowed in help end escape command".to_string()),
|
||||
location,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let mut value = String::new();
|
||||
unparse_expr(&e, &mut value)?;
|
||||
|
||||
Ok(ast::Stmt::IpyEscapeCommand(
|
||||
ast::StmtIpyEscapeCommand {
|
||||
kind,
|
||||
value,
|
||||
range: (location..end_location).into()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
CompoundStatement: ast::Stmt = {
|
||||
MatchStatement,
|
||||
IfStatement,
|
||||
@@ -1732,6 +1806,7 @@ extern {
|
||||
Dedent => token::Tok::Dedent,
|
||||
StartModule => token::Tok::StartModule,
|
||||
StartExpression => token::Tok::StartExpression,
|
||||
"?" => token::Tok::Question,
|
||||
"+" => token::Tok::Plus,
|
||||
"-" => token::Tok::Minus,
|
||||
"~" => token::Tok::Tilde,
|
||||
@@ -1825,8 +1900,8 @@ extern {
|
||||
triple_quoted: <bool>
|
||||
},
|
||||
name => token::Tok::Name { name: <String> },
|
||||
line_magic => token::Tok::MagicCommand {
|
||||
kind: <MagicKind>,
|
||||
ipy_escape_command => token::Tok::IpyEscapeCommand {
|
||||
kind: <IpyEscapeKind>,
|
||||
value: <String>
|
||||
},
|
||||
"\n" => token::Tok::Newline,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ expression: parse_ast
|
||||
---
|
||||
Module(
|
||||
ModModule {
|
||||
range: 0..803,
|
||||
range: 0..929,
|
||||
body: [
|
||||
Expr(
|
||||
StmtExpr {
|
||||
@@ -31,92 +31,92 @@ Module(
|
||||
),
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 66..73,
|
||||
kind: Help2,
|
||||
value: "a.foo",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 74..80,
|
||||
kind: Help,
|
||||
value: "a.foo",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 81..88,
|
||||
kind: Help,
|
||||
value: "a.foo",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 89..100,
|
||||
kind: Help2,
|
||||
value: "a.foo()",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 115..128,
|
||||
kind: Magic,
|
||||
value: "timeit a = b",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 129..147,
|
||||
kind: Magic,
|
||||
value: "timeit foo(b) % 3",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 148..176,
|
||||
kind: Magic,
|
||||
value: "alias showPath pwd && ls -a",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 177..205,
|
||||
kind: Magic,
|
||||
value: "timeit a = foo(b); b = 2",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 206..226,
|
||||
kind: Magic,
|
||||
value: "matplotlib --inline",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 227..253,
|
||||
kind: Magic,
|
||||
value: "matplotlib --inline",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 277..309,
|
||||
kind: Shell,
|
||||
value: "pwd && ls -a | sed 's/^/\\ /'",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 310..347,
|
||||
kind: Shell,
|
||||
value: "pwd && ls -a | sed 's/^/\\\\ /'",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 348..393,
|
||||
kind: ShCap,
|
||||
value: "cd /Users/foo/Library/Application\\ Support/",
|
||||
@@ -176,22 +176,22 @@ Module(
|
||||
],
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 656..664,
|
||||
kind: Paren,
|
||||
value: "foo 1 2",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 665..673,
|
||||
kind: Quote2,
|
||||
value: "foo 1 2",
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 674..682,
|
||||
kind: Quote,
|
||||
value: "foo 1 2",
|
||||
@@ -199,31 +199,31 @@ Module(
|
||||
),
|
||||
For(
|
||||
StmtFor {
|
||||
range: 701..727,
|
||||
range: 711..737,
|
||||
is_async: false,
|
||||
target: Name(
|
||||
ExprName {
|
||||
range: 705..706,
|
||||
range: 715..716,
|
||||
id: "a",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
iter: Call(
|
||||
ExprCall {
|
||||
range: 710..718,
|
||||
range: 720..728,
|
||||
func: Name(
|
||||
ExprName {
|
||||
range: 710..715,
|
||||
range: 720..725,
|
||||
id: "range",
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
arguments: Arguments {
|
||||
range: 715..718,
|
||||
range: 725..728,
|
||||
args: [
|
||||
Constant(
|
||||
ExprConstant {
|
||||
range: 716..717,
|
||||
range: 726..727,
|
||||
value: Int(
|
||||
5,
|
||||
),
|
||||
@@ -236,9 +236,9 @@ Module(
|
||||
},
|
||||
),
|
||||
body: [
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
range: 724..727,
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 734..737,
|
||||
kind: Shell,
|
||||
value: "ls",
|
||||
},
|
||||
@@ -249,19 +249,19 @@ Module(
|
||||
),
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 729..738,
|
||||
range: 739..748,
|
||||
targets: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 729..731,
|
||||
range: 739..741,
|
||||
id: "p1",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: LineMagic(
|
||||
ExprLineMagic {
|
||||
range: 734..738,
|
||||
value: IpyEscapeCommand(
|
||||
ExprIpyEscapeCommand {
|
||||
range: 744..748,
|
||||
kind: Shell,
|
||||
value: "pwd",
|
||||
},
|
||||
@@ -270,25 +270,25 @@ Module(
|
||||
),
|
||||
AnnAssign(
|
||||
StmtAnnAssign {
|
||||
range: 739..753,
|
||||
range: 749..763,
|
||||
target: Name(
|
||||
ExprName {
|
||||
range: 739..741,
|
||||
range: 749..751,
|
||||
id: "p2",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
annotation: Name(
|
||||
ExprName {
|
||||
range: 743..746,
|
||||
range: 753..756,
|
||||
id: "str",
|
||||
ctx: Load,
|
||||
},
|
||||
),
|
||||
value: Some(
|
||||
LineMagic(
|
||||
ExprLineMagic {
|
||||
range: 749..753,
|
||||
IpyEscapeCommand(
|
||||
ExprIpyEscapeCommand {
|
||||
range: 759..763,
|
||||
kind: Shell,
|
||||
value: "pwd",
|
||||
},
|
||||
@@ -299,53 +299,102 @@ Module(
|
||||
),
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 754..774,
|
||||
range: 764..784,
|
||||
targets: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 754..757,
|
||||
range: 764..767,
|
||||
id: "foo",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: LineMagic(
|
||||
ExprLineMagic {
|
||||
range: 760..774,
|
||||
value: IpyEscapeCommand(
|
||||
ExprIpyEscapeCommand {
|
||||
range: 770..784,
|
||||
kind: Magic,
|
||||
value: "foo bar",
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
LineMagic(
|
||||
StmtLineMagic {
|
||||
range: 776..781,
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 786..791,
|
||||
kind: Magic,
|
||||
value: " foo",
|
||||
},
|
||||
),
|
||||
Assign(
|
||||
StmtAssign {
|
||||
range: 782..803,
|
||||
range: 792..813,
|
||||
targets: [
|
||||
Name(
|
||||
ExprName {
|
||||
range: 782..785,
|
||||
range: 792..795,
|
||||
id: "foo",
|
||||
ctx: Store,
|
||||
},
|
||||
),
|
||||
],
|
||||
value: LineMagic(
|
||||
ExprLineMagic {
|
||||
range: 788..803,
|
||||
value: IpyEscapeCommand(
|
||||
ExprIpyEscapeCommand {
|
||||
range: 798..813,
|
||||
kind: Magic,
|
||||
value: "foo # comment",
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 838..842,
|
||||
kind: Help,
|
||||
value: "foo",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 843..852,
|
||||
kind: Help2,
|
||||
value: "foo.bar",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 853..865,
|
||||
kind: Help,
|
||||
value: "foo.bar.baz",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 866..874,
|
||||
kind: Help2,
|
||||
value: "foo[0]",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 875..885,
|
||||
kind: Help,
|
||||
value: "foo[0][1]",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 886..905,
|
||||
kind: Help2,
|
||||
value: "foo.bar[0].baz[1]",
|
||||
},
|
||||
),
|
||||
IpyEscapeCommand(
|
||||
StmtIpyEscapeCommand {
|
||||
range: 906..929,
|
||||
kind: Help2,
|
||||
value: "foo.bar[0].baz[2].egg",
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
)
|
||||
@@ -6,7 +6,7 @@
|
||||
//! [CPython source]: https://github.com/python/cpython/blob/dfc2e065a2e71011017077e549cd2f9bf4944c54/Include/internal/pycore_token.h;
|
||||
use crate::Mode;
|
||||
use num_bigint::BigInt;
|
||||
use ruff_python_ast::MagicKind;
|
||||
use ruff_python_ast::IpyEscapeKind;
|
||||
use ruff_text_size::TextSize;
|
||||
use std::fmt;
|
||||
|
||||
@@ -44,13 +44,13 @@ pub enum Tok {
|
||||
/// Whether the string is triple quoted.
|
||||
triple_quoted: bool,
|
||||
},
|
||||
/// Token value for a Jupyter magic commands. These are filtered out of the token stream
|
||||
/// prior to parsing when the mode is [`Mode::Jupyter`].
|
||||
MagicCommand {
|
||||
/// Token value for IPython escape commands. These are recognized by the lexer
|
||||
/// only when the mode is [`Mode::Jupyter`].
|
||||
IpyEscapeCommand {
|
||||
/// The magic command value.
|
||||
value: String,
|
||||
/// The kind of magic command.
|
||||
kind: MagicKind,
|
||||
kind: IpyEscapeKind,
|
||||
},
|
||||
/// Token value for a comment. These are filtered out of the token stream prior to parsing.
|
||||
Comment(String),
|
||||
@@ -64,6 +64,8 @@ pub enum Tok {
|
||||
/// Token value for a dedent.
|
||||
Dedent,
|
||||
EndOfFile,
|
||||
/// Token value for a question mark `?`. This is only used in [`Mode::Jupyter`].
|
||||
Question,
|
||||
/// Token value for a left parenthesis `(`.
|
||||
Lpar,
|
||||
/// Token value for a right parenthesis `)`.
|
||||
@@ -232,7 +234,7 @@ impl fmt::Display for Tok {
|
||||
let quotes = "\"".repeat(if *triple_quoted { 3 } else { 1 });
|
||||
write!(f, "{kind}{quotes}{value}{quotes}")
|
||||
}
|
||||
MagicCommand { kind, value } => write!(f, "{kind}{value}"),
|
||||
IpyEscapeCommand { kind, value } => write!(f, "{kind}{value}"),
|
||||
Newline => f.write_str("Newline"),
|
||||
NonLogicalNewline => f.write_str("NonLogicalNewline"),
|
||||
Indent => f.write_str("Indent"),
|
||||
@@ -240,6 +242,7 @@ impl fmt::Display for Tok {
|
||||
StartModule => f.write_str("StartProgram"),
|
||||
StartExpression => f.write_str("StartExpression"),
|
||||
EndOfFile => f.write_str("EOF"),
|
||||
Question => f.write_str("'?'"),
|
||||
Lpar => f.write_str("'('"),
|
||||
Rpar => f.write_str("')'"),
|
||||
Lsqb => f.write_str("'['"),
|
||||
@@ -447,8 +450,8 @@ pub enum TokenKind {
|
||||
Complex,
|
||||
/// Token value for a string.
|
||||
String,
|
||||
/// Token value for a Jupyter magic command.
|
||||
MagicCommand,
|
||||
/// Token value for a IPython escape command.
|
||||
EscapeCommand,
|
||||
/// Token value for a comment. These are filtered out of the token stream prior to parsing.
|
||||
Comment,
|
||||
/// Token value for a newline.
|
||||
@@ -461,6 +464,8 @@ pub enum TokenKind {
|
||||
/// Token value for a dedent.
|
||||
Dedent,
|
||||
EndOfFile,
|
||||
/// Token value for a question mark `?`.
|
||||
Question,
|
||||
/// Token value for a left parenthesis `(`.
|
||||
Lpar,
|
||||
/// Token value for a right parenthesis `)`.
|
||||
@@ -776,13 +781,14 @@ impl TokenKind {
|
||||
Tok::Float { .. } => TokenKind::Float,
|
||||
Tok::Complex { .. } => TokenKind::Complex,
|
||||
Tok::String { .. } => TokenKind::String,
|
||||
Tok::MagicCommand { .. } => TokenKind::MagicCommand,
|
||||
Tok::IpyEscapeCommand { .. } => TokenKind::EscapeCommand,
|
||||
Tok::Comment(_) => TokenKind::Comment,
|
||||
Tok::Newline => TokenKind::Newline,
|
||||
Tok::NonLogicalNewline => TokenKind::NonLogicalNewline,
|
||||
Tok::Indent => TokenKind::Indent,
|
||||
Tok::Dedent => TokenKind::Dedent,
|
||||
Tok::EndOfFile => TokenKind::EndOfFile,
|
||||
Tok::Question => TokenKind::Question,
|
||||
Tok::Lpar => TokenKind::Lpar,
|
||||
Tok::Rpar => TokenKind::Rpar,
|
||||
Tok::Lsqb => TokenKind::Lsqb,
|
||||
|
||||
@@ -585,7 +585,7 @@ impl<'a> Imported<'a> for FromImport<'a> {
|
||||
}
|
||||
|
||||
/// A wrapper around an import [`BindingKind`] that can be any of the three types of imports.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, is_macro::Is)]
|
||||
pub enum AnyImport<'a> {
|
||||
Import(&'a Import<'a>),
|
||||
SubmoduleImport(&'a SubmoduleImport<'a>),
|
||||
|
||||
@@ -590,14 +590,26 @@ impl<'a> SemanticModel<'a> {
|
||||
// print(pa.csv.read_csv("test.csv"))
|
||||
// ```
|
||||
let import = self.bindings[binding_id].as_any_import()?;
|
||||
if !import.is_import() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Grab, e.g., `pyarrow` from `import pyarrow as pa`.
|
||||
let call_path = import.call_path();
|
||||
let segment = call_path.last()?;
|
||||
if *segment == symbol {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Locate the submodule import (e.g., `pyarrow.csv`) that `pa` aliases.
|
||||
let binding_id = self.scopes[scope_id].get(segment)?;
|
||||
if !self.bindings[binding_id].kind.is_submodule_import() {
|
||||
let submodule = &self.bindings[binding_id].as_any_import()?;
|
||||
if !submodule.is_submodule_import() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Ensure that the submodule import and the aliased import are from the same module.
|
||||
if import.module_name() != submodule.module_name() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use ruff::rules::{
|
||||
};
|
||||
use ruff::settings::configuration::Configuration;
|
||||
use ruff::settings::options::Options;
|
||||
use ruff::settings::types::PythonVersion;
|
||||
use ruff::settings::{defaults, flags, Settings};
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_python_codegen::Stylist;
|
||||
@@ -134,7 +135,7 @@ impl Workspace {
|
||||
line_length: Some(LineLength::default()),
|
||||
select: Some(defaults::PREFIXES.to_vec()),
|
||||
tab_size: Some(TabSize::default()),
|
||||
target_version: Some(defaults::TARGET_VERSION),
|
||||
target_version: Some(PythonVersion::default()),
|
||||
// Ignore a bunch of options that don't make sense in a single-file editor.
|
||||
cache_dir: None,
|
||||
exclude: None,
|
||||
|
||||
@@ -242,7 +242,7 @@ This tutorial has focused on Ruff's command-line interface, but Ruff can also be
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
|
||||
@@ -22,7 +22,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.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -32,7 +32,7 @@ Or, to enable autofix:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [ --fix, --exit-non-zero-on-fix ]
|
||||
@@ -43,7 +43,7 @@ Or, to run the hook on Jupyter Notebooks too:
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.0.283
|
||||
rev: v0.0.284
|
||||
hooks:
|
||||
- id: ruff
|
||||
types_or: [python, pyi, jupyter]
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "ruff"
|
||||
version = "0.0.283"
|
||||
version = "0.0.284"
|
||||
description = "An extremely fast Python linter, written in Rust."
|
||||
authors = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
maintainers = [{ name = "Charlie Marsh", email = "charlie.r.marsh@gmail.com" }]
|
||||
|
||||
4
ruff.schema.json
generated
4
ruff.schema.json
generated
@@ -376,7 +376,7 @@
|
||||
]
|
||||
},
|
||||
"line-length": {
|
||||
"description": "The line length to use when enforcing long-lines violations (like `E501`).",
|
||||
"description": "The line length to use when enforcing long-lines violations (like `E501`). Must be greater than `0`.",
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/LineLength"
|
||||
@@ -2741,7 +2741,7 @@
|
||||
"description": "The size of a tab.",
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
"minimum": 1.0
|
||||
},
|
||||
"Version": {
|
||||
"type": "string"
|
||||
|
||||
Reference in New Issue
Block a user