X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/117891878e5be4d6b771ae5de299e51b679cea27..e401b6bb1e1c0ed534bba59d9dc908caf7ba898c:/src/black/__init__.py diff --git a/src/black/__init__.py b/src/black/__init__.py index a5ddec9..283c53f 100644 --- a/src/black/__init__.py +++ b/src/black/__init__.py @@ -10,7 +10,7 @@ from multiprocessing import Manager, freeze_support import os from pathlib import Path from pathspec.patterns.gitwildmatch import GitWildMatchPatternError -import regex as re +import re import signal import sys import tokenize @@ -37,10 +37,11 @@ from mypy_extensions import mypyc_attr from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES from black.const import STDIN_PLACEHOLDER from black.nodes import STARS, syms, is_simple_decorator_expression +from black.nodes import is_string_token from black.lines import Line, EmptyLineTracker from black.linegen import transform_line, LineGenerator, LN from black.comments import normalize_fmt_off -from black.mode import Mode, TargetVersion +from black.mode import FUTURE_FLAG_TO_FEATURE, Mode, TargetVersion from black.mode import Feature, supports_feature, VERSION_TO_FEATURES from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache from black.concurrency import cancel, shutdown, maybe_install_uvloop @@ -57,6 +58,7 @@ from black.handle_ipynb_magics import ( remove_trailing_semicolon, put_trailing_semicolon_back, TRANSFORMED_MAGICS, + PYTHON_CELL_MAGICS, jupyter_dependencies_are_installed, ) @@ -176,12 +178,12 @@ def validate_regex( ) -> Optional[Pattern[str]]: try: return re_compile_maybe_verbose(value) if value is not None else None - except re.error: - raise click.BadParameter("Not a valid regular expression") from None + except re.error as e: + raise click.BadParameter(f"Not a valid regular expression: {e}") from None @click.command( - context_settings=dict(help_option_names=["-h", "--help"]), + context_settings={"help_option_names": ["-h", "--help"]}, # While Click does set this field automatically using the docstring, mypyc # (annoyingly) strips 'em so we need to set it here too. help="The uncompromising code formatter.", @@ -687,7 +689,7 @@ def reformat_many( worker_count = min(worker_count, 60) try: executor = ProcessPoolExecutor(max_workers=worker_count) - except (ImportError, OSError): + except (ImportError, NotImplementedError, OSError): # we arrive here if the underlying system does not support multi-processing # like in AWS Lambda or Termux, in which case we gracefully fallback to # a ThreadPoolExecutor with just a single worker (more workers would not do us @@ -943,7 +945,9 @@ def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileCo def validate_cell(src: str) -> None: - """Check that cell does not already contain TransformerManager transformations. + """Check that cell does not already contain TransformerManager transformations, + or non-Python cell magics, which might cause tokenizer_rt to break because of + indentations. If a cell contains ``!ls``, then it'll be transformed to ``get_ipython().system('ls')``. However, if the cell originally contained @@ -959,6 +963,8 @@ def validate_cell(src: str) -> None: """ if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS): raise NothingChanged + if src[:2] == "%%" and src.split()[0][2:] not in PYTHON_CELL_MAGICS: + raise NothingChanged def format_cell(src: str, *, fast: bool, mode: Mode) -> str: @@ -1075,22 +1081,10 @@ def format_str(src_contents: str, *, mode: Mode) -> FileContent: if mode.target_versions: versions = mode.target_versions else: - versions = detect_target_versions(src_node) - - # TODO: fully drop support and this code hopefully in January 2022 :D - if TargetVersion.PY27 in mode.target_versions or versions == {TargetVersion.PY27}: - msg = ( - "DEPRECATION: Python 2 support will be removed in the first stable release " - "expected in January 2022." - ) - err(msg, fg="yellow", bold=True) + versions = detect_target_versions(src_node, future_imports=future_imports) normalize_fmt_off(src_node) - lines = LineGenerator( - mode=mode, - remove_u_prefix="unicode_literals" in future_imports - or supports_feature(versions, Feature.UNICODE_LITERALS), - ) + lines = LineGenerator(mode=mode) elt = EmptyLineTracker(is_pyi=mode.is_pyi) empty_line = Line(mode=mode) after = 0 @@ -1127,7 +1121,9 @@ def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]: return tiow.read(), encoding, newline -def get_features_used(node: Node) -> Set[Feature]: # noqa: C901 +def get_features_used( # noqa: C901 + node: Node, *, future_imports: Optional[Set[str]] = None +) -> Set[Feature]: """Return a set of (relatively) new Python features used in this file. Currently looking for: @@ -1137,12 +1133,20 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901 - positional only arguments in function signatures and lambdas; - assignment expression; - relaxed decorator syntax; + - usage of __future__ flags (annotations); - print / exec statements; """ features: Set[Feature] = set() + if future_imports: + features |= { + FUTURE_FLAG_TO_FEATURE[future_import] + for future_import in future_imports + if future_import in FUTURE_FLAG_TO_FEATURE + } + for n in node.pre_order(): - if n.type == token.STRING: - value_head = n.value[:2] # type: ignore + if is_string_token(n): + value_head = n.value[:2] if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}: features.add(Feature.F_STRINGS) @@ -1150,14 +1154,6 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901 assert isinstance(n, Leaf) if "_" in n.value: features.add(Feature.NUMERIC_UNDERSCORES) - elif n.value.endswith(("L", "l")): - # Python 2: 10L - features.add(Feature.LONG_INT_LITERAL) - elif len(n.value) >= 2 and n.value[0] == "0" and n.value[1].isdigit(): - # Python 2: 0123; 00123; ... - if not all(char == "0" for char in n.value): - # although we don't want to match 0000 or similar - features.add(Feature.OCTAL_INT_LITERAL) elif n.type == token.SLASH: if n.parent and n.parent.type in { @@ -1195,38 +1191,29 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901 if argch.type in STARS: features.add(feature) - # Python 2 only features (for its deprecation) except for integers, see above - elif n.type == syms.print_stmt: - features.add(Feature.PRINT_STMT) - elif n.type == syms.exec_stmt: - features.add(Feature.EXEC_STMT) - elif n.type == syms.tfpdef: - # def set_position((x, y), value): - # ... - features.add(Feature.AUTOMATIC_PARAMETER_UNPACKING) - elif n.type == syms.except_clause: - # try: - # ... - # except Exception, err: - # ... - if len(n.children) >= 4: - if n.children[-2].type == token.COMMA: - features.add(Feature.COMMA_STYLE_EXCEPT) - elif n.type == syms.raise_stmt: - # raise Exception, "msg" - if len(n.children) >= 4: - if n.children[-2].type == token.COMMA: - features.add(Feature.COMMA_STYLE_RAISE) - elif n.type == token.BACKQUOTE: - # `i'm surprised this ever existed` - features.add(Feature.BACKQUOTE_REPR) + elif ( + n.type in {syms.return_stmt, syms.yield_expr} + and len(n.children) >= 2 + and n.children[1].type == syms.testlist_star_expr + and any(child.type == syms.star_expr for child in n.children[1].children) + ): + features.add(Feature.UNPACKING_ON_FLOW) + + elif ( + n.type == syms.annassign + and len(n.children) >= 4 + and n.children[3].type == syms.testlist_star_expr + ): + features.add(Feature.ANN_ASSIGN_EXTENDED_RHS) return features -def detect_target_versions(node: Node) -> Set[TargetVersion]: +def detect_target_versions( + node: Node, *, future_imports: Optional[Set[str]] = None +) -> Set[TargetVersion]: """Detect the version to target based on the nodes used.""" - features = get_features_used(node) + features = get_features_used(node, future_imports=future_imports) return { version for version in TargetVersion if features <= VERSION_TO_FEATURES[version] } @@ -1288,7 +1275,7 @@ def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None: src_ast = parse_ast(src) except Exception as exc: raise AssertionError( - "cannot use --safe with this file; failed to parse source file." + f"cannot use --safe with this file; failed to parse source file: {exc}" ) from exc try: