All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 from json.decoder import JSONDecodeError
4 from concurrent.futures import Executor, ThreadPoolExecutor, ProcessPoolExecutor
5 from contextlib import contextmanager
6 from datetime import datetime
9 from multiprocessing import Manager, freeze_support
11 from pathlib import Path
12 from pathspec.patterns.gitwildmatch import GitWildMatchPatternError
34 from dataclasses import replace
35 from mypy_extensions import mypyc_attr
37 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
38 from black.const import STDIN_PLACEHOLDER
39 from black.nodes import STARS, syms, is_simple_decorator_expression
40 from black.lines import Line, EmptyLineTracker
41 from black.linegen import transform_line, LineGenerator, LN
42 from black.comments import normalize_fmt_off
43 from black.mode import Mode, TargetVersion
44 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
45 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
46 from black.concurrency import cancel, shutdown, maybe_install_uvloop
47 from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
48 from black.report import Report, Changed, NothingChanged
49 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
50 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
51 from black.files import wrap_stream_for_windows
52 from black.parsing import InvalidInput # noqa F401
53 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
54 from black.handle_ipynb_magics import (
57 remove_trailing_semicolon,
58 put_trailing_semicolon_back,
60 jupyter_dependencies_are_installed,
65 from blib2to3.pytree import Node, Leaf
66 from blib2to3.pgen2 import token
68 from _black_version import version as __version__
70 COMPILED = Path(__file__).suffix in (".pyd", ".so")
78 class WriteBack(Enum):
86 def from_configuration(
87 cls, *, check: bool, diff: bool, color: bool = False
89 if check and not diff:
95 return cls.DIFF if diff else cls.YES
98 # Legacy name, left for integrations.
101 DEFAULT_WORKERS = os.cpu_count()
104 def read_pyproject_toml(
105 ctx: click.Context, param: click.Parameter, value: Optional[str]
107 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
109 Returns the path to a successfully found and read configuration file, None
113 value = find_pyproject_toml(ctx.params.get("src", ()))
118 config = parse_pyproject_toml(value)
119 except (OSError, ValueError) as e:
120 raise click.FileError(
121 filename=value, hint=f"Error reading configuration file: {e}"
127 # Sanitize the values to be Click friendly. For more information please see:
128 # https://github.com/psf/black/issues/1458
129 # https://github.com/pallets/click/issues/1567
131 k: str(v) if not isinstance(v, (list, dict)) else v
132 for k, v in config.items()
135 target_version = config.get("target_version")
136 if target_version is not None and not isinstance(target_version, list):
137 raise click.BadOptionUsage(
138 "target-version", "Config key target-version must be a list"
141 default_map: Dict[str, Any] = {}
143 default_map.update(ctx.default_map)
144 default_map.update(config)
146 ctx.default_map = default_map
150 def target_version_option_callback(
151 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
152 ) -> List[TargetVersion]:
153 """Compute the target versions from a --target-version flag.
155 This is its own function because mypy couldn't infer the type correctly
156 when it was a lambda, causing mypyc trouble.
158 return [TargetVersion[val.upper()] for val in v]
161 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
162 """Compile a regular expression string in `regex`.
164 If it contains newlines, use verbose mode.
167 regex = "(?x)" + regex
168 compiled: Pattern[str] = re.compile(regex)
174 param: click.Parameter,
175 value: Optional[str],
176 ) -> Optional[Pattern[str]]:
178 return re_compile_maybe_verbose(value) if value is not None else None
180 raise click.BadParameter("Not a valid regular expression") from None
184 context_settings=dict(help_option_names=["-h", "--help"]),
185 # While Click does set this field automatically using the docstring, mypyc
186 # (annoyingly) strips 'em so we need to set it here too.
187 help="The uncompromising code formatter.",
189 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
194 default=DEFAULT_LINE_LENGTH,
195 help="How many characters per line to allow.",
201 type=click.Choice([v.name.lower() for v in TargetVersion]),
202 callback=target_version_option_callback,
205 "Python versions that should be supported by Black's output. [default: per-file"
213 "Format all input files like typing stubs regardless of file extension (useful"
214 " when piping source on standard input)."
221 "Format all input files like Jupyter Notebooks regardless of file extension "
222 "(useful when piping source on standard input)."
227 "--skip-string-normalization",
229 help="Don't normalize string quotes or prefixes.",
233 "--skip-magic-trailing-comma",
235 help="Don't use trailing commas as a reason to split lines.",
238 "--experimental-string-processing",
242 "Experimental option that performs more normalization on string literals."
243 " Currently disabled because it leads to some crashes."
250 "Don't write the files back, just return the status. Return code 0 means"
251 " nothing would change. Return code 1 means some files would be reformatted."
252 " Return code 123 means there was an internal error."
258 help="Don't write the files back, just output a diff for each file on stdout.",
261 "--color/--no-color",
263 help="Show colored diff. Only applies when `--diff` is given.",
268 help="If --fast given, skip temporary sanity checks. [default: --safe]",
271 "--required-version",
274 "Require a specific version of Black to be running (useful for unifying results"
275 " across many environments e.g. with a pyproject.toml file)."
281 default=DEFAULT_INCLUDES,
282 callback=validate_regex,
284 "A regular expression that matches files and directories that should be"
285 " included on recursive searches. An empty value means all files are included"
286 " regardless of the name. Use forward slashes for directories on all platforms"
287 " (Windows, too). Exclusions are calculated first, inclusions later."
294 callback=validate_regex,
296 "A regular expression that matches files and directories that should be"
297 " excluded on recursive searches. An empty value means no paths are excluded."
298 " Use forward slashes for directories on all platforms (Windows, too)."
299 " Exclusions are calculated first, inclusions later. [default:"
300 f" {DEFAULT_EXCLUDES}]"
307 callback=validate_regex,
309 "Like --exclude, but adds additional files and directories on top of the"
310 " excluded ones. (Useful if you simply want to add to the default)"
316 callback=validate_regex,
318 "Like --exclude, but files and directories matching this regex will be "
319 "excluded even when they are passed explicitly as arguments."
326 "The name of the file when passing it through stdin. Useful to make "
327 "sure Black will respect --force-exclude option on some "
328 "editors that rely on using stdin."
334 type=click.IntRange(min=1),
335 default=DEFAULT_WORKERS,
337 help="Number of parallel workers",
344 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
345 " those with 2>/dev/null."
353 "Also emit messages to stderr about files that were not changed or were ignored"
354 " due to exclusion patterns."
357 @click.version_option(
359 message=f"%(prog)s, %(version)s (compiled: {'yes' if COMPILED else 'no'})",
365 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
381 callback=read_pyproject_toml,
382 help="Read configuration from FILE path.",
389 target_version: List[TargetVersion],
396 skip_string_normalization: bool,
397 skip_magic_trailing_comma: bool,
398 experimental_string_processing: bool,
401 required_version: Optional[str],
402 include: Pattern[str],
403 exclude: Optional[Pattern[str]],
404 extend_exclude: Optional[Pattern[str]],
405 force_exclude: Optional[Pattern[str]],
406 stdin_filename: Optional[str],
408 src: Tuple[str, ...],
409 config: Optional[str],
411 """The uncompromising code formatter."""
412 if config and verbose:
413 out(f"Using configuration from {config}.", bold=False, fg="blue")
415 error_msg = "Oh no! 💥 💔 💥"
416 if required_version and required_version != __version__:
418 f"{error_msg} The required version `{required_version}` does not match"
419 f" the running version `{__version__}`!"
423 err("Cannot pass both `pyi` and `ipynb` flags!")
426 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
428 versions = set(target_version)
430 # We'll autodetect later.
433 target_versions=versions,
434 line_length=line_length,
437 string_normalization=not skip_string_normalization,
438 magic_trailing_comma=not skip_magic_trailing_comma,
439 experimental_string_processing=experimental_string_processing,
443 # Run in quiet mode by default with -c; the extra output isn't useful.
444 # You can still pass -v to get verbose output.
447 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
451 content=code, fast=fast, write_back=write_back, mode=mode, report=report
455 sources = get_sources(
462 extend_exclude=extend_exclude,
463 force_exclude=force_exclude,
465 stdin_filename=stdin_filename,
467 except GitWildMatchPatternError:
472 "No Python files are present to be formatted. Nothing to do 😴",
478 if len(sources) == 1:
482 write_back=write_back,
490 write_back=write_back,
496 if verbose or not quiet:
497 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
499 click.echo(str(report), err=True)
500 ctx.exit(report.return_code)
506 src: Tuple[str, ...],
509 include: Pattern[str],
510 exclude: Optional[Pattern[str]],
511 extend_exclude: Optional[Pattern[str]],
512 force_exclude: Optional[Pattern[str]],
514 stdin_filename: Optional[str],
516 """Compute the set of files to be formatted."""
518 root = find_project_root(src)
519 sources: Set[Path] = set()
520 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
523 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
524 gitignore = get_gitignore(root)
529 if s == "-" and stdin_filename:
530 p = Path(stdin_filename)
536 if is_stdin or p.is_file():
537 normalized_path = normalize_path_maybe_ignore(p, root, report)
538 if normalized_path is None:
541 normalized_path = "/" + normalized_path
542 # Hard-exclude any files that matches the `--force-exclude` regex.
544 force_exclude_match = force_exclude.search(normalized_path)
546 force_exclude_match = None
547 if force_exclude_match and force_exclude_match.group(0):
548 report.path_ignored(p, "matches the --force-exclude regular expression")
552 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
554 if p.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
555 verbose=verbose, quiet=quiet
578 err(f"invalid path: {s}")
583 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
586 Exit if there is no `src` provided for formatting
589 if verbose or not quiet:
595 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
598 Reformat and print out `content` without spawning child processes.
599 Similar to `reformat_one`, but for string content.
601 `fast`, `write_back`, and `mode` options are passed to
602 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
604 path = Path("<string>")
607 if format_stdin_to_stdout(
608 content=content, fast=fast, write_back=write_back, mode=mode
610 changed = Changed.YES
611 report.done(path, changed)
612 except Exception as exc:
614 traceback.print_exc()
615 report.failed(path, str(exc))
619 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
621 """Reformat a single file under `src` without spawning child processes.
623 `fast`, `write_back`, and `mode` options are passed to
624 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
631 elif str(src).startswith(STDIN_PLACEHOLDER):
633 # Use the original name again in case we want to print something
635 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
640 if src.suffix == ".pyi":
641 mode = replace(mode, is_pyi=True)
642 elif src.suffix == ".ipynb":
643 mode = replace(mode, is_ipynb=True)
644 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
645 changed = Changed.YES
648 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
649 cache = read_cache(mode)
650 res_src = src.resolve()
651 res_src_s = str(res_src)
652 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
653 changed = Changed.CACHED
654 if changed is not Changed.CACHED and format_file_in_place(
655 src, fast=fast, write_back=write_back, mode=mode
657 changed = Changed.YES
658 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
659 write_back is WriteBack.CHECK and changed is Changed.NO
661 write_cache(cache, [src], mode)
662 report.done(src, changed)
663 except Exception as exc:
665 traceback.print_exc()
666 report.failed(src, str(exc))
669 # diff-shades depends on being to monkeypatch this function to operate. I know it's
670 # not ideal, but this shouldn't cause any issues ... hopefully. ~ichard26
671 @mypyc_attr(patchable=True)
675 write_back: WriteBack,
678 workers: Optional[int],
680 """Reformat multiple files using a ProcessPoolExecutor."""
682 loop = asyncio.get_event_loop()
683 worker_count = workers if workers is not None else DEFAULT_WORKERS
684 if sys.platform == "win32":
685 # Work around https://bugs.python.org/issue26903
686 assert worker_count is not None
687 worker_count = min(worker_count, 60)
689 executor = ProcessPoolExecutor(max_workers=worker_count)
690 except (ImportError, NotImplementedError, OSError):
691 # we arrive here if the underlying system does not support multi-processing
692 # like in AWS Lambda or Termux, in which case we gracefully fallback to
693 # a ThreadPoolExecutor with just a single worker (more workers would not do us
694 # any good due to the Global Interpreter Lock)
695 executor = ThreadPoolExecutor(max_workers=1)
698 loop.run_until_complete(
702 write_back=write_back,
711 if executor is not None:
715 async def schedule_formatting(
718 write_back: WriteBack,
721 loop: asyncio.AbstractEventLoop,
724 """Run formatting of `sources` in parallel using the provided `executor`.
726 (Use ProcessPoolExecutors for actual parallelism.)
728 `write_back`, `fast`, and `mode` options are passed to
729 :func:`format_file_in_place`.
732 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
733 cache = read_cache(mode)
734 sources, cached = filter_cached(cache, sources)
735 for src in sorted(cached):
736 report.done(src, Changed.CACHED)
741 sources_to_cache = []
743 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
744 # For diff output, we need locks to ensure we don't interleave output
745 # from different processes.
747 lock = manager.Lock()
749 asyncio.ensure_future(
750 loop.run_in_executor(
751 executor, format_file_in_place, src, fast, mode, write_back, lock
754 for src in sorted(sources)
756 pending = tasks.keys()
758 loop.add_signal_handler(signal.SIGINT, cancel, pending)
759 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
760 except NotImplementedError:
761 # There are no good alternatives for these on Windows.
764 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
766 src = tasks.pop(task)
768 cancelled.append(task)
769 elif task.exception():
770 report.failed(src, str(task.exception()))
772 changed = Changed.YES if task.result() else Changed.NO
773 # If the file was written back or was successfully checked as
774 # well-formatted, store this information in the cache.
775 if write_back is WriteBack.YES or (
776 write_back is WriteBack.CHECK and changed is Changed.NO
778 sources_to_cache.append(src)
779 report.done(src, changed)
781 if sys.version_info >= (3, 7):
782 await asyncio.gather(*cancelled, return_exceptions=True)
784 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
786 write_cache(cache, sources_to_cache, mode)
789 def format_file_in_place(
793 write_back: WriteBack = WriteBack.NO,
794 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
796 """Format file under `src` path. Return True if changed.
798 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
800 `mode` and `fast` options are passed to :func:`format_file_contents`.
802 if src.suffix == ".pyi":
803 mode = replace(mode, is_pyi=True)
804 elif src.suffix == ".ipynb":
805 mode = replace(mode, is_ipynb=True)
807 then = datetime.utcfromtimestamp(src.stat().st_mtime)
808 with open(src, "rb") as buf:
809 src_contents, encoding, newline = decode_bytes(buf.read())
811 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
812 except NothingChanged:
814 except JSONDecodeError:
816 f"File '{src}' cannot be parsed as valid Jupyter notebook."
819 if write_back == WriteBack.YES:
820 with open(src, "w", encoding=encoding, newline=newline) as f:
821 f.write(dst_contents)
822 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
823 now = datetime.utcnow()
824 src_name = f"{src}\t{then} +0000"
825 dst_name = f"{src}\t{now} +0000"
827 diff_contents = ipynb_diff(src_contents, dst_contents, src_name, dst_name)
829 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
831 if write_back == WriteBack.COLOR_DIFF:
832 diff_contents = color_diff(diff_contents)
834 with lock or nullcontext():
835 f = io.TextIOWrapper(
841 f = wrap_stream_for_windows(f)
842 f.write(diff_contents)
848 def format_stdin_to_stdout(
851 content: Optional[str] = None,
852 write_back: WriteBack = WriteBack.NO,
855 """Format file on stdin. Return True if changed.
857 If content is None, it's read from sys.stdin.
859 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
860 write a diff to stdout. The `mode` argument is passed to
861 :func:`format_file_contents`.
863 then = datetime.utcnow()
866 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
868 src, encoding, newline = content, "utf-8", ""
872 dst = format_file_contents(src, fast=fast, mode=mode)
875 except NothingChanged:
879 f = io.TextIOWrapper(
880 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
882 if write_back == WriteBack.YES:
883 # Make sure there's a newline after the content
884 if dst and dst[-1] != "\n":
887 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
888 now = datetime.utcnow()
889 src_name = f"STDIN\t{then} +0000"
890 dst_name = f"STDOUT\t{now} +0000"
891 d = diff(src, dst, src_name, dst_name)
892 if write_back == WriteBack.COLOR_DIFF:
894 f = wrap_stream_for_windows(f)
899 def check_stability_and_equivalence(
900 src_contents: str, dst_contents: str, *, mode: Mode
902 """Perform stability and equivalence checks.
904 Raise AssertionError if source and destination contents are not
905 equivalent, or if a second pass of the formatter would format the
908 assert_equivalent(src_contents, dst_contents)
910 # Forced second pass to work around optional trailing commas (becoming
911 # forced trailing commas on pass 2) interacting differently with optional
912 # parentheses. Admittedly ugly.
913 dst_contents_pass2 = format_str(dst_contents, mode=mode)
914 if dst_contents != dst_contents_pass2:
915 dst_contents = dst_contents_pass2
916 assert_equivalent(src_contents, dst_contents, pass_num=2)
917 assert_stable(src_contents, dst_contents, mode=mode)
918 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
919 # the same as `dst_contents_pass2`.
922 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
923 """Reformat contents of a file and return new contents.
925 If `fast` is False, additionally confirm that the reformatted code is
926 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
927 `mode` is passed to :func:`format_str`.
929 if not src_contents.strip():
933 dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
935 dst_contents = format_str(src_contents, mode=mode)
936 if src_contents == dst_contents:
939 if not fast and not mode.is_ipynb:
940 # Jupyter notebooks will already have been checked above.
941 check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
945 def validate_cell(src: str) -> None:
946 """Check that cell does not already contain TransformerManager transformations.
948 If a cell contains ``!ls``, then it'll be transformed to
949 ``get_ipython().system('ls')``. However, if the cell originally contained
950 ``get_ipython().system('ls')``, then it would get transformed in the same way:
952 >>> TransformerManager().transform_cell("get_ipython().system('ls')")
953 "get_ipython().system('ls')\n"
954 >>> TransformerManager().transform_cell("!ls")
955 "get_ipython().system('ls')\n"
957 Due to the impossibility of safely roundtripping in such situations, cells
958 containing transformed magics will be ignored.
960 if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
964 def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
965 """Format code in given cell of Jupyter notebook.
969 - if cell has trailing semicolon, remove it;
970 - if cell has IPython magics, mask them;
972 - reinstate IPython magics;
973 - reinstate trailing semicolon (if originally present);
974 - strip trailing newlines.
976 Cells with syntax errors will not be processed, as they
977 could potentially be automagics or multi-line magics, which
978 are currently not supported.
981 src_without_trailing_semicolon, has_trailing_semicolon = remove_trailing_semicolon(
985 masked_src, replacements = mask_cell(src_without_trailing_semicolon)
987 raise NothingChanged from None
988 masked_dst = format_str(masked_src, mode=mode)
990 check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
991 dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
992 dst = put_trailing_semicolon_back(
993 dst_without_trailing_semicolon, has_trailing_semicolon
995 dst = dst.rstrip("\n")
997 raise NothingChanged from None
1001 def validate_metadata(nb: MutableMapping[str, Any]) -> None:
1002 """If notebook is marked as non-Python, don't format it.
1004 All notebook metadata fields are optional, see
1005 https://nbformat.readthedocs.io/en/latest/format_description.html. So
1006 if a notebook has empty metadata, we will try to parse it anyway.
1008 language = nb.get("metadata", {}).get("language_info", {}).get("name", None)
1009 if language is not None and language != "python":
1010 raise NothingChanged from None
1013 def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
1014 """Format Jupyter notebook.
1016 Operate cell-by-cell, only on code cells, only for Python notebooks.
1017 If the ``.ipynb`` originally had a trailing newline, it'll be preserved.
1019 trailing_newline = src_contents[-1] == "\n"
1021 nb = json.loads(src_contents)
1022 validate_metadata(nb)
1023 for cell in nb["cells"]:
1024 if cell.get("cell_type", None) == "code":
1026 src = "".join(cell["source"])
1027 dst = format_cell(src, fast=fast, mode=mode)
1028 except NothingChanged:
1031 cell["source"] = dst.splitlines(keepends=True)
1034 dst_contents = json.dumps(nb, indent=1, ensure_ascii=False)
1035 if trailing_newline:
1036 dst_contents = dst_contents + "\n"
1039 raise NothingChanged
1042 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
1043 """Reformat a string and return new contents.
1045 `mode` determines formatting options, such as how many characters per line are
1049 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
1050 def f(arg: str = "") -> None:
1053 A more complex example:
1056 ... black.format_str(
1057 ... "def f(arg:str='')->None: hey",
1058 ... mode=black.Mode(
1059 ... target_versions={black.TargetVersion.PY36},
1061 ... string_normalization=False,
1072 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1074 future_imports = get_future_imports(src_node)
1075 if mode.target_versions:
1076 versions = mode.target_versions
1078 versions = detect_target_versions(src_node)
1080 # TODO: fully drop support and this code hopefully in January 2022 :D
1081 if TargetVersion.PY27 in mode.target_versions or versions == {TargetVersion.PY27}:
1083 "DEPRECATION: Python 2 support will be removed in the first stable release "
1084 "expected in January 2022."
1086 err(msg, fg="yellow", bold=True)
1088 normalize_fmt_off(src_node)
1089 lines = LineGenerator(
1091 remove_u_prefix="unicode_literals" in future_imports
1092 or supports_feature(versions, Feature.UNICODE_LITERALS),
1094 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
1095 empty_line = Line(mode=mode)
1097 split_line_features = {
1099 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
1100 if supports_feature(versions, feature)
1102 for current_line in lines.visit(src_node):
1103 dst_contents.append(str(empty_line) * after)
1104 before, after = elt.maybe_empty_lines(current_line)
1105 dst_contents.append(str(empty_line) * before)
1106 for line in transform_line(
1107 current_line, mode=mode, features=split_line_features
1109 dst_contents.append(str(line))
1110 return "".join(dst_contents)
1113 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
1114 """Return a tuple of (decoded_contents, encoding, newline).
1116 `newline` is either CRLF or LF but `decoded_contents` is decoded with
1117 universal newlines (i.e. only contains LF).
1119 srcbuf = io.BytesIO(src)
1120 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
1122 return "", encoding, "\n"
1124 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
1126 with io.TextIOWrapper(srcbuf, encoding) as tiow:
1127 return tiow.read(), encoding, newline
1130 def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
1131 """Return a set of (relatively) new Python features used in this file.
1133 Currently looking for:
1135 - underscores in numeric literals;
1136 - trailing commas after * or ** in function signatures and calls;
1137 - positional only arguments in function signatures and lambdas;
1138 - assignment expression;
1139 - relaxed decorator syntax;
1140 - print / exec statements;
1142 features: Set[Feature] = set()
1143 for n in node.pre_order():
1144 if n.type == token.STRING:
1145 value_head = n.value[:2] # type: ignore
1146 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1147 features.add(Feature.F_STRINGS)
1149 elif n.type == token.NUMBER:
1150 assert isinstance(n, Leaf)
1152 features.add(Feature.NUMERIC_UNDERSCORES)
1153 elif n.value.endswith(("L", "l")):
1155 features.add(Feature.LONG_INT_LITERAL)
1156 elif len(n.value) >= 2 and n.value[0] == "0" and n.value[1].isdigit():
1157 # Python 2: 0123; 00123; ...
1158 if not all(char == "0" for char in n.value):
1159 # although we don't want to match 0000 or similar
1160 features.add(Feature.OCTAL_INT_LITERAL)
1162 elif n.type == token.SLASH:
1163 if n.parent and n.parent.type in {
1168 features.add(Feature.POS_ONLY_ARGUMENTS)
1170 elif n.type == token.COLONEQUAL:
1171 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
1173 elif n.type == syms.decorator:
1174 if len(n.children) > 1 and not is_simple_decorator_expression(
1177 features.add(Feature.RELAXED_DECORATORS)
1180 n.type in {syms.typedargslist, syms.arglist}
1182 and n.children[-1].type == token.COMMA
1184 if n.type == syms.typedargslist:
1185 feature = Feature.TRAILING_COMMA_IN_DEF
1187 feature = Feature.TRAILING_COMMA_IN_CALL
1189 for ch in n.children:
1190 if ch.type in STARS:
1191 features.add(feature)
1193 if ch.type == syms.argument:
1194 for argch in ch.children:
1195 if argch.type in STARS:
1196 features.add(feature)
1198 # Python 2 only features (for its deprecation) except for integers, see above
1199 elif n.type == syms.print_stmt:
1200 features.add(Feature.PRINT_STMT)
1201 elif n.type == syms.exec_stmt:
1202 features.add(Feature.EXEC_STMT)
1203 elif n.type == syms.tfpdef:
1204 # def set_position((x, y), value):
1206 features.add(Feature.AUTOMATIC_PARAMETER_UNPACKING)
1207 elif n.type == syms.except_clause:
1210 # except Exception, err:
1212 if len(n.children) >= 4:
1213 if n.children[-2].type == token.COMMA:
1214 features.add(Feature.COMMA_STYLE_EXCEPT)
1215 elif n.type == syms.raise_stmt:
1216 # raise Exception, "msg"
1217 if len(n.children) >= 4:
1218 if n.children[-2].type == token.COMMA:
1219 features.add(Feature.COMMA_STYLE_RAISE)
1220 elif n.type == token.BACKQUOTE:
1221 # `i'm surprised this ever existed`
1222 features.add(Feature.BACKQUOTE_REPR)
1227 def detect_target_versions(node: Node) -> Set[TargetVersion]:
1228 """Detect the version to target based on the nodes used."""
1229 features = get_features_used(node)
1231 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
1235 def get_future_imports(node: Node) -> Set[str]:
1236 """Return a set of __future__ imports in the file."""
1237 imports: Set[str] = set()
1239 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1240 for child in children:
1241 if isinstance(child, Leaf):
1242 if child.type == token.NAME:
1245 elif child.type == syms.import_as_name:
1246 orig_name = child.children[0]
1247 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1248 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1249 yield orig_name.value
1251 elif child.type == syms.import_as_names:
1252 yield from get_imports_from_children(child.children)
1255 raise AssertionError("Invalid syntax parsing imports")
1257 for child in node.children:
1258 if child.type != syms.simple_stmt:
1261 first_child = child.children[0]
1262 if isinstance(first_child, Leaf):
1263 # Continue looking if we see a docstring; otherwise stop.
1265 len(child.children) == 2
1266 and first_child.type == token.STRING
1267 and child.children[1].type == token.NEWLINE
1273 elif first_child.type == syms.import_from:
1274 module_name = first_child.children[1]
1275 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1278 imports |= set(get_imports_from_children(first_child.children[3:]))
1285 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1286 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1288 src_ast = parse_ast(src)
1289 except Exception as exc:
1290 raise AssertionError(
1291 "cannot use --safe with this file; failed to parse source file."
1295 dst_ast = parse_ast(dst)
1296 except Exception as exc:
1297 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1298 raise AssertionError(
1299 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1300 "Please report a bug on https://github.com/psf/black/issues. "
1301 f"This invalid output might be helpful: {log}"
1304 src_ast_str = "\n".join(stringify_ast(src_ast))
1305 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1306 if src_ast_str != dst_ast_str:
1307 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1308 raise AssertionError(
1309 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1310 f" source on pass {pass_num}. Please report a bug on "
1311 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1315 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1316 """Raise AssertionError if `dst` reformats differently the second time."""
1317 newdst = format_str(dst, mode=mode)
1321 diff(src, dst, "source", "first pass"),
1322 diff(dst, newdst, "first pass", "second pass"),
1324 raise AssertionError(
1325 "INTERNAL ERROR: Black produced different code on the second pass of the"
1326 " formatter. Please report a bug on https://github.com/psf/black/issues."
1327 f" This diff might be helpful: {log}"
1332 def nullcontext() -> Iterator[None]:
1333 """Return an empty context manager.
1335 To be used like `nullcontext` in Python 3.7.
1340 def patch_click() -> None:
1341 """Make Click not crash on Python 3.6 with LANG=C.
1343 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1344 default which restricts paths that it can access during the lifetime of the
1345 application. Click refuses to work in this scenario by raising a RuntimeError.
1347 In case of Black the likelihood that non-ASCII characters are going to be used in
1348 file paths is minimal since it's Python source code. Moreover, this crash was
1349 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1352 from click import core
1353 from click import _unicodefun
1354 except ModuleNotFoundError:
1357 for module in (core, _unicodefun):
1358 if hasattr(module, "_verify_python3_env"):
1359 module._verify_python3_env = lambda: None # type: ignore
1360 if hasattr(module, "_verify_python_env"):
1361 module._verify_python_env = lambda: None # type: ignore
1364 def patched_main() -> None:
1365 maybe_install_uvloop()
1371 if __name__ == "__main__":