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
33 from dataclasses import replace
36 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
37 from black.const import STDIN_PLACEHOLDER
38 from black.nodes import STARS, syms, is_simple_decorator_expression
39 from black.lines import Line, EmptyLineTracker
40 from black.linegen import transform_line, LineGenerator, LN
41 from black.comments import normalize_fmt_off
42 from black.mode import Mode, TargetVersion
43 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
44 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
45 from black.concurrency import cancel, shutdown, maybe_install_uvloop
46 from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
47 from black.report import Report, Changed, NothingChanged
48 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
49 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
50 from black.files import wrap_stream_for_windows
51 from black.parsing import InvalidInput # noqa F401
52 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
53 from black.handle_ipynb_magics import (
56 remove_trailing_semicolon,
57 put_trailing_semicolon_back,
59 jupyter_dependencies_are_installed,
64 from blib2to3.pytree import Node, Leaf
65 from blib2to3.pgen2 import token
67 from _black_version import version as __version__
75 class WriteBack(Enum):
83 def from_configuration(
84 cls, *, check: bool, diff: bool, color: bool = False
86 if check and not diff:
92 return cls.DIFF if diff else cls.YES
95 # Legacy name, left for integrations.
98 DEFAULT_WORKERS = os.cpu_count()
101 def read_pyproject_toml(
102 ctx: click.Context, param: click.Parameter, value: Optional[str]
104 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
106 Returns the path to a successfully found and read configuration file, None
110 value = find_pyproject_toml(ctx.params.get("src", ()))
115 config = parse_pyproject_toml(value)
116 except (OSError, ValueError) as e:
117 raise click.FileError(
118 filename=value, hint=f"Error reading configuration file: {e}"
124 # Sanitize the values to be Click friendly. For more information please see:
125 # https://github.com/psf/black/issues/1458
126 # https://github.com/pallets/click/issues/1567
128 k: str(v) if not isinstance(v, (list, dict)) else v
129 for k, v in config.items()
132 target_version = config.get("target_version")
133 if target_version is not None and not isinstance(target_version, list):
134 raise click.BadOptionUsage(
135 "target-version", "Config key target-version must be a list"
138 default_map: Dict[str, Any] = {}
140 default_map.update(ctx.default_map)
141 default_map.update(config)
143 ctx.default_map = default_map
147 def target_version_option_callback(
148 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
149 ) -> List[TargetVersion]:
150 """Compute the target versions from a --target-version flag.
152 This is its own function because mypy couldn't infer the type correctly
153 when it was a lambda, causing mypyc trouble.
155 return [TargetVersion[val.upper()] for val in v]
158 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
159 """Compile a regular expression string in `regex`.
161 If it contains newlines, use verbose mode.
164 regex = "(?x)" + regex
165 compiled: Pattern[str] = re.compile(regex)
171 param: click.Parameter,
172 value: Optional[str],
173 ) -> Optional[Pattern[str]]:
175 return re_compile_maybe_verbose(value) if value is not None else None
177 raise click.BadParameter("Not a valid regular expression") from None
180 @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
181 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
186 default=DEFAULT_LINE_LENGTH,
187 help="How many characters per line to allow.",
193 type=click.Choice([v.name.lower() for v in TargetVersion]),
194 callback=target_version_option_callback,
197 "Python versions that should be supported by Black's output. [default: per-file"
205 "Format all input files like typing stubs regardless of file extension (useful"
206 " when piping source on standard input)."
213 "Format all input files like Jupyter Notebooks regardless of file extension "
214 "(useful when piping source on standard input)."
219 "--skip-string-normalization",
221 help="Don't normalize string quotes or prefixes.",
225 "--skip-magic-trailing-comma",
227 help="Don't use trailing commas as a reason to split lines.",
230 "--experimental-string-processing",
234 "Experimental option that performs more normalization on string literals."
235 " Currently disabled because it leads to some crashes."
242 "Don't write the files back, just return the status. Return code 0 means"
243 " nothing would change. Return code 1 means some files would be reformatted."
244 " Return code 123 means there was an internal error."
250 help="Don't write the files back, just output a diff for each file on stdout.",
253 "--color/--no-color",
255 help="Show colored diff. Only applies when `--diff` is given.",
260 help="If --fast given, skip temporary sanity checks. [default: --safe]",
263 "--required-version",
266 "Require a specific version of Black to be running (useful for unifying results"
267 " across many environments e.g. with a pyproject.toml file)."
273 default=DEFAULT_INCLUDES,
274 callback=validate_regex,
276 "A regular expression that matches files and directories that should be"
277 " included on recursive searches. An empty value means all files are included"
278 " regardless of the name. Use forward slashes for directories on all platforms"
279 " (Windows, too). Exclusions are calculated first, inclusions later."
286 callback=validate_regex,
288 "A regular expression that matches files and directories that should be"
289 " excluded on recursive searches. An empty value means no paths are excluded."
290 " Use forward slashes for directories on all platforms (Windows, too)."
291 " Exclusions are calculated first, inclusions later. [default:"
292 f" {DEFAULT_EXCLUDES}]"
299 callback=validate_regex,
301 "Like --exclude, but adds additional files and directories on top of the"
302 " excluded ones. (Useful if you simply want to add to the default)"
308 callback=validate_regex,
310 "Like --exclude, but files and directories matching this regex will be "
311 "excluded even when they are passed explicitly as arguments."
318 "The name of the file when passing it through stdin. Useful to make "
319 "sure Black will respect --force-exclude option on some "
320 "editors that rely on using stdin."
326 type=click.IntRange(min=1),
327 default=DEFAULT_WORKERS,
329 help="Number of parallel workers",
336 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
337 " those with 2>/dev/null."
345 "Also emit messages to stderr about files that were not changed or were ignored"
346 " due to exclusion patterns."
349 @click.version_option(version=__version__)
354 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
370 callback=read_pyproject_toml,
371 help="Read configuration from FILE path.",
378 target_version: List[TargetVersion],
385 skip_string_normalization: bool,
386 skip_magic_trailing_comma: bool,
387 experimental_string_processing: bool,
390 required_version: str,
391 include: Pattern[str],
392 exclude: Optional[Pattern[str]],
393 extend_exclude: Optional[Pattern[str]],
394 force_exclude: Optional[Pattern[str]],
395 stdin_filename: Optional[str],
397 src: Tuple[str, ...],
398 config: Optional[str],
400 """The uncompromising code formatter."""
401 if config and verbose:
402 out(f"Using configuration from {config}.", bold=False, fg="blue")
404 error_msg = "Oh no! 💥 💔 💥"
405 if required_version and required_version != __version__:
407 f"{error_msg} The required version `{required_version}` does not match"
408 f" the running version `{__version__}`!"
412 err("Cannot pass both `pyi` and `ipynb` flags!")
415 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
417 versions = set(target_version)
419 # We'll autodetect later.
422 target_versions=versions,
423 line_length=line_length,
426 string_normalization=not skip_string_normalization,
427 magic_trailing_comma=not skip_magic_trailing_comma,
428 experimental_string_processing=experimental_string_processing,
432 # Run in quiet mode by default with -c; the extra output isn't useful.
433 # You can still pass -v to get verbose output.
436 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
440 content=code, fast=fast, write_back=write_back, mode=mode, report=report
444 sources = get_sources(
451 extend_exclude=extend_exclude,
452 force_exclude=force_exclude,
454 stdin_filename=stdin_filename,
456 except GitWildMatchPatternError:
461 "No Python files are present to be formatted. Nothing to do 😴",
467 if len(sources) == 1:
471 write_back=write_back,
479 write_back=write_back,
485 if verbose or not quiet:
486 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
488 click.echo(str(report), err=True)
489 ctx.exit(report.return_code)
495 src: Tuple[str, ...],
498 include: Pattern[str],
499 exclude: Optional[Pattern[str]],
500 extend_exclude: Optional[Pattern[str]],
501 force_exclude: Optional[Pattern[str]],
503 stdin_filename: Optional[str],
505 """Compute the set of files to be formatted."""
507 root = find_project_root(src)
508 sources: Set[Path] = set()
509 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
512 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
513 gitignore = get_gitignore(root)
518 if s == "-" and stdin_filename:
519 p = Path(stdin_filename)
525 if is_stdin or p.is_file():
526 normalized_path = normalize_path_maybe_ignore(p, root, report)
527 if normalized_path is None:
530 normalized_path = "/" + normalized_path
531 # Hard-exclude any files that matches the `--force-exclude` regex.
533 force_exclude_match = force_exclude.search(normalized_path)
535 force_exclude_match = None
536 if force_exclude_match and force_exclude_match.group(0):
537 report.path_ignored(p, "matches the --force-exclude regular expression")
541 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
543 if p.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
544 verbose=verbose, quiet=quiet
567 err(f"invalid path: {s}")
572 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
575 Exit if there is no `src` provided for formatting
578 if verbose or not quiet:
584 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
587 Reformat and print out `content` without spawning child processes.
588 Similar to `reformat_one`, but for string content.
590 `fast`, `write_back`, and `mode` options are passed to
591 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
593 path = Path("<string>")
596 if format_stdin_to_stdout(
597 content=content, fast=fast, write_back=write_back, mode=mode
599 changed = Changed.YES
600 report.done(path, changed)
601 except Exception as exc:
603 traceback.print_exc()
604 report.failed(path, str(exc))
608 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
610 """Reformat a single file under `src` without spawning child processes.
612 `fast`, `write_back`, and `mode` options are passed to
613 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
620 elif str(src).startswith(STDIN_PLACEHOLDER):
622 # Use the original name again in case we want to print something
624 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
629 if src.suffix == ".pyi":
630 mode = replace(mode, is_pyi=True)
631 elif src.suffix == ".ipynb":
632 mode = replace(mode, is_ipynb=True)
633 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
634 changed = Changed.YES
637 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
638 cache = read_cache(mode)
639 res_src = src.resolve()
640 res_src_s = str(res_src)
641 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
642 changed = Changed.CACHED
643 if changed is not Changed.CACHED and format_file_in_place(
644 src, fast=fast, write_back=write_back, mode=mode
646 changed = Changed.YES
647 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
648 write_back is WriteBack.CHECK and changed is Changed.NO
650 write_cache(cache, [src], mode)
651 report.done(src, changed)
652 except Exception as exc:
654 traceback.print_exc()
655 report.failed(src, str(exc))
661 write_back: WriteBack,
664 workers: Optional[int],
666 """Reformat multiple files using a ProcessPoolExecutor."""
668 loop = asyncio.get_event_loop()
669 worker_count = workers if workers is not None else DEFAULT_WORKERS
670 if sys.platform == "win32":
671 # Work around https://bugs.python.org/issue26903
672 worker_count = min(worker_count, 60)
674 executor = ProcessPoolExecutor(max_workers=worker_count)
675 except (ImportError, OSError):
676 # we arrive here if the underlying system does not support multi-processing
677 # like in AWS Lambda or Termux, in which case we gracefully fallback to
678 # a ThreadPoolExecutor with just a single worker (more workers would not do us
679 # any good due to the Global Interpreter Lock)
680 executor = ThreadPoolExecutor(max_workers=1)
683 loop.run_until_complete(
687 write_back=write_back,
696 if executor is not None:
700 async def schedule_formatting(
703 write_back: WriteBack,
706 loop: asyncio.AbstractEventLoop,
709 """Run formatting of `sources` in parallel using the provided `executor`.
711 (Use ProcessPoolExecutors for actual parallelism.)
713 `write_back`, `fast`, and `mode` options are passed to
714 :func:`format_file_in_place`.
717 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
718 cache = read_cache(mode)
719 sources, cached = filter_cached(cache, sources)
720 for src in sorted(cached):
721 report.done(src, Changed.CACHED)
726 sources_to_cache = []
728 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
729 # For diff output, we need locks to ensure we don't interleave output
730 # from different processes.
732 lock = manager.Lock()
734 asyncio.ensure_future(
735 loop.run_in_executor(
736 executor, format_file_in_place, src, fast, mode, write_back, lock
739 for src in sorted(sources)
741 pending = tasks.keys()
743 loop.add_signal_handler(signal.SIGINT, cancel, pending)
744 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
745 except NotImplementedError:
746 # There are no good alternatives for these on Windows.
749 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
751 src = tasks.pop(task)
753 cancelled.append(task)
754 elif task.exception():
755 report.failed(src, str(task.exception()))
757 changed = Changed.YES if task.result() else Changed.NO
758 # If the file was written back or was successfully checked as
759 # well-formatted, store this information in the cache.
760 if write_back is WriteBack.YES or (
761 write_back is WriteBack.CHECK and changed is Changed.NO
763 sources_to_cache.append(src)
764 report.done(src, changed)
766 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
768 write_cache(cache, sources_to_cache, mode)
771 def format_file_in_place(
775 write_back: WriteBack = WriteBack.NO,
776 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
778 """Format file under `src` path. Return True if changed.
780 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
782 `mode` and `fast` options are passed to :func:`format_file_contents`.
784 if src.suffix == ".pyi":
785 mode = replace(mode, is_pyi=True)
786 elif src.suffix == ".ipynb":
787 mode = replace(mode, is_ipynb=True)
789 then = datetime.utcfromtimestamp(src.stat().st_mtime)
790 with open(src, "rb") as buf:
791 src_contents, encoding, newline = decode_bytes(buf.read())
793 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
794 except NothingChanged:
796 except JSONDecodeError:
798 f"File '{src}' cannot be parsed as valid Jupyter notebook."
801 if write_back == WriteBack.YES:
802 with open(src, "w", encoding=encoding, newline=newline) as f:
803 f.write(dst_contents)
804 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
805 now = datetime.utcnow()
806 src_name = f"{src}\t{then} +0000"
807 dst_name = f"{src}\t{now} +0000"
809 diff_contents = ipynb_diff(src_contents, dst_contents, src_name, dst_name)
811 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
813 if write_back == WriteBack.COLOR_DIFF:
814 diff_contents = color_diff(diff_contents)
816 with lock or nullcontext():
817 f = io.TextIOWrapper(
823 f = wrap_stream_for_windows(f)
824 f.write(diff_contents)
830 def format_stdin_to_stdout(
833 content: Optional[str] = None,
834 write_back: WriteBack = WriteBack.NO,
837 """Format file on stdin. Return True if changed.
839 If content is None, it's read from sys.stdin.
841 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
842 write a diff to stdout. The `mode` argument is passed to
843 :func:`format_file_contents`.
845 then = datetime.utcnow()
848 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
850 src, encoding, newline = content, "utf-8", ""
854 dst = format_file_contents(src, fast=fast, mode=mode)
857 except NothingChanged:
861 f = io.TextIOWrapper(
862 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
864 if write_back == WriteBack.YES:
865 # Make sure there's a newline after the content
866 if dst and dst[-1] != "\n":
869 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
870 now = datetime.utcnow()
871 src_name = f"STDIN\t{then} +0000"
872 dst_name = f"STDOUT\t{now} +0000"
873 d = diff(src, dst, src_name, dst_name)
874 if write_back == WriteBack.COLOR_DIFF:
876 f = wrap_stream_for_windows(f)
881 def check_stability_and_equivalence(
882 src_contents: str, dst_contents: str, *, mode: Mode
884 """Perform stability and equivalence checks.
886 Raise AssertionError if source and destination contents are not
887 equivalent, or if a second pass of the formatter would format the
890 assert_equivalent(src_contents, dst_contents)
892 # Forced second pass to work around optional trailing commas (becoming
893 # forced trailing commas on pass 2) interacting differently with optional
894 # parentheses. Admittedly ugly.
895 dst_contents_pass2 = format_str(dst_contents, mode=mode)
896 if dst_contents != dst_contents_pass2:
897 dst_contents = dst_contents_pass2
898 assert_equivalent(src_contents, dst_contents, pass_num=2)
899 assert_stable(src_contents, dst_contents, mode=mode)
900 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
901 # the same as `dst_contents_pass2`.
904 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
905 """Reformat contents of a file and return new contents.
907 If `fast` is False, additionally confirm that the reformatted code is
908 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
909 `mode` is passed to :func:`format_str`.
911 if not src_contents.strip():
915 dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
917 dst_contents = format_str(src_contents, mode=mode)
918 if src_contents == dst_contents:
921 if not fast and not mode.is_ipynb:
922 # Jupyter notebooks will already have been checked above.
923 check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
927 def validate_cell(src: str) -> None:
928 """Check that cell does not already contain TransformerManager transformations.
930 If a cell contains ``!ls``, then it'll be transformed to
931 ``get_ipython().system('ls')``. However, if the cell originally contained
932 ``get_ipython().system('ls')``, then it would get transformed in the same way:
934 >>> TransformerManager().transform_cell("get_ipython().system('ls')")
935 "get_ipython().system('ls')\n"
936 >>> TransformerManager().transform_cell("!ls")
937 "get_ipython().system('ls')\n"
939 Due to the impossibility of safely roundtripping in such situations, cells
940 containing transformed magics will be ignored.
942 if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
946 def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
947 """Format code in given cell of Jupyter notebook.
951 - if cell has trailing semicolon, remove it;
952 - if cell has IPython magics, mask them;
954 - reinstate IPython magics;
955 - reinstate trailing semicolon (if originally present);
956 - strip trailing newlines.
958 Cells with syntax errors will not be processed, as they
959 could potentially be automagics or multi-line magics, which
960 are currently not supported.
963 src_without_trailing_semicolon, has_trailing_semicolon = remove_trailing_semicolon(
967 masked_src, replacements = mask_cell(src_without_trailing_semicolon)
969 raise NothingChanged from None
970 masked_dst = format_str(masked_src, mode=mode)
972 check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
973 dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
974 dst = put_trailing_semicolon_back(
975 dst_without_trailing_semicolon, has_trailing_semicolon
977 dst = dst.rstrip("\n")
979 raise NothingChanged from None
983 def validate_metadata(nb: MutableMapping[str, Any]) -> None:
984 """If notebook is marked as non-Python, don't format it.
986 All notebook metadata fields are optional, see
987 https://nbformat.readthedocs.io/en/latest/format_description.html. So
988 if a notebook has empty metadata, we will try to parse it anyway.
990 language = nb.get("metadata", {}).get("language_info", {}).get("name", None)
991 if language is not None and language != "python":
992 raise NothingChanged from None
995 def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
996 """Format Jupyter notebook.
998 Operate cell-by-cell, only on code cells, only for Python notebooks.
999 If the ``.ipynb`` originally had a trailing newline, it'll be preserved.
1001 trailing_newline = src_contents[-1] == "\n"
1003 nb = json.loads(src_contents)
1004 validate_metadata(nb)
1005 for cell in nb["cells"]:
1006 if cell.get("cell_type", None) == "code":
1008 src = "".join(cell["source"])
1009 dst = format_cell(src, fast=fast, mode=mode)
1010 except NothingChanged:
1013 cell["source"] = dst.splitlines(keepends=True)
1016 dst_contents = json.dumps(nb, indent=1, ensure_ascii=False)
1017 if trailing_newline:
1018 dst_contents = dst_contents + "\n"
1021 raise NothingChanged
1024 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
1025 """Reformat a string and return new contents.
1027 `mode` determines formatting options, such as how many characters per line are
1031 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
1032 def f(arg: str = "") -> None:
1035 A more complex example:
1038 ... black.format_str(
1039 ... "def f(arg:str='')->None: hey",
1040 ... mode=black.Mode(
1041 ... target_versions={black.TargetVersion.PY36},
1043 ... string_normalization=False,
1054 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1056 future_imports = get_future_imports(src_node)
1057 if mode.target_versions:
1058 versions = mode.target_versions
1060 versions = detect_target_versions(src_node)
1061 normalize_fmt_off(src_node)
1062 lines = LineGenerator(
1064 remove_u_prefix="unicode_literals" in future_imports
1065 or supports_feature(versions, Feature.UNICODE_LITERALS),
1067 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
1068 empty_line = Line(mode=mode)
1070 split_line_features = {
1072 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
1073 if supports_feature(versions, feature)
1075 for current_line in lines.visit(src_node):
1076 dst_contents.append(str(empty_line) * after)
1077 before, after = elt.maybe_empty_lines(current_line)
1078 dst_contents.append(str(empty_line) * before)
1079 for line in transform_line(
1080 current_line, mode=mode, features=split_line_features
1082 dst_contents.append(str(line))
1083 return "".join(dst_contents)
1086 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
1087 """Return a tuple of (decoded_contents, encoding, newline).
1089 `newline` is either CRLF or LF but `decoded_contents` is decoded with
1090 universal newlines (i.e. only contains LF).
1092 srcbuf = io.BytesIO(src)
1093 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
1095 return "", encoding, "\n"
1097 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
1099 with io.TextIOWrapper(srcbuf, encoding) as tiow:
1100 return tiow.read(), encoding, newline
1103 def get_features_used(node: Node) -> Set[Feature]:
1104 """Return a set of (relatively) new Python features used in this file.
1106 Currently looking for:
1108 - underscores in numeric literals;
1109 - trailing commas after * or ** in function signatures and calls;
1110 - positional only arguments in function signatures and lambdas;
1111 - assignment expression;
1112 - relaxed decorator syntax;
1114 features: Set[Feature] = set()
1115 for n in node.pre_order():
1116 if n.type == token.STRING:
1117 value_head = n.value[:2] # type: ignore
1118 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1119 features.add(Feature.F_STRINGS)
1121 elif n.type == token.NUMBER:
1122 if "_" in n.value: # type: ignore
1123 features.add(Feature.NUMERIC_UNDERSCORES)
1125 elif n.type == token.SLASH:
1126 if n.parent and n.parent.type in {
1131 features.add(Feature.POS_ONLY_ARGUMENTS)
1133 elif n.type == token.COLONEQUAL:
1134 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
1136 elif n.type == syms.decorator:
1137 if len(n.children) > 1 and not is_simple_decorator_expression(
1140 features.add(Feature.RELAXED_DECORATORS)
1143 n.type in {syms.typedargslist, syms.arglist}
1145 and n.children[-1].type == token.COMMA
1147 if n.type == syms.typedargslist:
1148 feature = Feature.TRAILING_COMMA_IN_DEF
1150 feature = Feature.TRAILING_COMMA_IN_CALL
1152 for ch in n.children:
1153 if ch.type in STARS:
1154 features.add(feature)
1156 if ch.type == syms.argument:
1157 for argch in ch.children:
1158 if argch.type in STARS:
1159 features.add(feature)
1164 def detect_target_versions(node: Node) -> Set[TargetVersion]:
1165 """Detect the version to target based on the nodes used."""
1166 features = get_features_used(node)
1168 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
1172 def get_future_imports(node: Node) -> Set[str]:
1173 """Return a set of __future__ imports in the file."""
1174 imports: Set[str] = set()
1176 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1177 for child in children:
1178 if isinstance(child, Leaf):
1179 if child.type == token.NAME:
1182 elif child.type == syms.import_as_name:
1183 orig_name = child.children[0]
1184 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1185 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1186 yield orig_name.value
1188 elif child.type == syms.import_as_names:
1189 yield from get_imports_from_children(child.children)
1192 raise AssertionError("Invalid syntax parsing imports")
1194 for child in node.children:
1195 if child.type != syms.simple_stmt:
1198 first_child = child.children[0]
1199 if isinstance(first_child, Leaf):
1200 # Continue looking if we see a docstring; otherwise stop.
1202 len(child.children) == 2
1203 and first_child.type == token.STRING
1204 and child.children[1].type == token.NEWLINE
1210 elif first_child.type == syms.import_from:
1211 module_name = first_child.children[1]
1212 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1215 imports |= set(get_imports_from_children(first_child.children[3:]))
1222 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1223 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1225 src_ast = parse_ast(src)
1226 except Exception as exc:
1227 raise AssertionError(
1228 "cannot use --safe with this file; failed to parse source file."
1232 dst_ast = parse_ast(dst)
1233 except Exception as exc:
1234 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1235 raise AssertionError(
1236 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1237 "Please report a bug on https://github.com/psf/black/issues. "
1238 f"This invalid output might be helpful: {log}"
1241 src_ast_str = "\n".join(stringify_ast(src_ast))
1242 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1243 if src_ast_str != dst_ast_str:
1244 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1245 raise AssertionError(
1246 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1247 f" source on pass {pass_num}. Please report a bug on "
1248 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1252 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1253 """Raise AssertionError if `dst` reformats differently the second time."""
1254 newdst = format_str(dst, mode=mode)
1258 diff(src, dst, "source", "first pass"),
1259 diff(dst, newdst, "first pass", "second pass"),
1261 raise AssertionError(
1262 "INTERNAL ERROR: Black produced different code on the second pass of the"
1263 " formatter. Please report a bug on https://github.com/psf/black/issues."
1264 f" This diff might be helpful: {log}"
1269 def nullcontext() -> Iterator[None]:
1270 """Return an empty context manager.
1272 To be used like `nullcontext` in Python 3.7.
1277 def patch_click() -> None:
1278 """Make Click not crash on Python 3.6 with LANG=C.
1280 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1281 default which restricts paths that it can access during the lifetime of the
1282 application. Click refuses to work in this scenario by raising a RuntimeError.
1284 In case of Black the likelihood that non-ASCII characters are going to be used in
1285 file paths is minimal since it's Python source code. Moreover, this crash was
1286 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1289 from click import core
1290 from click import _unicodefun # type: ignore
1291 except ModuleNotFoundError:
1294 for module in (core, _unicodefun):
1295 if hasattr(module, "_verify_python3_env"):
1296 module._verify_python3_env = lambda: None # type: ignore
1297 if hasattr(module, "_verify_python_env"):
1298 module._verify_python_env = lambda: None # type: ignore
1301 def patched_main() -> None:
1302 maybe_install_uvloop()
1308 if __name__ == "__main__":