Commit graph

1108 commits

Author SHA1 Message Date
Charlie Marsh
b3789cd9e9
Fix continuation detection following multi-line strings (#9332)
## Summary

The logic that detects continuations assumed that tokens themselves
cannot span multiple lines. However, strings _can_ -- even single-quoted
strings.

Closes https://github.com/astral-sh/ruff/issues/9323.
2023-12-31 10:43:35 -05:00
Charlie Marsh
003851c41d
Avoid panic when comment is preceded by Unicode (#9331)
Closes https://github.com/astral-sh/ruff/issues/9328.
2023-12-31 12:54:31 +00:00
Charlie Marsh
94727996e8
Respect runtime-required decorators on functions (#9317)
## Summary

This PR modifies the semantics of `runtime-evaluated-decorators` to
respect decorators on both classes _and_ functions. Historically, this
only respected classes, since the common use-case is (e.g.)
`pydantic.BaseModel` -- but functions are equally valid.

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

## Test Plan

`cargo test`
2023-12-29 22:14:53 -04:00
Charlie Marsh
2895e7d126
Respect mixed return and raise cases in return-type analysis (#9310)
## Summary

Given:

```python
from somewhere import get_cfg

def lookup_cfg(cfg_description):
    cfg = get_cfg(cfg_description)
    if cfg is not None:
        return cfg
    raise AttributeError(f"No cfg found matching {cfg_description}")
```

We were analyzing the method from last-to-first statement. So we saw the
`raise`, then assumed the method _always_ raised. In reality, though, it
_might_ return. This PR improves the branch analysis to respect these
mixed cases.

Closes https://github.com/astral-sh/ruff/issues/9269.
Closes https://github.com/astral-sh/ruff/issues/9304.
2023-12-29 16:46:37 +00:00
Charlie Marsh
00f3c7d1d5
Respect attribute chains when resolving builtin call paths (#9309)
## Summary

When resolving `dict.__dict__`, we were discarding the `.__dict__`
segment when computing the call path.

## Test Plan

`cargo test`
2023-12-29 15:13:24 +00:00
Mike Bernard
375c175d53
[pylint] Implement empty-comment (PLR2044) (#9174)
## Summary

Part of #970.

This adds Pylint's [R0244
empty_comment](https://pylint.pycqa.org/en/latest/user_guide/messages/refactor/empty-comment.html)
lint as well as an always-safe fix.

## Test Plan

The included snapshot verifies the following:

- A line containing only a non-empty comment is not changed
- A line containing leading whitespace before a non-empty comment is not
changed
- A line containing only an empty comment has its content deleted
- A line containing only leading whitespace before an empty comment has
its content deleted
- A line containing only leading and trailing whitespace on an empty
comment has its content deleted
- A line containing trailing whitespace after a non-empty comment is not
changed
- A line containing only a single newline character (i.e. a blank line)
is not changed
- A line containing code followed by a non-empty comment is not changed
- A line containing code followed by an empty comment has its content
deleted after the last non-whitespace character
- Lines containing code and no comments are not changed
- Empty comment lines within block comments are ignored
- Empty comments within triple-quoted sections are ignored

## Comparison to Pylint

Running Ruff and Pylint 3.0.3 with Python 3.12.0 against the
`empty_comment.py` file added in this PR, we see the following:

* Identical behavior:
* empty_comment.py:3:0: R2044: Line with empty comment (empty-comment)
* empty_comment.py:4:0: R2044: Line with empty comment (empty-comment)
* empty_comment.py:5:0: R2044: Line with empty comment (empty-comment)
* empty_comment.py:18:0: R2044: Line with empty comment (empty-comment)

* Differing behavior:
* Pylint doesn't ignore empty comments in block comments commonly used
for visual spacing; I decided these were fine in this implementation
since many projects use these and likely do not want them removed.
* empty_comment.py:28:0: R2044: Line with empty comment (empty-comment)
* Pylint detects "empty comments" within the triple-quoted section at
the bottom of the file, which is arguably a bug in the Pylint
implementation since these are not truly comments. These are ignored by
this implementation.
* empty_comment.py:37:0: R2044: Line with empty comment (empty-comment)
* empty_comment.py:38:0: R2044: Line with empty comment (empty-comment)
* empty_comment.py:39:0: R2044: Line with empty comment (empty-comment)
2023-12-29 02:53:56 +00:00
Mikael Arguedas
edfad461a8
[flake8-bandit/S506] Dont report violation when SafeLoader is imported from yaml.loader (#9299)
## Summary

Hey there 👋 thanks for this great project!

On python code looking like the following
```
import yaml
from yaml.loader import SafeLoader

with MY_FILE_PATH.open("r") as my_file:
    my_data = yaml.load(my_file, Loader=SafeLoader)
```

ruff reports this error:
```
S506 Probable use of unsafe loader `SafeLoader` with `yaml.load`. Allows instantiation of arbitrary objects. Consider `yaml.safe_load`.
```

This PR is an attempt to support SafeLoader being imported for either
`yaml` or `yaml.loader`

Disclaimer:
I am not familiar with Rust so this is likely not the better way of
doing it. Interested in hearing how to adapt this PR to provide similar
behavior in a better way
 

## Test Plan

The S506.py file was updated accordingly to cover the use cases and test
were confirmed to pass with this change.
2023-12-28 14:30:46 +00:00
Charlie Marsh
e178d938cc
Respect unused-noqa via per-file-ignores (#9300)
## Summary

If `RUF100` is ignored via `per-file-ignores`, we need to avoid raising
it. `RUF100` has special "self-ignore" logic, since the rule itself
deals with `# noqa` directives. This PR wires up `per-file-ignores` to
that "self-ignore" logic.

Closes https://github.com/astral-sh/ruff/issues/9297.
2023-12-28 14:15:23 +00:00
Steve C
c716acc7a6
[refurb] Implement bit-count (FURB161) (#9265)
## Summary

Implements
[`FURB161`/`use-bit-count`](https://github.com/dosisod/refurb/blob/master/refurb/checks/builtin/use_bit_count.py)

See: #1348 

## Test Plan

`cargo test`
2023-12-27 15:32:51 +00:00
Shantanu
50187016cb
[refurb] Avoid false positives for math-constant (FURB152) (#9290)
Fixes #9281
2023-12-27 09:45:37 -05:00
Charlie Marsh
fa78d2d97c
Avoid adding return types to stub methods (#9277)
We should avoid adding `-> None` to stubs in `.pyi` files, along with a
few other cases. (We already ignore abstract methods.)

Closes https://github.com/astral-sh/ruff/issues/9270.
2023-12-25 09:03:24 -05:00
Charlie Marsh
6d0c9c4e95
Avoid asyncio-dangling-task for nonlocal and global bindings (#9263)
Closes https://github.com/astral-sh/ruff/issues/9262.
2023-12-23 21:33:50 +00:00
Charlie Marsh
20def33fb7
Remove special pre-visit for module docstrings (#9261)
This ensures that we visit the module docstring like any other string.

Closes https://github.com/astral-sh/ruff/issues/9260.
2023-12-23 10:03:12 -05:00
Charlie Marsh
cdea7d71a3
Fix scoping for generators in named expressions in classes (#9248)
Closes https://github.com/astral-sh/ruff/issues/9230.
2023-12-22 18:06:40 +00:00
Charlie Marsh
1e7bc1dffe
Wrap subscripted dicts in parens for f-string conversion (#9238)
Closes https://github.com/astral-sh/ruff/issues/9227.
2023-12-21 21:51:50 +00:00
Charlie Marsh
b0ae1199e8
Add a fix for never-union (#9218)
## Summary

Enables us to rewrite `Never | int` as `int`.
2023-12-21 21:01:09 +00:00
Charlie Marsh
a9ceef5b5d
[ruff] Add never-union rule to detect redundant typing.NoReturn and typing.Never (#9217)
## Summary

Adds a rule to detect unions that include `typing.NoReturn` or
`typing.Never`. In such cases, the use of the bottom type is redundant.

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

## Test Plan

`cargo test`
2023-12-21 20:53:31 +00:00
asafamr-mm
5d41c84ef7
SIM300: CONSTANT_CASE variables are improperly flagged for yoda violation (#9164)
## Summary

fixes #6956 
details in issue

Following an advice in
https://github.com/astral-sh/ruff/issues/6956#issuecomment-1817672585,
this change separates expressions to 3 levels of "constant likelihood":
*  literals, empty dict and tuples... (definitely constant, level 2)
*  CONSTANT_CASE vars (probably constant, 1)
* all other expressions (0)

a comparison is marked yoda if the level is strictly higher on its left
hand side

following
https://github.com/astral-sh/ruff/issues/6956#issuecomment-1697107822
marking compound expressions of literals (e.g. `60 * 60` ) as constants
this change current behaviour on
`SomeClass().settings.SOME_CONSTANT_VALUE > (60 * 60)` in the fixture
from error to ok
2023-12-20 20:26:07 +00:00
Charlie Marsh
cbe3bf9bde
Avoid asyncio-dangling-task violations on shadowed bindings (#9215)
## Summary

Ensures that we avoid flagging cases like:

```python
async def f(x: int):
    if x > 0:
        task = asyncio.create_task(make_request())
    else:
        task = asyncio.create_task(make_request())
    await task
```

Closes https://github.com/astral-sh/ruff/issues/9133.
2023-12-20 12:07:57 -05:00
Charlie Marsh
4b4160eb48
Allow removal of typing from exempt-modules (#9214)
## Summary

If you remove `typing` from `exempt-modules`, we tend to panic, since we
try to add `TYPE_CHECKING` to `from typing import ...` statements while
concurrently attempting to remove other members from that import. This
PR adds special-casing for typing imports to avoid such panics.

Closes https://github.com/astral-sh/ruff/issues/5331
Closes https://github.com/astral-sh/ruff/issues/9196.
Closes https://github.com/astral-sh/ruff/issues/9197.
2023-12-20 11:03:02 -05:00
Charlie Marsh
5ccc21aea2
Add support for NoReturn in auto-return-typing (#9206)
## Summary

Given a function like:

```python
def func(x: int):
    if not x:
        raise ValueError
    else:
        raise TypeError
```

We now correctly use `NoReturn` as the return type, rather than `None`.

Closes https://github.com/astral-sh/ruff/issues/9201.
2023-12-20 00:06:31 -05:00
Alex Waygood
bc0bf6f41c
[flake8-pyi] Expand PYI018 to cover ParamSpecs and TypeVarTuples (#9198)
## Summary

Part of #8771. flake8-pyi will emit a Y018 error for unused TypeVars,
ParamSpecs or TypeVarTuples; Ruff currently only emits PYI018 for unused
TypeVars.

This is my first "proper" Ruff PR -- let me know if there's a better way
of doing this! Not sure if the repeated calls to `match_typing_expr()`
are ideal.

## Test Plan

I manually updated the fixtures to add some unused ParamSpecs and
TypeVarTuples, and then updated the snapshots using `cargo insta
review`. All tests then passed when run using `cargo test`.
2023-12-20 03:10:07 +00:00
Steve C
7c894921df
[pylint] Implement too-many-locals (PLR0914) (#9163)
## Summary

Implements [`PLR0914` -
`too-many-locals`](https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/too-many-locals.html)

See #970 

## Test Plan

`cargo test`
2023-12-18 20:00:04 +00:00
Charlie Marsh
be8f8e62b5
Reverse order of arguments for operator.contains (#9192)
Closes https://github.com/astral-sh/ruff/issues/9191.
2023-12-18 14:39:52 -05:00
Charlie Marsh
0bf7683a3f
Avoid mutable-class-default violations for Pydantic subclasses (#9187)
Only applies to subclasses defined within the same file, as elsewhere.

See:
https://github.com/astral-sh/ruff/issues/5243#issuecomment-1860776975.
2023-12-18 11:19:07 -05:00
Tuomas Siipola
c532089fb3
Implement reimplemented_operator (FURB118) (#9171)
## Summary

Implement
[FURB118](https://github.com/dosisod/refurb/blob/master/docs/checks.md#furb118-use-operator)
that recommends, for example, that `lambda x, y: x + y` is replaced with
`operator.add`. Part of #1348.

## Test Plan

Added test cases.
2023-12-18 14:59:16 +00:00
Charlie Marsh
2643f74a5d
Iterate over lambdas in deferred type annotations (#9175)
Closes https://github.com/astral-sh/ruff/issues/9159.
2023-12-18 04:51:23 +00:00
Charlie Marsh
c944d23053
Avoid nested quotations in auto-quoting fix (#9168)
## Summary

Given `Callable[[Callable[_P, _R]], Callable[_P, _R]]` from the
originating issue, when quoting `Callable`, we quoted the inner
`[Callable[_P, _R]]`, and then created a separate edit for the outer
`Callable`. Since there's an extra level of nesting in the subscript,
the edit for `[Callable[_P, _R]]` correctly did _not_ expand to the
entire expression. However, in this case, we should discard the inner
edit, since the expression is getting quoted by the outer edit anyway.

Closes https://github.com/astral-sh/ruff/issues/9162.
2023-12-17 12:53:58 +00:00
Charlie Marsh
a336c1bc95
Add a rule to detect string members in runtime-evaluated unions (#9143)
## Summary

A common mistake is to add quotes around one member in an `X | Y`-style
type union, as in:

```python
contract_versions_list: list[ContractVersion] | 'QuerySet[ContractVersion]' | None = None
```

However, doing so will lead to a runtime error if the annotation is
runtime-evaluated. This PR lints against such patterns.

Closes https://github.com/astral-sh/ruff/issues/9139.
2023-12-16 21:22:06 +00:00
Steve C
85b27a994f
Fix dropped union expressions for piped non-types in PYI055 autofix (#9161)
## Summary

Fix dropped union expressions for piped non-types in `PYI055` autofix

If you had `type[int] | type[str] | str`, it would have dropped the
`str`, which breaks the type!

Closes #9156 

## Test Plan

`cargo test`
2023-12-16 15:58:28 -05:00
Charlie Marsh
6ecf844214
Add base-class inheritance detection to flake8-django rules (#9151)
## Summary

As elsewhere, this only applies to classes defined within the same file.

Closes https://github.com/astral-sh/ruff/issues/9150.
2023-12-15 18:01:32 +00:00
konsti
82731b8194
Fix panic in D208 with multibyte indent (#9147)
Fix #9080

Example, where `[]` is a 2 byte non-breaking space:
```
def f():
    """ Docstring header
^^^^ Real indentation is 4 chars
      docstring body, over-indented
^^^^^^ Over-indentation is 6 - 4 = 2 chars due to this line
   [] []  docstring body 2, further indented
^^^^^ We take these 4 chars/5 bytes to match the docstring ...
     ^^^ ... and these 2 chars/3 bytes to remove the `over_indented_size` ...
        ^^ ... but preserve this real indent
```
2023-12-15 12:02:15 -05:00
Charlie Marsh
d1a7bc38ff
Enable annotation quoting for multi-line expressions (#9142)
Given:

```python
x: DataFrame[
    int
] = 1
```

We currently wrap the annotation in single quotes, which leads to a
syntax error:

```python
x: "DataFrame[
    int
]" = 1
```

There are a few options for what to suggest for users here... Use triple
quotes:

```python
x: """DataFrame[
    int
]""" = 1
```

Or, use an implicit string concatenation (which may require
parentheses):

```python
x: ("DataFrame["
    "int"
"]") = 1
```

The solution I settled on here is to use the `Generator`, which
effectively means we write it out on a single line, like:

```python
x: "DataFrame[int]" = 1
```

It's kind of the "least opinionated" solution, but it does mean we'll
expand to a very long line in some cases.

Closes https://github.com/astral-sh/ruff/issues/9136.
2023-12-15 01:03:09 +00:00
Charlie Marsh
6c224cec52
Deduplicate edits when quoting annotations (#9140)
If you have multiple sub-expressions that need to be quoted, we'll
generate the same edit twice.

Closes https://github.com/astral-sh/ruff/issues/9135.
2023-12-14 19:46:35 +00:00
Charlie Marsh
1a65e544c5
Allow flake8-type-checking rules to automatically quote runtime-evaluated references (#6001)
## Summary

This allows us to fix usages like:

```python
from pandas import DataFrame

def baz() -> DataFrame:
    ...
```

By quoting the `DataFrame` in `-> DataFrame`. Without quotes, moving
`from pandas import DataFrame` into an `if TYPE_CHECKING:` block will
fail at runtime, since Python tries to evaluate the annotation to add it
to the function's `__annotations__`.

Unfortunately, this does require us to split our "annotation kind" flags
into three categories, rather than two:

- `typing-only`: The annotation is only evaluated at type-checking-time.
- `runtime-evaluated`: Python will evaluate the annotation at runtime
(like above) -- but we're willing to quote it.
- `runtime-required`: Python will evaluate the annotation at runtime
(like above), and some library (like Pydantic) needs it to be available
at runtime, so we _can't_ quote it.

This functionality is gated behind a setting
(`flake8-type-checking.quote-annotations`).

Closes https://github.com/astral-sh/ruff/issues/5559.
2023-12-13 03:12:38 +00:00
Charlie Marsh
4d2ee5bf98
Add named expression handling to find_assigned_value (#9109) 2023-12-12 20:07:33 -05:00
qdegraaf
8314c8bb05
[typing] Add find_assigned_value helper func to typing.rs to retrieve value of a given variable id (#8583)
## Summary

Adds `find_assigned_value` a function which gets the `&Expr` assigned to
a given `id` if one exists in the semantic model.

Open TODOs:

- [ ] Handle `binding.kind.is_unpacked_assignment()`: I am bit confused
by this one. The snippet from its documentation does not appear to be
counted as an unpacked assignment and the only ones I could find for
which that was true were invalid Python like:
```python
x, y = 1 
```
- [ ] How to handle AugAssign. Can we combine statements like:
```python
(a, b) = [(1, 2, 3), (4,)]
a += (6, 7)
```
to get the full value for a? Code currently just returns `None` for
these assign types

- [ ] Multi target assigns
```python
m_c = (m_d, m_e) = (0, 0)
trio.sleep(m_c)  # OK
trio.sleep(m_d)  # TRIO115
trio.sleep(m_e)  # TRIO115
```

## Test Plan

Used the function in two rules:

- `TRIO115`
- `PERF101`

Expanded both their fixtures for explicit multi target check
2023-12-13 00:24:47 +00:00
T-256
cb201bc4a5
PIE804: Prevent keyword arguments duplication (#8450) 2023-12-12 23:19:55 +00:00
Shantanu
cb8eea64a8
[pylint] Add fix for subprocess-run-without-check (PLW1510) (#6708) 2023-12-12 05:08:17 +00:00
Tuomas Siipola
a53d59f6bd
Support floating-point base in FURB163 (#9100)
<!--
Thank you for contributing to Ruff! To help us out with reviewing,
please consider the following:

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

## Summary

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

Check floating-point numbers similarly to integers in FURB163. For
example, both `math.log(x, 10)` and `math.log(x, 10.0)` should be
changed to `math.log10(x)`.

## Test Plan

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

Added couple of test cases.
2023-12-11 15:47:37 -05:00
Samuel Cormier-Iijima
1026ece946
E274: allow tab indentation before keyword (#9099)
## Summary

E274 currently flags any keyword at the start of a line indented with
tabs. This turns out to be due to a bug in `Whitespace::trailing` that
never considers any whitespace containing a tab as indentation.

## Test Plan

Added a simple test case.
2023-12-11 15:25:31 -05:00
Charlie Marsh
f452bf8cad
Allow matplotlib.use calls to intersperse imports (#9094)
This PR allows `matplotlib.use` calls to intersperse imports without
triggering `E402`. This is a pragmatic choice as it's common to require
`matplotlib.use` calls prior to importing from within `matplotlib`
itself.

Closes https://github.com/astral-sh/ruff/issues/9091.
2023-12-11 17:06:25 +00:00
Simon Brugman
6e36dcfefe
[refurb] Implement hashlib-digest-hex (FURB181) (#9077)
## Summary

Implementation of  Refurb FURB181
Part of https://github.com/astral-sh/ruff/issues/1348

## Test Plan

Test cases from Refurb
2023-12-10 02:00:11 +00:00
asafamr-mm
6c2613b44e
Detect unused-asyncio-dangling-task (RUF006) on unused assignments (#9060)
## Summary

Fixes #8863 : Detect asyncio-dangling-task (RUF006) when discarding
return value

## Test Plan

added new two testcases, changed result of an old one that was made more
specific
2023-12-09 21:10:38 +00:00
Charlie Marsh
cb8a2f5615
Add fix for comment-related whitespace rules (#9075)
Closes https://github.com/astral-sh/ruff/issues/9067.

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

Closes https://github.com/astral-sh/ruff/issues/8119.
2023-12-09 15:18:07 -05:00
Charlie Marsh
20e33bf514
Allow class names when apps.get_model is a non-string (#9065)
See:
https://github.com/astral-sh/ruff/issues/7675#issuecomment-1848206022
2023-12-08 22:59:05 -05:00
Dhruv Manilawala
b7dd2b5941
Allow EM fixes even if msg variable is defined (#9059)
This PR updates the `EM` rules to generate the auto-fix even if the
`msg` variable is defined in the current scope.

As discussed in https://github.com/astral-sh/ruff/issues/9052.
2023-12-08 15:16:15 -06:00
Charlie Marsh
e043bd46b5
Make math-constant rule more targeted (#9054)
## Summary

We now only flag `math.pi` if the value is in `[3.14, 3.15)`, and apply
similar rules to the other constants.

Closes https://github.com/astral-sh/ruff/issues/9049.
2023-12-08 12:42:18 -05:00
Charlie Marsh
b021ede481
Allow sys.path modifications between imports (#9047)
## Summary

It's common to interleave a `sys.path` modification between imports at
the top of a file. This is a frequent cause of `# noqa: E402` false
positives, as seen in the ecosystem checks. This PR modifies E402 to
omit such modifications when determining the "import boundary".

(We could consider linting against `sys.path` modifications, but that
should be a separate rule.)

Closes: https://github.com/astral-sh/ruff/issues/5557.
2023-12-07 13:35:55 -05:00
Dhruv Manilawala
cdac90ef68
New AST nodes for f-string elements (#8835)
Rebase of #6365 authored by @davidszotten.

## Summary

This PR updates the AST structure for an f-string elements.

The main **motivation** behind this change is to have a dedicated node
for the string part of an f-string. Previously, the existing
`ExprStringLiteral` node was used for this purpose which isn't exactly
correct. The `ExprStringLiteral` node should include the quotes as well
in the range but the f-string literal element doesn't include the quote
as it's a specific part within an f-string. For example,

```python
f"foo {x}"
# ^^^^
# This is the literal part of an f-string
```

The introduction of `FStringElement` enum is helpful which represent
either the literal part or the expression part of an f-string.

### Rule Updates

This means that there'll be two nodes representing a string depending on
the context. One for a normal string literal while the other is a string
literal within an f-string. The AST checker is updated to accommodate
this change. The rules which work on string literal are updated to check
on the literal part of f-string as well.

#### Notes

1. The `Expr::is_literal_expr` method would check for
`ExprStringLiteral` and return true if so. But now that we don't
represent the literal part of an f-string using that node, this improves
the method's behavior and confines to the actual expression. We do have
the `FStringElement::is_literal` method.
2. We avoid checking if we're in a f-string context before adding to
`string_type_definitions` because the f-string literal is now a
dedicated node and not part of `Expr`.
3. Annotations cannot use f-string so we avoid changing any rules which
work on annotation and checks for `ExprStringLiteral`.

## Test Plan

- All references of `Expr::StringLiteral` were checked to see if any of
the rules require updating to account for the f-string literal element
node.
- New test cases are added for rules which check against the literal
part of an f-string.
- Check the ecosystem results and ensure it remains unchanged.

## Performance

There's a performance penalty in the parser. The reason for this remains
unknown as it seems that the generated assembly code is now different
for the `__reduce154` function. The reduce function body is just popping
the `ParenthesizedExpr` on top of the stack and pushing it with the new
location.

- The size of `FStringElement` enum is the same as `Expr` which is what
it replaces in `FString::format_spec`
- The size of `FStringExpressionElement` is the same as
`ExprFormattedValue` which is what it replaces

I tried reducing the `Expr` enum from 80 bytes to 72 bytes but it hardly
resulted in any performance gain. The difference can be seen here:
- Original profile: https://share.firefox.dev/3Taa7ES
- Profile after boxing some node fields:
https://share.firefox.dev/3GsNXpD

### Backtracking

I tried backtracking the changes to see if any of the isolated change
produced this regression. The problem here is that the overall change is
so small that there's only a single checkpoint where I can backtrack and
that checkpoint results in the same regression. This checkpoint is to
revert using `Expr` to the `FString::format_spec` field. After this
point, the change would revert back to the original implementation.

## Review process

The review process is similar to #7927. The first set of commits update
the node structure, parser, and related AST files. Then, further commits
update the linter and formatter part to account for the AST change.

---------

Co-authored-by: David Szotten <davidszotten@gmail.com>
2023-12-07 10:28:05 -06:00