Skip to content

Commit

Permalink
Merge branch 'main' into releasenotes
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed Jan 26, 2022
2 parents 906af7a + 32dd9ec commit 5cb5ad8
Show file tree
Hide file tree
Showing 23 changed files with 406 additions and 138 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Expand Up @@ -17,6 +17,9 @@ and the first release covered by our new stability policy.
- For stubs, one blank line between class attributes and methods is now kept if there's
at least one pre-existing blank line (#2736)
- Black now normalizes string prefix order (#2297)
- Remove spaces around power operators if both operands are simple (#2726)
- Work around bug that causes unstable formatting in some cases in the presence of the
magic trailing comma (#2807)

### Parser

Expand Down Expand Up @@ -47,6 +50,9 @@ and the first release covered by our new stability policy.
Jupyter Notebooks (#2744)
- Allow setting custom cache directory on all platforms with environment variable
`BLACK_CACHE_DIR` (#2739).
- Enable Python 3.10+ by default, without any extra need to specify
`--target-version=py310`. (#2758)
- Make passing `SRC` or `--code` mandatory and mutually exclusive (#2804)

### Output

Expand Down
15 changes: 6 additions & 9 deletions README.md
Expand Up @@ -64,16 +64,13 @@ Further information can be found in our docs:

- [Usage and Configuration](https://black.readthedocs.io/en/stable/usage_and_configuration/index.html)

### NOTE: This is a beta product

_Black_ is already [successfully used](https://github.com/psf/black#used-by) by many
projects, small and big. Black has a comprehensive test suite, with efficient parallel
tests, and our own auto formatting and parallel Continuous Integration runner. However,
_Black_ is still beta. Things will probably be wonky for a while. This is made explicit
by the "Beta" trove classifier, as well as by the "b" in the version number. What this
means for you is that **until the formatter becomes stable, you should expect some
formatting to change in the future**. That being said, no drastic stylistic changes are
planned, mostly responses to bug reports.
projects, small and big. _Black_ has a comprehensive test suite, with efficient parallel
tests, and our own auto formatting and parallel Continuous Integration runner. Now that
we have become stable, you should not expect large formatting to changes in the future.
Stylistic changes will mostly be responses to bug reports and support for new Python
syntax. For more information please refer to the
[The Black Code Style](docs/the_black_code_style/index.rst).

Also, as a safety measure which slows down processing, _Black_ will check that the
reformatted code still produces a valid AST that is effectively equivalent to the
Expand Down
11 changes: 4 additions & 7 deletions docs/faq.md
Expand Up @@ -17,20 +17,17 @@ though.

## Is Black safe to use?

Yes, for the most part. _Black_ is strictly about formatting, nothing else. But because
_Black_ is still in [beta](index.rst), some edges are still a bit rough. To combat
issues, the equivalence of code after formatting is
Yes. _Black_ is strictly about formatting, nothing else. Black strives to ensure that
after formatting the AST is
[checked](the_black_code_style/current_style.md#ast-before-and-after-formatting) with
limited special cases where the code is allowed to differ. If issues are found, an error
is raised and the file is left untouched. Magical comments that influence linters and
other tools, such as `# noqa`, may be moved by _Black_. See below for more details.

## How stable is Black's style?

Quite stable. _Black_ aims to enforce one style and one style only, with some room for
pragmatism. However, _Black_ is still in beta so style changes are both planned and
still proposed on the issue tracker. See
[The Black Code Style](the_black_code_style/index.rst) for more details.
Stable. _Black_ aims to enforce one style and one style only, with some room for
pragmatism. See [The Black Code Style](the_black_code_style/index.rst) for more details.

Starting in 2022, the formatting output will be stable for the releases made in the same
year (other than unintentional bugs). It is possible to opt-in to the latest formatting
Expand Down
14 changes: 6 additions & 8 deletions docs/index.rst
Expand Up @@ -18,16 +18,14 @@ can focus on the content instead.

Try it out now using the `Black Playground <https://black.vercel.app>`_.

.. admonition:: Note - this is a beta product
.. admonition:: Note - Black is now stable!

*Black* is already `successfully used <https://github.com/psf/black#used-by>`_ by
*Black* is `successfully used <https://github.com/psf/black#used-by>`_ by
many projects, small and big. *Black* has a comprehensive test suite, with efficient
parallel tests, our own auto formatting and parallel Continuous Integration runner.
However, *Black* is still beta. Things will probably be wonky for a while. This is
made explicit by the "Beta" trove classifier, as well as by the "b" in the version
number. What this means for you is that **until the formatter becomes stable, you
should expect some formatting to change in the future**. That being said, no drastic
stylistic changes are planned, mostly responses to bug reports.
parallel tests, our own auto formatting and parallel Continuous Integration runner.
Now that we have become stable, you should not expect large formatting to changes in
the future. Stylistic changes will mostly be responses to bug reports and support for new Python
syntax.

Also, as a safety measure which slows down processing, *Black* will check that the
reformatted code still produces a valid AST that is effectively equivalent to the
Expand Down
20 changes: 20 additions & 0 deletions docs/the_black_code_style/current_style.md
Expand Up @@ -284,6 +284,26 @@ multiple lines. This is so that _Black_ is compliant with the recent changes in
[PEP 8](https://www.python.org/dev/peps/pep-0008/#should-a-line-break-before-or-after-a-binary-operator)
style guide, which emphasizes that this approach improves readability.

Almost all operators will be surrounded by single spaces, the only exceptions are unary
operators (`+`, `-`, and `~`), and power operators when both operands are simple. For
powers, an operand is considered simple if it's only a NAME, numeric CONSTANT, or
attribute access (chained attribute access is allowed), with or without a preceding
unary operator.

```python
# For example, these won't be surrounded by whitespace
a = x**y
b = config.base**5.2
c = config.base**runtime.config.exponent
d = 2**5
e = 2**~5

# ... but these will be surrounded by whitespace
f = 2 ** get_exponent()
g = get_x() ** get_y()
h = config['base'] ** 2
```

### Slices

PEP 8
Expand Down
4 changes: 2 additions & 2 deletions docs/usage_and_configuration/the_basics.md
Expand Up @@ -4,11 +4,11 @@ Foundational knowledge on using and configuring Black.

_Black_ is a well-behaved Unix-style command-line tool:

- it does nothing if no sources are passed to it;
- it does nothing if it finds no sources to format;
- it will read from standard input and write to standard output if `-` is used as the
filename;
- it only outputs messages to users on standard error;
- exits with code 0 unless an internal error occurred (or `--check` was used).
- exits with code 0 unless an internal error occurred or a CLI option prompted it.

## Usage

Expand Down
2 changes: 2 additions & 0 deletions fuzz.py
Expand Up @@ -32,7 +32,9 @@
black.FileMode,
line_length=st.just(88) | st.integers(0, 200),
string_normalization=st.booleans(),
preview=st.booleans(),
is_pyi=st.booleans(),
magic_trailing_comma=st.booleans(),
),
)
def test_idempotent_any_syntatically_valid_python(
Expand Down
41 changes: 27 additions & 14 deletions src/black/__init__.py
Expand Up @@ -431,6 +431,17 @@ def main(
) -> None:
"""The uncompromising code formatter."""
ctx.ensure_object(dict)

if src and code is not None:
out(
main.get_usage(ctx)
+ "\n\n'SRC' and 'code' cannot be passed simultaneously."
)
ctx.exit(1)
if not src and code is None:
out(main.get_usage(ctx) + "\n\nOne of 'SRC' or 'code' is required.")
ctx.exit(1)

root, method = find_project_root(src) if code is None else (None, None)
ctx.obj["root"] = root

Expand Down Expand Up @@ -569,7 +580,6 @@ def get_sources(
) -> Set[Path]:
"""Compute the set of files to be formatted."""
sources: Set[Path] = set()
path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)

if exclude is None:
exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
Expand Down Expand Up @@ -958,17 +968,7 @@ def check_stability_and_equivalence(
content differently.
"""
assert_equivalent(src_contents, dst_contents)

# Forced second pass to work around optional trailing commas (becoming
# forced trailing commas on pass 2) interacting differently with optional
# parentheses. Admittedly ugly.
dst_contents_pass2 = format_str(dst_contents, mode=mode)
if dst_contents != dst_contents_pass2:
dst_contents = dst_contents_pass2
assert_equivalent(src_contents, dst_contents, pass_num=2)
assert_stable(src_contents, dst_contents, mode=mode)
# Note: no need to explicitly call `assert_stable` if `dst_contents` was
# the same as `dst_contents_pass2`.
assert_stable(src_contents, dst_contents, mode=mode)


def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
Expand Down Expand Up @@ -1098,7 +1098,7 @@ def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileCon
raise NothingChanged


def format_str(src_contents: str, *, mode: Mode) -> FileContent:
def format_str(src_contents: str, *, mode: Mode) -> str:
"""Reformat a string and return new contents.
`mode` determines formatting options, such as how many characters per line are
Expand Down Expand Up @@ -1128,6 +1128,16 @@ def f(
hey
"""
dst_contents = _format_str_once(src_contents, mode=mode)
# Forced second pass to work around optional trailing commas (becoming
# forced trailing commas on pass 2) interacting differently with optional
# parentheses. Admittedly ugly.
if src_contents != dst_contents:
return _format_str_once(dst_contents, mode=mode)
return dst_contents


def _format_str_once(src_contents: str, *, mode: Mode) -> str:
src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
dst_contents = []
future_imports = get_future_imports(src_node)
Expand Down Expand Up @@ -1357,7 +1367,10 @@ def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:

def assert_stable(src: str, dst: str, mode: Mode) -> None:
"""Raise AssertionError if `dst` reformats differently the second time."""
newdst = format_str(dst, mode=mode)
# We shouldn't call format_str() here, because that formats the string
# twice and may hide a bug where we bounce back and forth between two
# versions.
newdst = _format_str_once(dst, mode=mode)
if dst != newdst:
log = dump_to_file(
str(mode),
Expand Down
7 changes: 5 additions & 2 deletions src/black/linegen.py
Expand Up @@ -21,8 +21,8 @@
from black.numerics import normalize_numeric_literal
from black.strings import get_string_prefix, fix_docstring
from black.strings import normalize_string_prefix, normalize_string_quotes
from black.trans import Transformer, CannotTransform, StringMerger
from black.trans import StringSplitter, StringParenWrapper, StringParenStripper
from black.trans import Transformer, CannotTransform, StringMerger, StringSplitter
from black.trans import StringParenWrapper, StringParenStripper, hug_power_op
from black.mode import Mode, Feature, Preview

from blib2to3.pytree import Node, Leaf
Expand Down Expand Up @@ -404,6 +404,9 @@ def _rhs(
transformers = [delimiter_split, standalone_comment_split, rhs]
else:
transformers = [rhs]
# It's always safe to attempt hugging of power operations and pretty much every line
# could match.
transformers.append(hug_power_op)

for transform in transformers:
# We are accumulating lines in `result` because we might want to abort
Expand Down
86 changes: 84 additions & 2 deletions src/black/trans.py
Expand Up @@ -24,9 +24,9 @@
import sys

if sys.version_info < (3, 8):
from typing_extensions import Final
from typing_extensions import Literal, Final
else:
from typing import Final
from typing import Literal, Final

from mypy_extensions import trait

Expand Down Expand Up @@ -71,6 +71,88 @@ def TErr(err_msg: str) -> Err[CannotTransform]:
return Err(cant_transform)


def hug_power_op(line: Line, features: Collection[Feature]) -> Iterator[Line]:
"""A transformer which normalizes spacing around power operators."""

# Performance optimization to avoid unnecessary Leaf clones and other ops.
for leaf in line.leaves:
if leaf.type == token.DOUBLESTAR:
break
else:
raise CannotTransform("No doublestar token was found in the line.")

def is_simple_lookup(index: int, step: Literal[1, -1]) -> bool:
# Brackets and parentheses indicate calls, subscripts, etc. ...
# basically stuff that doesn't count as "simple". Only a NAME lookup
# or dotted lookup (eg. NAME.NAME) is OK.
if step == -1:
disallowed = {token.RPAR, token.RSQB}
else:
disallowed = {token.LPAR, token.LSQB}

while 0 <= index < len(line.leaves):
current = line.leaves[index]
if current.type in disallowed:
return False
if current.type not in {token.NAME, token.DOT} or current.value == "for":
# If the current token isn't disallowed, we'll assume this is simple as
# only the disallowed tokens are semantically attached to this lookup
# expression we're checking. Also, stop early if we hit the 'for' bit
# of a comprehension.
return True

index += step

return True

def is_simple_operand(index: int, kind: Literal["base", "exponent"]) -> bool:
# An operand is considered "simple" if's a NAME, a numeric CONSTANT, a simple
# lookup (see above), with or without a preceding unary operator.
start = line.leaves[index]
if start.type in {token.NAME, token.NUMBER}:
return is_simple_lookup(index, step=(1 if kind == "exponent" else -1))

if start.type in {token.PLUS, token.MINUS, token.TILDE}:
if line.leaves[index + 1].type in {token.NAME, token.NUMBER}:
# step is always one as bases with a preceding unary op will be checked
# for simplicity starting from the next token (so it'll hit the check
# above).
return is_simple_lookup(index + 1, step=1)

return False

leaves: List[Leaf] = []
should_hug = False
for idx, leaf in enumerate(line.leaves):
new_leaf = leaf.clone()
if should_hug:
new_leaf.prefix = ""
should_hug = False

should_hug = (
(0 < idx < len(line.leaves) - 1)
and leaf.type == token.DOUBLESTAR
and is_simple_operand(idx - 1, kind="base")
and line.leaves[idx - 1].value != "lambda"
and is_simple_operand(idx + 1, kind="exponent")
)
if should_hug:
new_leaf.prefix = ""

leaves.append(new_leaf)

yield Line(
mode=line.mode,
depth=line.depth,
leaves=leaves,
comments=line.comments,
bracket_tracker=line.bracket_tracker,
inside_brackets=line.inside_brackets,
should_split_rhs=line.should_split_rhs,
magic_trailing_comma=line.magic_trailing_comma,
)


class StringTransformer(ABC):
"""
An implementation of the Transformer protocol that relies on its
Expand Down
2 changes: 1 addition & 1 deletion src/black_primer/primer.json
Expand Up @@ -81,7 +81,7 @@
},
"flake8-bugbear": {
"cli_arguments": ["--experimental-string-processing"],
"expect_formatting_changes": false,
"expect_formatting_changes": true,
"git_clone_url": "https://github.com/PyCQA/flake8-bugbear.git",
"long_checkout": false,
"py_versions": ["all"]
Expand Down

0 comments on commit 5cb5ad8

Please sign in to comment.