Merge branch 'main' into 241a1really

This commit is contained in:
Jelle Zijlstra 2023-12-09 19:35:14 -08:00 committed by GitHub
commit 337a85df22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 436 additions and 285 deletions

View File

@ -11,11 +11,7 @@ jobs:
github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.event_name == 'push' || github.event.pull_request.head.repo.full_name !=
github.repository github.repository
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -73,7 +73,7 @@ jobs:
| pyp 'json.dumps({"only": x, "os": "ubuntu-latest"})' | pyp 'json.dumps({"only": x, "os": "ubuntu-latest"})'
} | pyp 'json.dumps(list(map(json.loads, lines)))' > /tmp/matrix } | pyp 'json.dumps(list(map(json.loads, lines)))' > /tmp/matrix
env: env:
CIBW_BUILD: "cp38-* cp311-*" CIBW_BUILD: "cp38-* cp312-*"
CIBW_ARCHS_LINUX: x86_64 CIBW_ARCHS_LINUX: x86_64
- id: set-matrix - id: set-matrix
run: echo "include=$(cat /tmp/matrix)" | tee -a $GITHUB_OUTPUT run: echo "include=$(cat /tmp/matrix)" | tee -a $GITHUB_OUTPUT

View File

@ -39,7 +39,7 @@ repos:
exclude: ^src/blib2to3/ exclude: ^src/blib2to3/
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.6.1 rev: v1.7.1
hooks: hooks:
- id: mypy - id: mypy
exclude: ^docs/conf.py exclude: ^docs/conf.py

View File

@ -8,15 +8,18 @@
### Stable style ### Stable style
<!-- Changes that affect Black's stable style --> - Fix bug where `# fmt: off` automatically dedents when used with the `--line-ranges`
option, even when it is not within the specified line range. (#4084)
### Preview style ### Preview style
- Prefer more equal signs before a break when splitting chained assignments (#4010)
- Standalone form feed characters at the module level are no longer removed (#4021) - Standalone form feed characters at the module level are no longer removed (#4021)
- Additional cases of immediately nested tuples, lists, and dictionaries are now - Additional cases of immediately nested tuples, lists, and dictionaries are now
indented less (#4012) indented less (#4012)
- Allow empty lines at the beginning of all blocks, except immediately before a - Allow empty lines at the beginning of all blocks, except immediately before a
docstring (#4060) docstring (#4060)
- Fix crash in preview mode when using a short `--line-length` (#4086)
### Configuration ### Configuration
@ -27,7 +30,8 @@
### Packaging ### Packaging
- Upgrade to mypy 1.6.1 (#4049) - Upgrade to mypy 1.7.1 (#4049) (#4069)
- Faster compiled wheels are now available for CPython 3.12 (#4070)
### Parser ### Parser

View File

@ -8,18 +8,9 @@ deliberately limited and rarely added. Previous formatting is taken into account
little as possible, with rare exceptions like the magic trailing comma. The coding style little as possible, with rare exceptions like the magic trailing comma. The coding style
used by _Black_ can be viewed as a strict subset of PEP 8. used by _Black_ can be viewed as a strict subset of PEP 8.
_Black_ reformats entire files in place. It doesn't reformat lines that contain This document describes the current formatting style. If you're interested in trying out
`# fmt: skip` or blocks that start with `# fmt: off` and end with `# fmt: on`. where the style is heading, see [future style](./future_style.md) and try running
`# fmt: skip` can be mixed with other pragmas/comments either with multiple comments `black --preview`.
(e.g. `# fmt: skip # pylint # noqa`) or as a semicolon separated list (e.g.
`# fmt: skip; pylint; noqa`). `# fmt: on/off` must be on the same level of indentation
and in the same block, meaning no unindents beyond the initial indentation level between
them. It also recognizes [YAPF](https://github.com/google/yapf)'s block comments to the
same effect, as a courtesy for straddling code.
The rest of this document describes the current formatting style. If you're interested
in trying out where the style is heading, see [future style](./future_style.md) and try
running `black --preview`.
### How _Black_ wraps lines ### How _Black_ wraps lines

View File

@ -12,7 +12,8 @@ _Black_ is a well-behaved Unix-style command-line tool:
## Usage ## Usage
To get started right away with sensible defaults: _Black_ will reformat entire files in place. To get started right away with sensible
defaults:
```sh ```sh
black {source_file_or_directory} black {source_file_or_directory}
@ -24,6 +25,17 @@ You can run _Black_ as a package if running it as a script doesn't work:
python -m black {source_file_or_directory} python -m black {source_file_or_directory}
``` ```
### Ignoring sections
Black will not reformat lines that contain `# fmt: skip` or blocks that start with
`# fmt: off` and end with `# fmt: on`. `# fmt: skip` can be mixed with other
pragmas/comments either with multiple comments (e.g. `# fmt: skip # pylint # noqa`) or
as a semicolon separated list (e.g. `# fmt: skip; pylint; noqa`). `# fmt: on/off` must
be on the same level of indentation and in the same block, meaning no unindents beyond
the initial indentation level between them. Black also recognizes
[YAPF](https://github.com/google/yapf)'s block comments to the same effect, as a
courtesy for straddling code.
### Command line options ### Command line options
The CLI options of _Black_ can be displayed by running `black --help`. All options are The CLI options of _Black_ can be displayed by running `black --help`. All options are
@ -35,6 +47,10 @@ are deliberately limited and rarely added.
Note that all command-line options listed above can also be configured using a Note that all command-line options listed above can also be configured using a
`pyproject.toml` file (more on that below). `pyproject.toml` file (more on that below).
#### `-h`, `--help`
Show available command-line options and exit.
#### `-c`, `--code` #### `-c`, `--code`
Format the code passed in as a string. Format the code passed in as a string.
@ -109,6 +125,10 @@ useful when piping source on standard input.
When processing Jupyter Notebooks, add the given magic to the list of known python- When processing Jupyter Notebooks, add the given magic to the list of known python-
magics. Useful for formatting cells with custom python magics. magics. Useful for formatting cells with custom python magics.
#### `-x, --skip-source-first-line`
Skip the first line of the source code.
#### `-S, --skip-string-normalization` #### `-S, --skip-string-normalization`
By default, _Black_ uses double quotes for all strings and normalizes string prefixes, By default, _Black_ uses double quotes for all strings and normalizes string prefixes,
@ -132,7 +152,7 @@ functionality in the next major release. Read more about
#### `--check` #### `--check`
Passing `--check` will make _Black_ exit with: Don't write the files back, just return the status. _Black_ will exit with:
- code 0 if nothing would change; - code 0 if nothing would change;
- code 1 if some files would be reformatted; or - code 1 if some files would be reformatted; or
@ -162,8 +182,8 @@ $ echo $?
#### `--diff` #### `--diff`
Passing `--diff` will make _Black_ print out diffs that indicate what changes _Black_ Don't write the files back, just output a diff to indicate what changes _Black_ would've
would've made. They are printed to stdout so capturing them is simple. made. They are printed to stdout so capturing them is simple.
If you'd like colored diffs, you can enable them with `--color`. If you'd like colored diffs, you can enable them with `--color`.
@ -179,7 +199,11 @@ All done! ✨ 🍰 ✨
1 file would be reformatted. 1 file would be reformatted.
``` ```
### `--line-ranges` #### `--color` / `--no-color`
Show (or do not show) colored diff. Only applies when `--diff` is given.
#### `--line-ranges`
When specified, _Black_ will try its best to only format these lines. When specified, _Black_ will try its best to only format these lines.
@ -202,10 +226,6 @@ extra lines outside of the ranges when ther are unformatted lines with the exact
content. It also disables _Black_'s formatting stability check in `--safe` mode. content. It also disables _Black_'s formatting stability check in `--safe` mode.
``` ```
#### `--color` / `--no-color`
Show (or do not show) colored diff. Only applies when `--diff` is given.
#### `--fast` / `--safe` #### `--fast` / `--safe`
By default, _Black_ performs [an AST safety check](labels/ast-changes) after formatting By default, _Black_ performs [an AST safety check](labels/ast-changes) after formatting
@ -241,29 +261,22 @@ Because of our [stability policy](../the_black_code_style/index.md), this will g
stable formatting, but still allow you to take advantage of improvements that do not stable formatting, but still allow you to take advantage of improvements that do not
affect formatting. affect formatting.
#### `--include`
A regular expression that matches files and directories that should be included on
recursive searches. An empty value means all files are included regardless of the name.
Use forward slashes for directories on all platforms (Windows, too). Exclusions are
calculated first, inclusions later.
#### `--exclude` #### `--exclude`
A regular expression that matches files and directories that should be excluded on A regular expression that matches files and directories that should be excluded on
recursive searches. An empty value means no paths are excluded. Use forward slashes for recursive searches. An empty value means no paths are excluded. Use forward slashes for
directories on all platforms (Windows, too). Exclusions are calculated first, inclusions directories on all platforms (Windows, too). By default, Black also ignores all paths
later. listed in `.gitignore`. Changing this value will override all default exclusions.
#### `--extend-exclude` #### `--extend-exclude`
Like `--exclude`, but adds additional files and directories on top of the excluded ones. Like `--exclude`, but adds additional files and directories on top of the default values
Useful if you simply want to add to the default. instead of overriding them.
#### `--force-exclude` #### `--force-exclude`
Like `--exclude`, but files and directories matching this regex will be excluded even Like `--exclude`, but files and directories matching this regex will be excluded even
when they are passed explicitly as arguments. This is useful when invoking _Black_ when they are passed explicitly as arguments. This is useful when invoking Black
programmatically on changed files, such as in a pre-commit hook or editor plugin. programmatically on changed files, such as in a pre-commit hook or editor plugin.
#### `--stdin-filename` #### `--stdin-filename`
@ -271,16 +284,23 @@ programmatically on changed files, such as in a pre-commit hook or editor plugin
The name of the file when passing it through stdin. Useful to make sure Black will The name of the file when passing it through stdin. Useful to make sure Black will
respect the `--force-exclude` option on some editors that rely on using stdin. respect the `--force-exclude` option on some editors that rely on using stdin.
#### `--include`
A regular expression that matches files and directories that should be included on
recursive searches. An empty value means all files are included regardless of the name.
Use forward slashes for directories on all platforms (Windows, too). Overrides all
exclusions, including from `.gitignore` and command line options.
#### `-W`, `--workers` #### `-W`, `--workers`
When _Black_ formats multiple files, it may use a process pool to speed up formatting. When _Black_ formats multiple files, it may use a process pool to speed up formatting.
This option controls the number of parallel workers. This can also be specified via the This option controls the number of parallel workers. This can also be specified via the
`BLACK_NUM_WORKERS` environment variable. `BLACK_NUM_WORKERS` environment variable. Defaults to the number of CPUs in the system.
#### `-q`, `--quiet` #### `-q`, `--quiet`
Passing `-q` / `--quiet` will cause _Black_ to stop emitting all non-critical output. Stop emitting all non-critical output. Error messages will still be emitted (which can
Error messages will still be emitted (which can silenced by `2>/dev/null`). silenced by `2>/dev/null`).
```console ```console
$ black src/ -q $ black src/ -q
@ -289,9 +309,9 @@ error: cannot format src/black_primer/cli.py: Cannot parse: 5:6: mport asyncio
#### `-v`, `--verbose` #### `-v`, `--verbose`
Passing `-v` / `--verbose` will cause _Black_ to also emit messages about files that Emit messages about files that were not changed or were ignored due to exclusion
were not changed or were ignored due to exclusion patterns. If _Black_ is using a patterns. If _Black_ is using a configuration file, a message detailing which one it is
configuration file, a blue message detailing which one it is using will be emitted. using will be emitted.
```console ```console
$ black src/ -v $ black src/ -v
@ -321,10 +341,6 @@ black, 23.11.0
Read configuration options from a configuration file. See Read configuration options from a configuration file. See
[below](#configuration-via-a-file) for more details on the configuration file. [below](#configuration-via-a-file) for more details on the configuration file.
#### `-h`, `--help`
Show available command-line options and exit.
### Environment variable options ### Environment variable options
_Black_ supports the following configuration via environment variables. _Black_ supports the following configuration via environment variables.
@ -355,7 +371,7 @@ All done! ✨ 🍰 ✨
use `--stdin-filename`. Useful to make sure _Black_ will respect the `--force-exclude` use `--stdin-filename`. Useful to make sure _Black_ will respect the `--force-exclude`
option on some editors that rely on using stdin. option on some editors that rely on using stdin.
You can also pass code as a string using the `-c` / `--code` option. You can also pass code as a string using the `--code` option.
### Writeback and reporting ### Writeback and reporting
@ -435,8 +451,7 @@ refers to the path to your home directory. On Windows, this will be something li
You can also explicitly specify the path to a particular file that you want with You can also explicitly specify the path to a particular file that you want with
`--config`. In this situation _Black_ will not look for any other file. `--config`. In this situation _Black_ will not look for any other file.
If you're running with `--verbose`, you will see a blue message if a file was found and If you're running with `--verbose`, you will see a message if a file was found and used.
used.
Please note `blackd` will not use `pyproject.toml` configuration. Please note `blackd` will not use `pyproject.toml` configuration.

View File

@ -121,7 +121,7 @@ macos-max-compat = true
enable-by-default = false enable-by-default = false
dependencies = [ dependencies = [
"hatch-mypyc>=0.16.0", "hatch-mypyc>=0.16.0",
"mypy==1.6.1", "mypy==1.7.1",
"click==8.1.3", # avoid https://github.com/pallets/click/issues/2558 "click==8.1.3", # avoid https://github.com/pallets/click/issues/2558
] ]
require-runtime-dependencies = true require-runtime-dependencies = true
@ -150,7 +150,7 @@ build-verbosity = 1
# - Architecture (64-bit only): amd64 / x86_64, universal2, and arm64 # - Architecture (64-bit only): amd64 / x86_64, universal2, and arm64
# - OS: Linux (no musl), Windows, and macOS # - OS: Linux (no musl), Windows, and macOS
build = "cp3*" build = "cp3*"
skip = ["*-manylinux_i686", "*-musllinux_*", "*-win32", "pp*", "cp312-*"] skip = ["*-manylinux_i686", "*-musllinux_*", "*-win32", "pp*"]
# This is the bare minimum needed to run the test suite. Pulling in the full # This is the bare minimum needed to run the test suite. Pulling in the full
# test_requirements.txt would download a bunch of other packages not necessary # test_requirements.txt would download a bunch of other packages not necessary
# here and would slow down the testing step a fair bit. # here and would slow down the testing step a fair bit.
@ -187,7 +187,7 @@ CC = "clang"
build-frontend = { name = "build", args = ["--no-isolation"] } build-frontend = { name = "build", args = ["--no-isolation"] }
# Unfortunately, hatch doesn't respect MACOSX_DEPLOYMENT_TARGET # Unfortunately, hatch doesn't respect MACOSX_DEPLOYMENT_TARGET
before-build = [ before-build = [
"python -m pip install 'hatchling==1.18.0' hatch-vcs hatch-fancy-pypi-readme 'hatch-mypyc>=0.16.0' 'mypy==1.6.1' 'click==8.1.3'", "python -m pip install 'hatchling==1.18.0' hatch-vcs hatch-fancy-pypi-readme 'hatch-mypyc>=0.16.0' 'mypy==1.7.1' 'click==8.1.3'",
"""sed -i '' -e "600,700s/'10_16'/os.environ['MACOSX_DEPLOYMENT_TARGET'].replace('.', '_')/" $(python -c 'import hatchling.builders.wheel as h; print(h.__file__)') """, """sed -i '' -e "600,700s/'10_16'/os.environ['MACOSX_DEPLOYMENT_TARGET'].replace('.', '_')/" $(python -c 'import hatchling.builders.wheel as h; print(h.__file__)') """,
] ]

View File

@ -235,25 +235,26 @@ def validate_regex(
callback=target_version_option_callback, callback=target_version_option_callback,
multiple=True, multiple=True,
help=( help=(
"Python versions that should be supported by Black's output. By default, Black" "Python versions that should be supported by Black's output. You should"
" will try to infer this from the project metadata in pyproject.toml. If this" " include all versions that your code supports. By default, Black will infer"
" does not yield conclusive results, Black will use per-file auto-detection." " target versions from the project metadata in pyproject.toml. If this does"
" not yield conclusive results, Black will use per-file auto-detection."
), ),
) )
@click.option( @click.option(
"--pyi", "--pyi",
is_flag=True, is_flag=True,
help=( help=(
"Format all input files like typing stubs regardless of file extension (useful" "Format all input files like typing stubs regardless of file extension. This"
" when piping source on standard input)." " is useful when piping source on standard input."
), ),
) )
@click.option( @click.option(
"--ipynb", "--ipynb",
is_flag=True, is_flag=True,
help=( help=(
"Format all input files like Jupyter Notebooks regardless of file extension " "Format all input files like Jupyter Notebooks regardless of file extension."
"(useful when piping source on standard input)." "This is useful when piping source on standard input."
), ),
) )
@click.option( @click.option(
@ -310,14 +311,22 @@ def validate_regex(
@click.option( @click.option(
"--diff", "--diff",
is_flag=True, is_flag=True,
help="Don't write the files back, just output a diff for each file on stdout.", help=(
"Don't write the files back, just output a diff to indicate what changes"
" Black would've made. They are printed to stdout so capturing them is simple."
),
)
@click.option(
"--color/--no-color",
is_flag=True,
help="Show (or do not show) colored diff. Only applies when --diff is given.",
) )
@click.option( @click.option(
"--line-ranges", "--line-ranges",
multiple=True, multiple=True,
metavar="START-END", metavar="START-END",
help=( help=(
"When specified, _Black_ will try its best to only format these lines. This" "When specified, Black will try its best to only format these lines. This"
" option can be specified multiple times, and a union of the lines will be" " option can be specified multiple times, and a union of the lines will be"
" formatted. Each range must be specified as two integers connected by a `-`:" " formatted. Each range must be specified as two integers connected by a `-`:"
" `<START>-<END>`. The `<START>` and `<END>` integer indices are 1-based and" " `<START>-<END>`. The `<START>` and `<END>` integer indices are 1-based and"
@ -325,23 +334,67 @@ def validate_regex(
), ),
default=(), default=(),
) )
@click.option(
"--color/--no-color",
is_flag=True,
help="Show colored diff. Only applies when `--diff` is given.",
)
@click.option( @click.option(
"--fast/--safe", "--fast/--safe",
is_flag=True, is_flag=True,
help="If --fast given, skip temporary sanity checks. [default: --safe]", help=(
"By default, Black performs an AST safety check after formatting your code."
" The --fast flag turns off this check and the --safe flag explicitly enables"
" it. [default: --safe]"
),
) )
@click.option( @click.option(
"--required-version", "--required-version",
type=str, type=str,
help=( help=(
"Require a specific version of Black to be running (useful for unifying results" "Require a specific version of Black to be running. This is useful for"
" across many environments e.g. with a pyproject.toml file). It can be" " ensuring that all contributors to your project are using the same"
" either a major version number or an exact version." " version, because different versions of Black may format code a little"
" differently. This option can be set in a configuration file for consistent"
" results across environments."
),
)
@click.option(
"--exclude",
type=str,
callback=validate_regex,
help=(
"A regular expression that matches files and directories that should be"
" excluded on recursive searches. An empty value means no paths are excluded."
" Use forward slashes for directories on all platforms (Windows, too)."
" By default, Black also ignores all paths listed in .gitignore. Changing this"
f" value will override all default exclusions. [default: {DEFAULT_EXCLUDES}]"
),
show_default=False,
)
@click.option(
"--extend-exclude",
type=str,
callback=validate_regex,
help=(
"Like --exclude, but adds additional files and directories on top of the"
" default values instead of overriding them."
),
)
@click.option(
"--force-exclude",
type=str,
callback=validate_regex,
help=(
"Like --exclude, but files and directories matching this regex will be excluded"
" even when they are passed explicitly as arguments. This is useful when"
" invoking Black programmatically on changed files, such as in a pre-commit"
" hook or editor plugin."
),
)
@click.option(
"--stdin-filename",
type=str,
is_eager=True,
help=(
"The name of the file when passing it through stdin. Useful to make sure Black"
" will respect the --force-exclude option on some editors that rely on using"
" stdin."
), ),
) )
@click.option( @click.option(
@ -353,59 +406,21 @@ def validate_regex(
"A regular expression that matches files and directories that should be" "A regular expression that matches files and directories that should be"
" included on recursive searches. An empty value means all files are included" " included on recursive searches. An empty value means all files are included"
" regardless of the name. Use forward slashes for directories on all platforms" " regardless of the name. Use forward slashes for directories on all platforms"
" (Windows, too). Exclusions are calculated first, inclusions later." " (Windows, too). Overrides all exclusions, including from .gitignore and"
" command line options."
), ),
show_default=True, show_default=True,
) )
@click.option(
"--exclude",
type=str,
callback=validate_regex,
help=(
"A regular expression that matches files and directories that should be"
" excluded on recursive searches. An empty value means no paths are excluded."
" Use forward slashes for directories on all platforms (Windows, too)."
" Exclusions are calculated first, inclusions later. [default:"
f" {DEFAULT_EXCLUDES}]"
),
show_default=False,
)
@click.option(
"--extend-exclude",
type=str,
callback=validate_regex,
help=(
"Like --exclude, but adds additional files and directories on top of the"
" excluded ones. (Useful if you simply want to add to the default)"
),
)
@click.option(
"--force-exclude",
type=str,
callback=validate_regex,
help=(
"Like --exclude, but files and directories matching this regex will be "
"excluded even when they are passed explicitly as arguments."
),
)
@click.option(
"--stdin-filename",
type=str,
is_eager=True,
help=(
"The name of the file when passing it through stdin. Useful to make "
"sure Black will respect --force-exclude option on some "
"editors that rely on using stdin."
),
)
@click.option( @click.option(
"-W", "-W",
"--workers", "--workers",
type=click.IntRange(min=1), type=click.IntRange(min=1),
default=None, default=None,
help=( help=(
"Number of parallel workers [default: BLACK_NUM_WORKERS environment variable " "When Black formats multiple files, it may use a process pool to speed up"
"or number of CPUs in the system]" " formatting. This option controls the number of parallel workers. This can"
" also be specified via the BLACK_NUM_WORKERS environment variable. Defaults"
" to the number of CPUs in the system."
), ),
) )
@click.option( @click.option(
@ -413,8 +428,8 @@ def validate_regex(
"--quiet", "--quiet",
is_flag=True, is_flag=True,
help=( help=(
"Don't emit non-error messages to stderr. Errors are still emitted; silence" "Stop emitting all non-critical output. Error messages will still be emitted"
" those with 2>/dev/null." " (which can silenced by 2>/dev/null)."
), ),
) )
@click.option( @click.option(
@ -422,8 +437,9 @@ def validate_regex(
"--verbose", "--verbose",
is_flag=True, is_flag=True,
help=( help=(
"Also emit messages to stderr about files that were not changed or were ignored" "Emit messages about files that were not changed or were ignored due to"
" due to exclusion patterns." " exclusion patterns. If Black is using a configuration file, a message"
" detailing which one it is using will be emitted."
), ),
) )
@click.version_option( @click.version_option(
@ -454,7 +470,7 @@ def validate_regex(
), ),
is_eager=True, is_eager=True,
callback=read_pyproject_toml, callback=read_pyproject_toml,
help="Read configuration from FILE path.", help="Read configuration options from a configuration file.",
) )
@click.pass_context @click.pass_context
def main( # noqa: C901 def main( # noqa: C901
@ -1180,7 +1196,7 @@ def _format_str_once(
for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS} for feature in {Feature.PARENTHESIZED_CONTEXT_MANAGERS}
if supports_feature(versions, feature) if supports_feature(versions, feature)
} }
normalize_fmt_off(src_node, mode) normalize_fmt_off(src_node, mode, lines)
if lines: if lines:
# This should be called after normalize_fmt_off. # This should be called after normalize_fmt_off.
convert_unchanged_lines(src_node, lines) convert_unchanged_lines(src_node, lines)

View File

@ -1,7 +1,7 @@
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from functools import lru_cache from functools import lru_cache
from typing import Final, Iterator, List, Optional, Union from typing import Collection, Final, Iterator, List, Optional, Tuple, Union
from black.mode import Mode, Preview from black.mode import Mode, Preview
from black.nodes import ( from black.nodes import (
@ -161,14 +161,18 @@ def make_comment(content: str) -> str:
return "#" + content return "#" + content
def normalize_fmt_off(node: Node, mode: Mode) -> None: def normalize_fmt_off(
node: Node, mode: Mode, lines: Collection[Tuple[int, int]]
) -> None:
"""Convert content between `# fmt: off`/`# fmt: on` into standalone comments.""" """Convert content between `# fmt: off`/`# fmt: on` into standalone comments."""
try_again = True try_again = True
while try_again: while try_again:
try_again = convert_one_fmt_off_pair(node, mode) try_again = convert_one_fmt_off_pair(node, mode, lines)
def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool: def convert_one_fmt_off_pair(
node: Node, mode: Mode, lines: Collection[Tuple[int, int]]
) -> bool:
"""Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment. """Convert content of a single `# fmt: off`/`# fmt: on` into a standalone comment.
Returns True if a pair was converted. Returns True if a pair was converted.
@ -213,7 +217,18 @@ def convert_one_fmt_off_pair(node: Node, mode: Mode) -> bool:
prefix[:previous_consumed] + "\n" * comment.newlines prefix[:previous_consumed] + "\n" * comment.newlines
) )
hidden_value = "".join(str(n) for n in ignored_nodes) hidden_value = "".join(str(n) for n in ignored_nodes)
comment_lineno = leaf.lineno - comment.newlines
if comment.value in FMT_OFF: if comment.value in FMT_OFF:
fmt_off_prefix = ""
if len(lines) > 0 and not any(
comment_lineno >= line[0] and comment_lineno <= line[1]
for line in lines
):
# keeping indentation of comment by preserving original whitespaces.
fmt_off_prefix = prefix.split(comment.value)[0]
if "\n" in fmt_off_prefix:
fmt_off_prefix = fmt_off_prefix.split("\n")[-1]
standalone_comment_prefix += fmt_off_prefix
hidden_value = comment.value + "\n" + hidden_value hidden_value = comment.value + "\n" + hidden_value
if _contains_fmt_skip_comment(comment.value, mode): if _contains_fmt_skip_comment(comment.value, mode):
hidden_value += " " + comment.value hidden_value += " " + comment.value

View File

@ -716,7 +716,7 @@ def left_hand_split(
if leaf.type in OPENING_BRACKETS: if leaf.type in OPENING_BRACKETS:
matching_bracket = leaf matching_bracket = leaf
current_leaves = body_leaves current_leaves = body_leaves
if not matching_bracket: if not matching_bracket or not tail_leaves:
raise CannotSplit("No brackets found") raise CannotSplit("No brackets found")
head = bracket_split_build_line( head = bracket_split_build_line(
@ -882,23 +882,28 @@ def _maybe_split_omitting_optional_parens(
try: try:
# The RHSResult Omitting Optional Parens. # The RHSResult Omitting Optional Parens.
rhs_oop = _first_right_hand_split(line, omit=omit) rhs_oop = _first_right_hand_split(line, omit=omit)
if not ( is_split_right_after_equal = (
# the split is right after `=` len(rhs.head.leaves) >= 2 and rhs.head.leaves[-2].type == token.EQUAL
len(rhs.head.leaves) >= 2 )
and rhs.head.leaves[-2].type == token.EQUAL rhs_head_contains_brackets = any(
# the left side of assignment contains brackets leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]
and any(leaf.type in BRACKETS for leaf in rhs.head.leaves[:-1]) )
# the left side of assignment is short enough (the -1 is for the ending # the -1 is for the ending optional paren
# optional paren) rhs_head_short_enough = is_line_short_enough(
and is_line_short_enough( rhs.head, mode=replace(mode, line_length=mode.line_length - 1)
rhs.head, mode=replace(mode, line_length=mode.line_length - 1) )
rhs_head_explode_blocked_by_magic_trailing_comma = (
rhs.head.magic_trailing_comma is None
)
if (
not (
is_split_right_after_equal
and rhs_head_contains_brackets
and rhs_head_short_enough
and rhs_head_explode_blocked_by_magic_trailing_comma
) )
# the left side of assignment won't explode further because of magic # the omit optional parens split is preferred by some other reason
# trailing comma or _prefer_split_rhs_oop_over_rhs(rhs_oop, rhs, mode)
and rhs.head.magic_trailing_comma is None
# the split by omitting optional parens isn't preferred by some other
# reason
and not _prefer_split_rhs_oop(rhs_oop, mode)
): ):
yield from _maybe_split_omitting_optional_parens( yield from _maybe_split_omitting_optional_parens(
rhs_oop, line, mode, features=features, omit=omit rhs_oop, line, mode, features=features, omit=omit
@ -906,8 +911,12 @@ def _maybe_split_omitting_optional_parens(
return return
except CannotSplit as e: except CannotSplit as e:
if not ( # For chained assignments we want to use the previous successful split
can_be_split(rhs.body) or is_line_short_enough(rhs.body, mode=mode) if line.is_chained_assignment:
pass
elif not can_be_split(rhs.body) and not is_line_short_enough(
rhs.body, mode=mode
): ):
raise CannotSplit( raise CannotSplit(
"Splitting failed, body is still too long and can't be split." "Splitting failed, body is still too long and can't be split."
@ -931,10 +940,22 @@ def _maybe_split_omitting_optional_parens(
yield result yield result
def _prefer_split_rhs_oop(rhs_oop: RHSResult, mode: Mode) -> bool: def _prefer_split_rhs_oop_over_rhs(
rhs_oop: RHSResult, rhs: RHSResult, mode: Mode
) -> bool:
""" """
Returns whether we should prefer the result from a split omitting optional parens. Returns whether we should prefer the result from a split omitting optional parens
(rhs_oop) over the original (rhs).
""" """
# If we have multiple targets, we prefer more `=`s on the head vs pushing them to
# the body
rhs_head_equal_count = [leaf.type for leaf in rhs.head.leaves].count(token.EQUAL)
rhs_oop_head_equal_count = [leaf.type for leaf in rhs_oop.head.leaves].count(
token.EQUAL
)
if rhs_head_equal_count > 1 and rhs_head_equal_count > rhs_oop_head_equal_count:
return False
has_closing_bracket_after_assign = False has_closing_bracket_after_assign = False
for leaf in reversed(rhs_oop.head.leaves): for leaf in reversed(rhs_oop.head.leaves):
if leaf.type == token.EQUAL: if leaf.type == token.EQUAL:

View File

@ -207,6 +207,11 @@ def is_triple_quoted_string(self) -> bool:
return True return True
return False return False
@property
def is_chained_assignment(self) -> bool:
"""Is the line a chained assignment"""
return [leaf.type for leaf in self.leaves].count(token.EQUAL) > 1
@property @property
def opens_block(self) -> bool: def opens_block(self) -> bool:
"""Does this line open a new level of indentation.""" """Does this line open a new level of indentation."""
@ -674,11 +679,9 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
): ):
return 0, 1 return 0, 1
if ( # In preview mode, always allow blank lines, except right before a function
self.previous_line # docstring
and self.previous_line.opens_block is_empty_first_line_ok = (
# Always allow blank lines, except right before a function docstring
and not (
not is_docstring(current_line.leaves[0]) not is_docstring(current_line.leaves[0])
or ( or (
self.previous_line self.previous_line
@ -686,7 +689,6 @@ def _maybe_empty_lines(self, current_line: Line) -> Tuple[int, int]:
and self.previous_line.leaves[0].parent and self.previous_line.leaves[0].parent
and not is_funcdef(self.previous_line.leaves[0].parent) and not is_funcdef(self.previous_line.leaves[0].parent)
) )
)
): ):
return 0, 0 return 0, 0
return before, 0 return before, 0

View File

@ -171,6 +171,10 @@ class Preview(Enum):
hex_codes_in_unicode_sequences = auto() hex_codes_in_unicode_sequences = auto()
string_processing = auto() string_processing = auto()
hug_parens_with_braces_and_square_brackets = auto() hug_parens_with_braces_and_square_brackets = auto()
allow_empty_first_line_in_block = auto()
single_line_format_skip_with_multiple_comments = auto()
long_case_block_line_splitting = auto()
allow_form_feeds = auto()
class Deprecated(UserWarning): class Deprecated(UserWarning):

View File

@ -14,7 +14,7 @@ def format_hex(text: str) -> str:
def format_scientific_notation(text: str) -> str: def format_scientific_notation(text: str) -> str:
"""Formats a numeric string utilizing scentific notation""" """Formats a numeric string utilizing scientific notation"""
before, after = text.split("e") before, after = text.split("e")
sign = "" sign = ""
if after.startswith("-"): if after.startswith("-"):

View File

@ -172,7 +172,7 @@ class _TopLevelStatementsVisitor(Visitor[None]):
A node visitor that converts unchanged top-level statements to A node visitor that converts unchanged top-level statements to
STANDALONE_COMMENT. STANDALONE_COMMENT.
This is used in addition to _convert_unchanged_lines_by_flatterning, to This is used in addition to _convert_unchanged_line_by_line, to
speed up formatting when there are unchanged top-level speed up formatting when there are unchanged top-level
classes/functions/statements. classes/functions/statements.
""" """
@ -302,7 +302,7 @@ def _convert_node_to_standalone_comment(node: LN) -> None:
index = node.remove() index = node.remove()
if index is not None: if index is not None:
# Remove the '\n', as STANDALONE_COMMENT will have '\n' appended when # Remove the '\n', as STANDALONE_COMMENT will have '\n' appended when
# genearting the formatted code. # generating the formatted code.
value = str(node)[:-1] value = str(node)[:-1]
parent.insert_child( parent.insert_child(
index, index,

View File

@ -39,7 +39,6 @@
Set, Set,
Tuple, Tuple,
Union, Union,
cast,
) )
from blib2to3.pgen2.grammar import Grammar from blib2to3.pgen2.grammar import Grammar
@ -262,11 +261,9 @@ def add_whitespace(self, start: Coord) -> None:
def untokenize(self, iterable: Iterable[TokenInfo]) -> str: def untokenize(self, iterable: Iterable[TokenInfo]) -> str:
for t in iterable: for t in iterable:
if len(t) == 2: if len(t) == 2:
self.compat(cast(Tuple[int, str], t), iterable) self.compat(t, iterable)
break break
tok_type, token, start, end, line = cast( tok_type, token, start, end, line = t
Tuple[int, str, Coord, Coord, str], t
)
self.add_whitespace(start) self.add_whitespace(start)
self.tokens.append(token) self.tokens.append(token)
self.prev_row, self.prev_col = end self.prev_row, self.prev_col = end

View File

@ -0,0 +1,3 @@
# flags: --no-preview-line-length-1
# split out from comments2 as it does not work with line-length=1, losing the comment
a = "type comment with trailing space" # type: str

View File

@ -155,8 +155,6 @@ def _init_host(self, parsed) -> None:
pass pass
a = "type comment with trailing space" # type: str
####################### #######################
### SECTION COMMENT ### ### SECTION COMMENT ###
####################### #######################
@ -335,8 +333,6 @@ def _init_host(self, parsed) -> None:
pass pass
a = "type comment with trailing space" # type: str
####################### #######################
### SECTION COMMENT ### ### SECTION COMMENT ###
####################### #######################

View File

@ -1,9 +1,12 @@
# flags: --no-preview-line-length-1
# l2 loses the comment with line-length=1 in preview mode
l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"] l1 = ["This list should be broken up", "into multiple lines", "because it is way too long"]
l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip l2 = ["But this list shouldn't", "even though it also has", "way too many characters in it"] # fmt: skip
l3 = ["I have", "trailing comma", "so I should be braked",] l3 = ["I have", "trailing comma", "so I should be braked",]
# output # output
# l2 loses the comment with line-length=1 in preview mode
l1 = [ l1 = [
"This list should be broken up", "This list should be broken up",
"into multiple lines", "into multiple lines",
@ -14,4 +17,4 @@
"I have", "I have",
"trailing comma", "trailing comma",
"so I should be braked", "so I should be braked",
] ]

View File

@ -1,4 +1,4 @@
# flags: --line-ranges=12-12 # flags: --line-ranges=12-12 --line-ranges=21-21
# NOTE: If you need to modify this file, pay special attention to the --line-ranges= # NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines. # flag above as it's formatting specifically these lines.
@ -11,9 +11,19 @@ class MyClass:
def method(): def method():
print ( "str" ) print ( "str" )
@decor(
a=1,
# fmt: off
b=(2, 3),
# fmt: on
)
def func():
pass
# output # output
# flags: --line-ranges=12-12 # flags: --line-ranges=12-12 --line-ranges=21-21
# NOTE: If you need to modify this file, pay special attention to the --line-ranges= # NOTE: If you need to modify this file, pay special attention to the --line-ranges=
# flag above as it's formatting specifically these lines. # flag above as it's formatting specifically these lines.
@ -25,3 +35,13 @@ class MyClass:
# fmt: on # fmt: on
def method(): def method():
print("str") print("str")
@decor(
a=1,
# fmt: off
b=(2, 3),
# fmt: on
)
def func():
pass

View File

@ -1,4 +1,4 @@
# flags: --minimum-version=3.8 # flags: --minimum-version=3.8 --no-preview-line-length-1
if (foo := 0): if (foo := 0):
pass pass

View File

@ -55,6 +55,13 @@ def quux():
new_line = here new_line = here
class Cls:
def method(self):
pass
# output # output
def foo(): def foo():
@ -113,3 +120,9 @@ def baz():
def quux(): def quux():
new_line = here new_line = here
class Cls:
def method(self):
pass

View File

@ -125,23 +125,6 @@ def foo_square_brackets(request):
func([x for x in "long line long line long line long line long line long line long line"]) func([x for x in "long line long line long line long line long line long line long line"])
func([x for x in [x for x in "long line long line long line long line long line long line long line"]]) func([x for x in [x for x in "long line long line long line long line long line long line long line"]])
func({"short line"})
func({"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"})
func({{"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"}})
func(("long line", "long long line", "long long long line", "long long long long line", "long long long long long line"))
func((("long line", "long long line", "long long long line", "long long long long line", "long long long long long line")))
func([["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]])
# Do not hug if the argument fits on a single line.
func({"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"})
func(("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"))
func(["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"])
func(**{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"})
func(*("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----"))
array = [{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}]
array = [("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")]
array = [["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]]
foooooooooooooooooooo( foooooooooooooooooooo(
[{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size} [{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size}
) )
@ -151,14 +134,11 @@ def foo_square_brackets(request):
) )
nested_mapping = {"key": [{"a very long key 1": "with a very long value", "a very long key 2": "with a very long value"}]} nested_mapping = {"key": [{"a very long key 1": "with a very long value", "a very long key 2": "with a very long value"}]}
nested_array = [[["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]]]
explicit_exploding = [[["short", "line",],],] explicit_exploding = [[["short", "line",],],]
single_item_do_not_explode = Context({ single_item_do_not_explode = Context({
"version": get_docs_version(), "version": get_docs_version(),
}) })
foo(*["long long long long long line", "long long long long long line", "long long long long long line"])
foo(*[str(i) for i in range(100000000000000000000000000000000000000000000000000000000000)]) foo(*[str(i) for i in range(100000000000000000000000000000000000000000000000000000000000)])
foo( foo(
@ -310,69 +290,6 @@ def foo_square_brackets(request):
] ]
]) ])
func({"short line"})
func({
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
})
func({{
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
}})
func((
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
))
func(((
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
)))
func([[
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
]])
# Do not hug if the argument fits on a single line.
func(
{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}
)
func(
("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")
)
func(
["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]
)
func(
**{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"}
)
func(
*("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----")
)
array = [
{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}
]
array = [
("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")
]
array = [
["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]
]
foooooooooooooooooooo( foooooooooooooooooooo(
[{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size} [{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size}
) )
@ -387,13 +304,6 @@ def foo_square_brackets(request):
"a very long key 2": "with a very long value", "a very long key 2": "with a very long value",
}] }]
} }
nested_array = [[[
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
]]]
explicit_exploding = [ explicit_exploding = [
[ [
[ [
@ -406,12 +316,6 @@ def foo_square_brackets(request):
"version": get_docs_version(), "version": get_docs_version(),
}) })
foo(*[
"long long long long long line",
"long long long long long line",
"long long long long long line",
])
foo(*[ foo(*[
str(i) for i in range(100000000000000000000000000000000000000000000000000000000000) str(i) for i in range(100000000000000000000000000000000000000000000000000000000000)
]) ])

View File

@ -0,0 +1,106 @@
# flags: --preview --no-preview-line-length-1
# split out from preview_hug_parens_with_brackes_and_square_brackets, as it produces
# different code on the second pass with line-length 1 in many cases.
# Seems to be about whether the last string in a sequence gets wrapped in parens or not.
foo(*["long long long long long line", "long long long long long line", "long long long long long line"])
func({"short line"})
func({"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"})
func({{"long line", "long long line", "long long long line", "long long long long line", "long long long long long line"}})
func(("long line", "long long line", "long long long line", "long long long long line", "long long long long long line"))
func((("long line", "long long line", "long long long line", "long long long long line", "long long long long long line")))
func([["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]])
# Do not hug if the argument fits on a single line.
func({"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"})
func(("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"))
func(["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"])
func(**{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"})
func(*("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----"))
array = [{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}]
array = [("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")]
array = [["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]]
nested_array = [[["long line", "long long line", "long long long line", "long long long long line", "long long long long long line"]]]
# output
# split out from preview_hug_parens_with_brackes_and_square_brackets, as it produces
# different code on the second pass with line-length 1 in many cases.
# Seems to be about whether the last string in a sequence gets wrapped in parens or not.
foo(*[
"long long long long long line",
"long long long long long line",
"long long long long long line",
])
func({"short line"})
func({
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
})
func({{
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
}})
func((
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
))
func(((
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
)))
func([[
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
]])
# Do not hug if the argument fits on a single line.
func(
{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}
)
func(
("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")
)
func(
["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]
)
func(
**{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit---"}
)
func(
*("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit----")
)
array = [
{"fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"}
]
array = [
("fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line")
]
array = [
["fit line", "fit line", "fit line", "fit line", "fit line", "fit line", "fit line"]
]
nested_array = [[[
"long line",
"long long line",
"long long long line",
"long long long long line",
"long long long long long line",
]]]

View File

@ -84,3 +84,24 @@
) or ( ) or (
isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing isinstance(some_other_var, BaseClass) and table.something != table.some_other_thing
) )
# Multiple targets
a = b = (
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
)
a = b = c = d = e = f = g = (
hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
) = i = j = (
kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
)
a = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
) = c
a = (
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
) = (
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
) = ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd

View File

@ -30,6 +30,7 @@ def check_file(subdir: str, filename: str, *, data: bool = True) -> None:
fast=args.fast, fast=args.fast,
minimum_version=args.minimum_version, minimum_version=args.minimum_version,
lines=args.lines, lines=args.lines,
no_preview_line_length_1=args.no_preview_line_length_1,
) )
if args.minimum_version is not None: if args.minimum_version is not None:
major, minor = args.minimum_version major, minor = args.minimum_version
@ -42,6 +43,7 @@ def check_file(subdir: str, filename: str, *, data: bool = True) -> None:
fast=args.fast, fast=args.fast,
minimum_version=args.minimum_version, minimum_version=args.minimum_version,
lines=args.lines, lines=args.lines,
no_preview_line_length_1=args.no_preview_line_length_1,
) )

View File

@ -46,6 +46,7 @@ class TestCaseArgs:
fast: bool = False fast: bool = False
minimum_version: Optional[Tuple[int, int]] = None minimum_version: Optional[Tuple[int, int]] = None
lines: Collection[Tuple[int, int]] = () lines: Collection[Tuple[int, int]] = ()
no_preview_line_length_1: bool = False
def _assert_format_equal(expected: str, actual: str) -> None: def _assert_format_equal(expected: str, actual: str) -> None:
@ -96,6 +97,7 @@ def assert_format(
fast: bool = False, fast: bool = False,
minimum_version: Optional[Tuple[int, int]] = None, minimum_version: Optional[Tuple[int, int]] = None,
lines: Collection[Tuple[int, int]] = (), lines: Collection[Tuple[int, int]] = (),
no_preview_line_length_1: bool = False,
) -> None: ) -> None:
"""Convenience function to check that Black formats as expected. """Convenience function to check that Black formats as expected.
@ -124,21 +126,28 @@ def assert_format(
f"Black crashed formatting this case in {text} mode." f"Black crashed formatting this case in {text} mode."
) from e ) from e
# Similarly, setting line length to 1 is a good way to catch # Similarly, setting line length to 1 is a good way to catch
# stability bugs. But only in non-preview mode because preview mode # stability bugs. Some tests are known to be broken in preview mode with line length
# currently has a lot of line length 1 bugs. # of 1 though, and have marked that with a flag --no-preview-line-length-1
try: preview_modes = [False]
_assert_format_inner( if not no_preview_line_length_1:
source, preview_modes.append(True)
None,
replace(mode, preview=False, line_length=1), for preview_mode in preview_modes:
fast=fast,
minimum_version=minimum_version, try:
lines=lines, _assert_format_inner(
) source,
except Exception as e: None,
raise FormatFailure( replace(mode, preview=preview_mode, line_length=1),
"Black crashed formatting this case with line-length set to 1." fast=fast,
) from e minimum_version=minimum_version,
lines=lines,
)
except Exception as e:
text = "preview" if preview_mode else "non-preview"
raise FormatFailure(
f"Black crashed formatting this case in {text} mode with line-length=1."
) from e
def _assert_format_inner( def _assert_format_inner(
@ -246,6 +255,15 @@ def get_flags_parser() -> argparse.ArgumentParser:
), ),
) )
parser.add_argument("--line-ranges", action="append") parser.add_argument("--line-ranges", action="append")
parser.add_argument(
"--no-preview-line-length-1",
default=False,
action="store_true",
help=(
"Don't run in preview mode with --line-length=1, as that's known to cause a"
" crash"
),
)
return parser return parser
@ -266,7 +284,11 @@ def parse_mode(flags_line: str) -> TestCaseArgs:
else: else:
lines = [] lines = []
return TestCaseArgs( return TestCaseArgs(
mode=mode, fast=args.fast, minimum_version=args.minimum_version, lines=lines mode=mode,
fast=args.fast,
minimum_version=args.minimum_version,
lines=lines,
no_preview_line_length_1=args.no_preview_line_length_1,
) )