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.
   1 """Builds on top of nodes.py to track brackets."""
 
   4 from dataclasses import dataclass, field
 
   5 from typing import Dict, Iterable, List, Optional, Tuple, Union
 
   7 if sys.version_info < (3, 8):
 
   8     from typing_extensions import Final
 
  10     from typing import Final
 
  12 from black.nodes import (
 
  24 from blib2to3.pgen2 import token
 
  25 from blib2to3.pytree import Leaf, Node
 
  28 LN = Union[Leaf, Node]
 
  35 COMPREHENSION_PRIORITY: Final = 20
 
  36 COMMA_PRIORITY: Final = 18
 
  37 TERNARY_PRIORITY: Final = 16
 
  38 LOGIC_PRIORITY: Final = 14
 
  39 STRING_PRIORITY: Final = 12
 
  40 COMPARATOR_PRIORITY: Final = 10
 
  41 MATH_PRIORITIES: Final = {
 
  57 DOT_PRIORITY: Final = 1
 
  60 class BracketMatchError(Exception):
 
  61     """Raised when an opening bracket is unable to be matched to a closing bracket."""
 
  66     """Keeps track of brackets on a line."""
 
  69     bracket_match: Dict[Tuple[Depth, NodeType], Leaf] = field(default_factory=dict)
 
  70     delimiters: Dict[LeafID, Priority] = field(default_factory=dict)
 
  71     previous: Optional[Leaf] = None
 
  72     _for_loop_depths: List[int] = field(default_factory=list)
 
  73     _lambda_argument_depths: List[int] = field(default_factory=list)
 
  74     invisible: List[Leaf] = field(default_factory=list)
 
  76     def mark(self, leaf: Leaf) -> None:
 
  77         """Mark `leaf` with bracket-related metadata. Keep track of delimiters.
 
  79         All leaves receive an int `bracket_depth` field that stores how deep
 
  80         within brackets a given leaf is. 0 means there are no enclosing brackets
 
  81         that started on this line.
 
  83         If a leaf is itself a closing bracket, it receives an `opening_bracket`
 
  84         field that it forms a pair with. This is a one-directional link to
 
  85         avoid reference cycles.
 
  87         If a leaf is a delimiter (a token on which Black can split the line if
 
  88         needed) and it's on depth 0, its `id()` is stored in the tracker's
 
  91         if leaf.type == token.COMMENT:
 
  94         self.maybe_decrement_after_for_loop_variable(leaf)
 
  95         self.maybe_decrement_after_lambda_arguments(leaf)
 
  96         if leaf.type in CLOSING_BRACKETS:
 
  99                 opening_bracket = self.bracket_match.pop((self.depth, leaf.type))
 
 100             except KeyError as e:
 
 101                 raise BracketMatchError(
 
 102                     "Unable to match a closing bracket to the following opening"
 
 105             leaf.opening_bracket = opening_bracket
 
 107                 self.invisible.append(leaf)
 
 108         leaf.bracket_depth = self.depth
 
 110             delim = is_split_before_delimiter(leaf, self.previous)
 
 111             if delim and self.previous is not None:
 
 112                 self.delimiters[id(self.previous)] = delim
 
 114                 delim = is_split_after_delimiter(leaf, self.previous)
 
 116                     self.delimiters[id(leaf)] = delim
 
 117         if leaf.type in OPENING_BRACKETS:
 
 118             self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf
 
 121                 self.invisible.append(leaf)
 
 123         self.maybe_increment_lambda_arguments(leaf)
 
 124         self.maybe_increment_for_loop_variable(leaf)
 
 126     def any_open_brackets(self) -> bool:
 
 127         """Return True if there is an yet unmatched open bracket on the line."""
 
 128         return bool(self.bracket_match)
 
 130     def max_delimiter_priority(self, exclude: Iterable[LeafID] = ()) -> Priority:
 
 131         """Return the highest priority of a delimiter found on the line.
 
 133         Values are consistent with what `is_split_*_delimiter()` return.
 
 134         Raises ValueError on no delimiters.
 
 136         return max(v for k, v in self.delimiters.items() if k not in exclude)
 
 138     def delimiter_count_with_priority(self, priority: Priority = 0) -> int:
 
 139         """Return the number of delimiters with the given `priority`.
 
 141         If no `priority` is passed, defaults to max priority on the line.
 
 143         if not self.delimiters:
 
 146         priority = priority or self.max_delimiter_priority()
 
 147         return sum(1 for p in self.delimiters.values() if p == priority)
 
 149     def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool:
 
 150         """In a for loop, or comprehension, the variables are often unpacks.
 
 152         To avoid splitting on the comma in this situation, increase the depth of
 
 153         tokens between `for` and `in`.
 
 155         if leaf.type == token.NAME and leaf.value == "for":
 
 157             self._for_loop_depths.append(self.depth)
 
 162     def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool:
 
 163         """See `maybe_increment_for_loop_variable` above for explanation."""
 
 165             self._for_loop_depths
 
 166             and self._for_loop_depths[-1] == self.depth
 
 167             and leaf.type == token.NAME
 
 168             and leaf.value == "in"
 
 171             self._for_loop_depths.pop()
 
 176     def maybe_increment_lambda_arguments(self, leaf: Leaf) -> bool:
 
 177         """In a lambda expression, there might be more than one argument.
 
 179         To avoid splitting on the comma in this situation, increase the depth of
 
 180         tokens between `lambda` and `:`.
 
 182         if leaf.type == token.NAME and leaf.value == "lambda":
 
 184             self._lambda_argument_depths.append(self.depth)
 
 189     def maybe_decrement_after_lambda_arguments(self, leaf: Leaf) -> bool:
 
 190         """See `maybe_increment_lambda_arguments` above for explanation."""
 
 192             self._lambda_argument_depths
 
 193             and self._lambda_argument_depths[-1] == self.depth
 
 194             and leaf.type == token.COLON
 
 197             self._lambda_argument_depths.pop()
 
 202     def get_open_lsqb(self) -> Optional[Leaf]:
 
 203         """Return the most recent opening square bracket (if any)."""
 
 204         return self.bracket_match.get((self.depth - 1, token.RSQB))
 
 207 def is_split_after_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> Priority:
 
 208     """Return the priority of the `leaf` delimiter, given a line break after it.
 
 210     The delimiter priorities returned here are from those delimiters that would
 
 211     cause a line break after themselves.
 
 213     Higher numbers are higher priority.
 
 215     if leaf.type == token.COMMA:
 
 216         return COMMA_PRIORITY
 
 221 def is_split_before_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> Priority:
 
 222     """Return the priority of the `leaf` delimiter, given a line break before it.
 
 224     The delimiter priorities returned here are from those delimiters that would
 
 225     cause a line break before themselves.
 
 227     Higher numbers are higher priority.
 
 229     if is_vararg(leaf, within=VARARGS_PARENTS | UNPACKING_PARENTS):
 
 230         # * and ** might also be MATH_OPERATORS but in this case they are not.
 
 231         # Don't treat them as a delimiter.
 
 235         leaf.type == token.DOT
 
 237         and leaf.parent.type not in {syms.import_from, syms.dotted_name}
 
 238         and (previous is None or previous.type in CLOSING_BRACKETS)
 
 243         leaf.type in MATH_OPERATORS
 
 245         and leaf.parent.type not in {syms.factor, syms.star_expr}
 
 247         return MATH_PRIORITIES[leaf.type]
 
 249     if leaf.type in COMPARATORS:
 
 250         return COMPARATOR_PRIORITY
 
 253         leaf.type == token.STRING
 
 254         and previous is not None
 
 255         and previous.type == token.STRING
 
 257         return STRING_PRIORITY
 
 259     if leaf.type not in {token.NAME, token.ASYNC}:
 
 265         and leaf.parent.type in {syms.comp_for, syms.old_comp_for}
 
 266         or leaf.type == token.ASYNC
 
 269             not isinstance(leaf.prev_sibling, Leaf)
 
 270             or leaf.prev_sibling.value != "async"
 
 272             return COMPREHENSION_PRIORITY
 
 277         and leaf.parent.type in {syms.comp_if, syms.old_comp_if}
 
 279         return COMPREHENSION_PRIORITY
 
 281     if leaf.value in {"if", "else"} and leaf.parent and leaf.parent.type == syms.test:
 
 282         return TERNARY_PRIORITY
 
 284     if leaf.value == "is":
 
 285         return COMPARATOR_PRIORITY
 
 290         and leaf.parent.type in {syms.comp_op, syms.comparison}
 
 293             and previous.type == token.NAME
 
 294             and previous.value == "not"
 
 297         return COMPARATOR_PRIORITY
 
 302         and leaf.parent.type == syms.comp_op
 
 305             and previous.type == token.NAME
 
 306             and previous.value == "is"
 
 309         return COMPARATOR_PRIORITY
 
 311     if leaf.value in LOGIC_OPERATORS and leaf.parent:
 
 312         return LOGIC_PRIORITY
 
 317 def max_delimiter_priority_in_atom(node: LN) -> Priority:
 
 318     """Return maximum delimiter priority inside `node`.
 
 320     This is specific to atoms with contents contained in a pair of parentheses.
 
 321     If `node` isn't an atom or there are no enclosing parentheses, returns 0.
 
 323     if node.type != syms.atom:
 
 326     first = node.children[0]
 
 327     last = node.children[-1]
 
 328     if not (first.type == token.LPAR and last.type == token.RPAR):
 
 331     bt = BracketTracker()
 
 332     for c in node.children[1:-1]:
 
 333         if isinstance(c, Leaf):
 
 336             for leaf in c.leaves():
 
 339         return bt.max_delimiter_priority()