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
32 from dataclasses import replace
35 from black.const import DEFAULT_LINE_LENGTH, DEFAULT_INCLUDES, DEFAULT_EXCLUDES
36 from black.const import STDIN_PLACEHOLDER
37 from black.nodes import STARS, syms, is_simple_decorator_expression
38 from black.lines import Line, EmptyLineTracker
39 from black.linegen import transform_line, LineGenerator, LN
40 from black.comments import normalize_fmt_off
41 from black.mode import Mode, TargetVersion
42 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
43 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
44 from black.concurrency import cancel, shutdown, maybe_install_uvloop
45 from black.output import dump_to_file, ipynb_diff, diff, color_diff, out, err
46 from black.report import Report, Changed, NothingChanged
47 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
48 from black.files import gen_python_files, get_gitignore, normalize_path_maybe_ignore
49 from black.files import wrap_stream_for_windows
50 from black.parsing import InvalidInput # noqa F401
51 from black.parsing import lib2to3_parse, parse_ast, stringify_ast
52 from black.handle_ipynb_magics import (
55 remove_trailing_semicolon,
56 put_trailing_semicolon_back,
58 jupyter_dependencies_are_installed,
63 from blib2to3.pytree import Node, Leaf
64 from blib2to3.pgen2 import token
66 from _black_version import version as __version__
74 class WriteBack(Enum):
82 def from_configuration(
83 cls, *, check: bool, diff: bool, color: bool = False
85 if check and not diff:
91 return cls.DIFF if diff else cls.YES
94 # Legacy name, left for integrations.
98 def read_pyproject_toml(
99 ctx: click.Context, param: click.Parameter, value: Optional[str]
101 """Inject Black configuration from "pyproject.toml" into defaults in `ctx`.
103 Returns the path to a successfully found and read configuration file, None
107 value = find_pyproject_toml(ctx.params.get("src", ()))
112 config = parse_pyproject_toml(value)
113 except (OSError, ValueError) as e:
114 raise click.FileError(
115 filename=value, hint=f"Error reading configuration file: {e}"
121 # Sanitize the values to be Click friendly. For more information please see:
122 # https://github.com/psf/black/issues/1458
123 # https://github.com/pallets/click/issues/1567
125 k: str(v) if not isinstance(v, (list, dict)) else v
126 for k, v in config.items()
129 target_version = config.get("target_version")
130 if target_version is not None and not isinstance(target_version, list):
131 raise click.BadOptionUsage(
132 "target-version", "Config key target-version must be a list"
135 default_map: Dict[str, Any] = {}
137 default_map.update(ctx.default_map)
138 default_map.update(config)
140 ctx.default_map = default_map
144 def target_version_option_callback(
145 c: click.Context, p: Union[click.Option, click.Parameter], v: Tuple[str, ...]
146 ) -> List[TargetVersion]:
147 """Compute the target versions from a --target-version flag.
149 This is its own function because mypy couldn't infer the type correctly
150 when it was a lambda, causing mypyc trouble.
152 return [TargetVersion[val.upper()] for val in v]
155 def re_compile_maybe_verbose(regex: str) -> Pattern[str]:
156 """Compile a regular expression string in `regex`.
158 If it contains newlines, use verbose mode.
161 regex = "(?x)" + regex
162 compiled: Pattern[str] = re.compile(regex)
168 param: click.Parameter,
169 value: Optional[str],
170 ) -> Optional[Pattern]:
172 return re_compile_maybe_verbose(value) if value is not None else None
174 raise click.BadParameter("Not a valid regular expression")
177 @click.command(context_settings=dict(help_option_names=["-h", "--help"]))
178 @click.option("-c", "--code", type=str, help="Format the code passed in as a string.")
183 default=DEFAULT_LINE_LENGTH,
184 help="How many characters per line to allow.",
190 type=click.Choice([v.name.lower() for v in TargetVersion]),
191 callback=target_version_option_callback,
194 "Python versions that should be supported by Black's output. [default: per-file"
202 "Format all input files like typing stubs regardless of file extension (useful"
203 " when piping source on standard input)."
210 "Format all input files like Jupyter Notebooks regardless of file extension "
211 "(useful when piping source on standard input)."
216 "--skip-string-normalization",
218 help="Don't normalize string quotes or prefixes.",
222 "--skip-magic-trailing-comma",
224 help="Don't use trailing commas as a reason to split lines.",
227 "--experimental-string-processing",
231 "Experimental option that performs more normalization on string literals."
232 " Currently disabled because it leads to some crashes."
239 "Don't write the files back, just return the status. Return code 0 means"
240 " nothing would change. Return code 1 means some files would be reformatted."
241 " Return code 123 means there was an internal error."
247 help="Don't write the files back, just output a diff for each file on stdout.",
250 "--color/--no-color",
252 help="Show colored diff. Only applies when `--diff` is given.",
257 help="If --fast given, skip temporary sanity checks. [default: --safe]",
260 "--required-version",
263 "Require a specific version of Black to be running (useful for unifying results"
264 " across many environments e.g. with a pyproject.toml file)."
270 default=DEFAULT_INCLUDES,
271 callback=validate_regex,
273 "A regular expression that matches files and directories that should be"
274 " included on recursive searches. An empty value means all files are included"
275 " regardless of the name. Use forward slashes for directories on all platforms"
276 " (Windows, too). Exclusions are calculated first, inclusions later."
283 callback=validate_regex,
285 "A regular expression that matches files and directories that should be"
286 " excluded on recursive searches. An empty value means no paths are excluded."
287 " Use forward slashes for directories on all platforms (Windows, too)."
288 " Exclusions are calculated first, inclusions later. [default:"
289 f" {DEFAULT_EXCLUDES}]"
296 callback=validate_regex,
298 "Like --exclude, but adds additional files and directories on top of the"
299 " excluded ones. (Useful if you simply want to add to the default)"
305 callback=validate_regex,
307 "Like --exclude, but files and directories matching this regex will be "
308 "excluded even when they are passed explicitly as arguments."
315 "The name of the file when passing it through stdin. Useful to make "
316 "sure Black will respect --force-exclude option on some "
317 "editors that rely on using stdin."
325 "Don't emit non-error messages to stderr. Errors are still emitted; silence"
326 " those with 2>/dev/null."
334 "Also emit messages to stderr about files that were not changed or were ignored"
335 " due to exclusion patterns."
338 @click.version_option(version=__version__)
343 exists=True, file_okay=True, dir_okay=True, readable=True, allow_dash=True
359 callback=read_pyproject_toml,
360 help="Read configuration from FILE path.",
367 target_version: List[TargetVersion],
374 skip_string_normalization: bool,
375 skip_magic_trailing_comma: bool,
376 experimental_string_processing: bool,
379 required_version: str,
381 exclude: Optional[Pattern],
382 extend_exclude: Optional[Pattern],
383 force_exclude: Optional[Pattern],
384 stdin_filename: Optional[str],
385 src: Tuple[str, ...],
386 config: Optional[str],
388 """The uncompromising code formatter."""
389 if config and verbose:
390 out(f"Using configuration from {config}.", bold=False, fg="blue")
392 error_msg = "Oh no! 💥 💔 💥"
393 if required_version and required_version != __version__:
395 f"{error_msg} The required version `{required_version}` does not match"
396 f" the running version `{__version__}`!"
400 err("Cannot pass both `pyi` and `ipynb` flags!")
403 write_back = WriteBack.from_configuration(check=check, diff=diff, color=color)
405 versions = set(target_version)
407 # We'll autodetect later.
410 target_versions=versions,
411 line_length=line_length,
414 string_normalization=not skip_string_normalization,
415 magic_trailing_comma=not skip_magic_trailing_comma,
416 experimental_string_processing=experimental_string_processing,
420 # Run in quiet mode by default with -c; the extra output isn't useful.
421 # You can still pass -v to get verbose output.
424 report = Report(check=check, diff=diff, quiet=quiet, verbose=verbose)
428 content=code, fast=fast, write_back=write_back, mode=mode, report=report
431 sources = get_sources(
438 extend_exclude=extend_exclude,
439 force_exclude=force_exclude,
441 stdin_filename=stdin_filename,
446 "No Python files are present to be formatted. Nothing to do 😴",
452 if len(sources) == 1:
456 write_back=write_back,
464 write_back=write_back,
469 if verbose or not quiet:
470 out(error_msg if report.return_code else "All done! ✨ 🍰 ✨")
472 click.echo(str(report), err=True)
473 ctx.exit(report.return_code)
479 src: Tuple[str, ...],
482 include: Pattern[str],
483 exclude: Optional[Pattern[str]],
484 extend_exclude: Optional[Pattern[str]],
485 force_exclude: Optional[Pattern[str]],
487 stdin_filename: Optional[str],
489 """Compute the set of files to be formatted."""
491 root = find_project_root(src)
492 sources: Set[Path] = set()
493 path_empty(src, "No Path provided. Nothing to do 😴", quiet, verbose, ctx)
496 exclude = re_compile_maybe_verbose(DEFAULT_EXCLUDES)
497 gitignore = get_gitignore(root)
502 if s == "-" and stdin_filename:
503 p = Path(stdin_filename)
509 if is_stdin or p.is_file():
510 normalized_path = normalize_path_maybe_ignore(p, root, report)
511 if normalized_path is None:
514 normalized_path = "/" + normalized_path
515 # Hard-exclude any files that matches the `--force-exclude` regex.
517 force_exclude_match = force_exclude.search(normalized_path)
519 force_exclude_match = None
520 if force_exclude_match and force_exclude_match.group(0):
521 report.path_ignored(p, "matches the --force-exclude regular expression")
525 p = Path(f"{STDIN_PLACEHOLDER}{str(p)}")
527 if p.suffix == ".ipynb" and not jupyter_dependencies_are_installed(
528 verbose=verbose, quiet=quiet
551 err(f"invalid path: {s}")
556 src: Sized, msg: str, quiet: bool, verbose: bool, ctx: click.Context
559 Exit if there is no `src` provided for formatting
562 if verbose or not quiet:
568 content: str, fast: bool, write_back: WriteBack, mode: Mode, report: Report
571 Reformat and print out `content` without spawning child processes.
572 Similar to `reformat_one`, but for string content.
574 `fast`, `write_back`, and `mode` options are passed to
575 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
577 path = Path("<string>")
580 if format_stdin_to_stdout(
581 content=content, fast=fast, write_back=write_back, mode=mode
583 changed = Changed.YES
584 report.done(path, changed)
585 except Exception as exc:
587 traceback.print_exc()
588 report.failed(path, str(exc))
592 src: Path, fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
594 """Reformat a single file under `src` without spawning child processes.
596 `fast`, `write_back`, and `mode` options are passed to
597 :func:`format_file_in_place` or :func:`format_stdin_to_stdout`.
604 elif str(src).startswith(STDIN_PLACEHOLDER):
606 # Use the original name again in case we want to print something
608 src = Path(str(src)[len(STDIN_PLACEHOLDER) :])
613 if src.suffix == ".pyi":
614 mode = replace(mode, is_pyi=True)
615 elif src.suffix == ".ipynb":
616 mode = replace(mode, is_ipynb=True)
617 if format_stdin_to_stdout(fast=fast, write_back=write_back, mode=mode):
618 changed = Changed.YES
621 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
622 cache = read_cache(mode)
623 res_src = src.resolve()
624 res_src_s = str(res_src)
625 if res_src_s in cache and cache[res_src_s] == get_cache_info(res_src):
626 changed = Changed.CACHED
627 if changed is not Changed.CACHED and format_file_in_place(
628 src, fast=fast, write_back=write_back, mode=mode
630 changed = Changed.YES
631 if (write_back is WriteBack.YES and changed is not Changed.CACHED) or (
632 write_back is WriteBack.CHECK and changed is Changed.NO
634 write_cache(cache, [src], mode)
635 report.done(src, changed)
636 except Exception as exc:
638 traceback.print_exc()
639 report.failed(src, str(exc))
643 sources: Set[Path], fast: bool, write_back: WriteBack, mode: Mode, report: "Report"
645 """Reformat multiple files using a ProcessPoolExecutor."""
647 loop = asyncio.get_event_loop()
648 worker_count = os.cpu_count()
649 if sys.platform == "win32":
650 # Work around https://bugs.python.org/issue26903
651 worker_count = min(worker_count, 60)
653 executor = ProcessPoolExecutor(max_workers=worker_count)
654 except (ImportError, OSError):
655 # we arrive here if the underlying system does not support multi-processing
656 # like in AWS Lambda or Termux, in which case we gracefully fallback to
657 # a ThreadPoolExecutor with just a single worker (more workers would not do us
658 # any good due to the Global Interpreter Lock)
659 executor = ThreadPoolExecutor(max_workers=1)
662 loop.run_until_complete(
666 write_back=write_back,
675 if executor is not None:
679 async def schedule_formatting(
682 write_back: WriteBack,
685 loop: asyncio.AbstractEventLoop,
688 """Run formatting of `sources` in parallel using the provided `executor`.
690 (Use ProcessPoolExecutors for actual parallelism.)
692 `write_back`, `fast`, and `mode` options are passed to
693 :func:`format_file_in_place`.
696 if write_back not in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
697 cache = read_cache(mode)
698 sources, cached = filter_cached(cache, sources)
699 for src in sorted(cached):
700 report.done(src, Changed.CACHED)
705 sources_to_cache = []
707 if write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
708 # For diff output, we need locks to ensure we don't interleave output
709 # from different processes.
711 lock = manager.Lock()
713 asyncio.ensure_future(
714 loop.run_in_executor(
715 executor, format_file_in_place, src, fast, mode, write_back, lock
718 for src in sorted(sources)
720 pending = tasks.keys()
722 loop.add_signal_handler(signal.SIGINT, cancel, pending)
723 loop.add_signal_handler(signal.SIGTERM, cancel, pending)
724 except NotImplementedError:
725 # There are no good alternatives for these on Windows.
728 done, _ = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
730 src = tasks.pop(task)
732 cancelled.append(task)
733 elif task.exception():
734 report.failed(src, str(task.exception()))
736 changed = Changed.YES if task.result() else Changed.NO
737 # If the file was written back or was successfully checked as
738 # well-formatted, store this information in the cache.
739 if write_back is WriteBack.YES or (
740 write_back is WriteBack.CHECK and changed is Changed.NO
742 sources_to_cache.append(src)
743 report.done(src, changed)
745 await asyncio.gather(*cancelled, loop=loop, return_exceptions=True)
747 write_cache(cache, sources_to_cache, mode)
750 def format_file_in_place(
754 write_back: WriteBack = WriteBack.NO,
755 lock: Any = None, # multiprocessing.Manager().Lock() is some crazy proxy
757 """Format file under `src` path. Return True if changed.
759 If `write_back` is DIFF, write a diff to stdout. If it is YES, write reformatted
761 `mode` and `fast` options are passed to :func:`format_file_contents`.
763 if src.suffix == ".pyi":
764 mode = replace(mode, is_pyi=True)
765 elif src.suffix == ".ipynb":
766 mode = replace(mode, is_ipynb=True)
768 then = datetime.utcfromtimestamp(src.stat().st_mtime)
769 with open(src, "rb") as buf:
770 src_contents, encoding, newline = decode_bytes(buf.read())
772 dst_contents = format_file_contents(src_contents, fast=fast, mode=mode)
773 except NothingChanged:
775 except JSONDecodeError:
776 raise ValueError(f"File '{src}' cannot be parsed as valid Jupyter notebook.")
778 if write_back == WriteBack.YES:
779 with open(src, "w", encoding=encoding, newline=newline) as f:
780 f.write(dst_contents)
781 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
782 now = datetime.utcnow()
783 src_name = f"{src}\t{then} +0000"
784 dst_name = f"{src}\t{now} +0000"
786 diff_contents = ipynb_diff(src_contents, dst_contents, src_name, dst_name)
788 diff_contents = diff(src_contents, dst_contents, src_name, dst_name)
790 if write_back == WriteBack.COLOR_DIFF:
791 diff_contents = color_diff(diff_contents)
793 with lock or nullcontext():
794 f = io.TextIOWrapper(
800 f = wrap_stream_for_windows(f)
801 f.write(diff_contents)
807 def format_stdin_to_stdout(
810 content: Optional[str] = None,
811 write_back: WriteBack = WriteBack.NO,
814 """Format file on stdin. Return True if changed.
816 If content is None, it's read from sys.stdin.
818 If `write_back` is YES, write reformatted code back to stdout. If it is DIFF,
819 write a diff to stdout. The `mode` argument is passed to
820 :func:`format_file_contents`.
822 then = datetime.utcnow()
825 src, encoding, newline = decode_bytes(sys.stdin.buffer.read())
827 src, encoding, newline = content, "utf-8", ""
831 dst = format_file_contents(src, fast=fast, mode=mode)
834 except NothingChanged:
838 f = io.TextIOWrapper(
839 sys.stdout.buffer, encoding=encoding, newline=newline, write_through=True
841 if write_back == WriteBack.YES:
842 # Make sure there's a newline after the content
843 if dst and dst[-1] != "\n":
846 elif write_back in (WriteBack.DIFF, WriteBack.COLOR_DIFF):
847 now = datetime.utcnow()
848 src_name = f"STDIN\t{then} +0000"
849 dst_name = f"STDOUT\t{now} +0000"
850 d = diff(src, dst, src_name, dst_name)
851 if write_back == WriteBack.COLOR_DIFF:
853 f = wrap_stream_for_windows(f)
858 def check_stability_and_equivalence(
859 src_contents: str, dst_contents: str, *, mode: Mode
861 """Perform stability and equivalence checks.
863 Raise AssertionError if source and destination contents are not
864 equivalent, or if a second pass of the formatter would format the
867 assert_equivalent(src_contents, dst_contents)
869 # Forced second pass to work around optional trailing commas (becoming
870 # forced trailing commas on pass 2) interacting differently with optional
871 # parentheses. Admittedly ugly.
872 dst_contents_pass2 = format_str(dst_contents, mode=mode)
873 if dst_contents != dst_contents_pass2:
874 dst_contents = dst_contents_pass2
875 assert_equivalent(src_contents, dst_contents, pass_num=2)
876 assert_stable(src_contents, dst_contents, mode=mode)
877 # Note: no need to explicitly call `assert_stable` if `dst_contents` was
878 # the same as `dst_contents_pass2`.
881 def format_file_contents(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
882 """Reformat contents of a file and return new contents.
884 If `fast` is False, additionally confirm that the reformatted code is
885 valid by calling :func:`assert_equivalent` and :func:`assert_stable` on it.
886 `mode` is passed to :func:`format_str`.
888 if not src_contents.strip():
892 dst_contents = format_ipynb_string(src_contents, fast=fast, mode=mode)
894 dst_contents = format_str(src_contents, mode=mode)
895 if src_contents == dst_contents:
898 if not fast and not mode.is_ipynb:
899 # Jupyter notebooks will already have been checked above.
900 check_stability_and_equivalence(src_contents, dst_contents, mode=mode)
904 def validate_cell(src: str) -> None:
905 """Check that cell does not already contain TransformerManager transformations.
907 If a cell contains ``!ls``, then it'll be transformed to
908 ``get_ipython().system('ls')``. However, if the cell originally contained
909 ``get_ipython().system('ls')``, then it would get transformed in the same way:
911 >>> TransformerManager().transform_cell("get_ipython().system('ls')")
912 "get_ipython().system('ls')\n"
913 >>> TransformerManager().transform_cell("!ls")
914 "get_ipython().system('ls')\n"
916 Due to the impossibility of safely roundtripping in such situations, cells
917 containing transformed magics will be ignored.
919 if any(transformed_magic in src for transformed_magic in TRANSFORMED_MAGICS):
923 def format_cell(src: str, *, fast: bool, mode: Mode) -> str:
924 """Format code in given cell of Jupyter notebook.
928 - if cell has trailing semicolon, remove it;
929 - if cell has IPython magics, mask them;
931 - reinstate IPython magics;
932 - reinstate trailing semicolon (if originally present);
933 - strip trailing newlines.
935 Cells with syntax errors will not be processed, as they
936 could potentially be automagics or multi-line magics, which
937 are currently not supported.
940 src_without_trailing_semicolon, has_trailing_semicolon = remove_trailing_semicolon(
944 masked_src, replacements = mask_cell(src_without_trailing_semicolon)
947 masked_dst = format_str(masked_src, mode=mode)
949 check_stability_and_equivalence(masked_src, masked_dst, mode=mode)
950 dst_without_trailing_semicolon = unmask_cell(masked_dst, replacements)
951 dst = put_trailing_semicolon_back(
952 dst_without_trailing_semicolon, has_trailing_semicolon
954 dst = dst.rstrip("\n")
960 def validate_metadata(nb: MutableMapping[str, Any]) -> None:
961 """If notebook is marked as non-Python, don't format it.
963 All notebook metadata fields are optional, see
964 https://nbformat.readthedocs.io/en/latest/format_description.html. So
965 if a notebook has empty metadata, we will try to parse it anyway.
967 language = nb.get("metadata", {}).get("language_info", {}).get("name", None)
968 if language is not None and language != "python":
972 def format_ipynb_string(src_contents: str, *, fast: bool, mode: Mode) -> FileContent:
973 """Format Jupyter notebook.
975 Operate cell-by-cell, only on code cells, only for Python notebooks.
976 If the ``.ipynb`` originally had a trailing newline, it'll be preseved.
978 trailing_newline = src_contents[-1] == "\n"
980 nb = json.loads(src_contents)
981 validate_metadata(nb)
982 for cell in nb["cells"]:
983 if cell.get("cell_type", None) == "code":
985 src = "".join(cell["source"])
986 dst = format_cell(src, fast=fast, mode=mode)
987 except NothingChanged:
990 cell["source"] = dst.splitlines(keepends=True)
993 dst_contents = json.dumps(nb, indent=1, ensure_ascii=False)
995 dst_contents = dst_contents + "\n"
1001 def format_str(src_contents: str, *, mode: Mode) -> FileContent:
1002 """Reformat a string and return new contents.
1004 `mode` determines formatting options, such as how many characters per line are
1008 >>> print(black.format_str("def f(arg:str='')->None:...", mode=black.Mode()))
1009 def f(arg: str = "") -> None:
1012 A more complex example:
1015 ... black.format_str(
1016 ... "def f(arg:str='')->None: hey",
1017 ... mode=black.Mode(
1018 ... target_versions={black.TargetVersion.PY36},
1020 ... string_normalization=False,
1031 src_node = lib2to3_parse(src_contents.lstrip(), mode.target_versions)
1033 future_imports = get_future_imports(src_node)
1034 if mode.target_versions:
1035 versions = mode.target_versions
1037 versions = detect_target_versions(src_node)
1038 normalize_fmt_off(src_node)
1039 lines = LineGenerator(
1041 remove_u_prefix="unicode_literals" in future_imports
1042 or supports_feature(versions, Feature.UNICODE_LITERALS),
1044 elt = EmptyLineTracker(is_pyi=mode.is_pyi)
1045 empty_line = Line(mode=mode)
1047 split_line_features = {
1049 for feature in {Feature.TRAILING_COMMA_IN_CALL, Feature.TRAILING_COMMA_IN_DEF}
1050 if supports_feature(versions, feature)
1052 for current_line in lines.visit(src_node):
1053 dst_contents.append(str(empty_line) * after)
1054 before, after = elt.maybe_empty_lines(current_line)
1055 dst_contents.append(str(empty_line) * before)
1056 for line in transform_line(
1057 current_line, mode=mode, features=split_line_features
1059 dst_contents.append(str(line))
1060 return "".join(dst_contents)
1063 def decode_bytes(src: bytes) -> Tuple[FileContent, Encoding, NewLine]:
1064 """Return a tuple of (decoded_contents, encoding, newline).
1066 `newline` is either CRLF or LF but `decoded_contents` is decoded with
1067 universal newlines (i.e. only contains LF).
1069 srcbuf = io.BytesIO(src)
1070 encoding, lines = tokenize.detect_encoding(srcbuf.readline)
1072 return "", encoding, "\n"
1074 newline = "\r\n" if b"\r\n" == lines[0][-2:] else "\n"
1076 with io.TextIOWrapper(srcbuf, encoding) as tiow:
1077 return tiow.read(), encoding, newline
1080 def get_features_used(node: Node) -> Set[Feature]:
1081 """Return a set of (relatively) new Python features used in this file.
1083 Currently looking for:
1085 - underscores in numeric literals;
1086 - trailing commas after * or ** in function signatures and calls;
1087 - positional only arguments in function signatures and lambdas;
1088 - assignment expression;
1089 - relaxed decorator syntax;
1091 features: Set[Feature] = set()
1092 for n in node.pre_order():
1093 if n.type == token.STRING:
1094 value_head = n.value[:2] # type: ignore
1095 if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
1096 features.add(Feature.F_STRINGS)
1098 elif n.type == token.NUMBER:
1099 if "_" in n.value: # type: ignore
1100 features.add(Feature.NUMERIC_UNDERSCORES)
1102 elif n.type == token.SLASH:
1103 if n.parent and n.parent.type in {syms.typedargslist, syms.arglist}:
1104 features.add(Feature.POS_ONLY_ARGUMENTS)
1106 elif n.type == token.COLONEQUAL:
1107 features.add(Feature.ASSIGNMENT_EXPRESSIONS)
1109 elif n.type == syms.decorator:
1110 if len(n.children) > 1 and not is_simple_decorator_expression(
1113 features.add(Feature.RELAXED_DECORATORS)
1116 n.type in {syms.typedargslist, syms.arglist}
1118 and n.children[-1].type == token.COMMA
1120 if n.type == syms.typedargslist:
1121 feature = Feature.TRAILING_COMMA_IN_DEF
1123 feature = Feature.TRAILING_COMMA_IN_CALL
1125 for ch in n.children:
1126 if ch.type in STARS:
1127 features.add(feature)
1129 if ch.type == syms.argument:
1130 for argch in ch.children:
1131 if argch.type in STARS:
1132 features.add(feature)
1137 def detect_target_versions(node: Node) -> Set[TargetVersion]:
1138 """Detect the version to target based on the nodes used."""
1139 features = get_features_used(node)
1141 version for version in TargetVersion if features <= VERSION_TO_FEATURES[version]
1145 def get_future_imports(node: Node) -> Set[str]:
1146 """Return a set of __future__ imports in the file."""
1147 imports: Set[str] = set()
1149 def get_imports_from_children(children: List[LN]) -> Generator[str, None, None]:
1150 for child in children:
1151 if isinstance(child, Leaf):
1152 if child.type == token.NAME:
1155 elif child.type == syms.import_as_name:
1156 orig_name = child.children[0]
1157 assert isinstance(orig_name, Leaf), "Invalid syntax parsing imports"
1158 assert orig_name.type == token.NAME, "Invalid syntax parsing imports"
1159 yield orig_name.value
1161 elif child.type == syms.import_as_names:
1162 yield from get_imports_from_children(child.children)
1165 raise AssertionError("Invalid syntax parsing imports")
1167 for child in node.children:
1168 if child.type != syms.simple_stmt:
1171 first_child = child.children[0]
1172 if isinstance(first_child, Leaf):
1173 # Continue looking if we see a docstring; otherwise stop.
1175 len(child.children) == 2
1176 and first_child.type == token.STRING
1177 and child.children[1].type == token.NEWLINE
1183 elif first_child.type == syms.import_from:
1184 module_name = first_child.children[1]
1185 if not isinstance(module_name, Leaf) or module_name.value != "__future__":
1188 imports |= set(get_imports_from_children(first_child.children[3:]))
1195 def assert_equivalent(src: str, dst: str, *, pass_num: int = 1) -> None:
1196 """Raise AssertionError if `src` and `dst` aren't equivalent."""
1198 src_ast = parse_ast(src)
1199 except Exception as exc:
1200 raise AssertionError(
1201 "cannot use --safe with this file; failed to parse source file. AST"
1202 f" error message: {exc}"
1206 dst_ast = parse_ast(dst)
1207 except Exception as exc:
1208 log = dump_to_file("".join(traceback.format_tb(exc.__traceback__)), dst)
1209 raise AssertionError(
1210 f"INTERNAL ERROR: Black produced invalid code on pass {pass_num}: {exc}. "
1211 "Please report a bug on https://github.com/psf/black/issues. "
1212 f"This invalid output might be helpful: {log}"
1215 src_ast_str = "\n".join(stringify_ast(src_ast))
1216 dst_ast_str = "\n".join(stringify_ast(dst_ast))
1217 if src_ast_str != dst_ast_str:
1218 log = dump_to_file(diff(src_ast_str, dst_ast_str, "src", "dst"))
1219 raise AssertionError(
1220 "INTERNAL ERROR: Black produced code that is not equivalent to the"
1221 f" source on pass {pass_num}. Please report a bug on "
1222 f"https://github.com/psf/black/issues. This diff might be helpful: {log}"
1226 def assert_stable(src: str, dst: str, mode: Mode) -> None:
1227 """Raise AssertionError if `dst` reformats differently the second time."""
1228 newdst = format_str(dst, mode=mode)
1232 diff(src, dst, "source", "first pass"),
1233 diff(dst, newdst, "first pass", "second pass"),
1235 raise AssertionError(
1236 "INTERNAL ERROR: Black produced different code on the second pass of the"
1237 " formatter. Please report a bug on https://github.com/psf/black/issues."
1238 f" This diff might be helpful: {log}"
1243 def nullcontext() -> Iterator[None]:
1244 """Return an empty context manager.
1246 To be used like `nullcontext` in Python 3.7.
1251 def patch_click() -> None:
1252 """Make Click not crash on Python 3.6 with LANG=C.
1254 On certain misconfigured environments, Python 3 selects the ASCII encoding as the
1255 default which restricts paths that it can access during the lifetime of the
1256 application. Click refuses to work in this scenario by raising a RuntimeError.
1258 In case of Black the likelihood that non-ASCII characters are going to be used in
1259 file paths is minimal since it's Python source code. Moreover, this crash was
1260 spurious on Python 3.7 thanks to PEP 538 and PEP 540.
1263 from click import core
1264 from click import _unicodefun # type: ignore
1265 except ModuleNotFoundError:
1268 for module in (core, _unicodefun):
1269 if hasattr(module, "_verify_python3_env"):
1270 module._verify_python3_env = lambda: None # type: ignore
1271 if hasattr(module, "_verify_python_env"):
1272 module._verify_python_env = lambda: None # type: ignore
1275 def patched_main() -> None:
1276 maybe_install_uvloop()
1282 if __name__ == "__main__":