Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/black/brackets.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

167 statements  

1"""Builds on top of nodes.py to track brackets.""" 

2 

3from collections.abc import Iterable, Sequence 

4from dataclasses import dataclass, field 

5from typing import Final, Optional, Union 

6 

7from black.nodes import ( 

8 BRACKET, 

9 CLOSING_BRACKETS, 

10 COMPARATORS, 

11 LOGIC_OPERATORS, 

12 MATH_OPERATORS, 

13 OPENING_BRACKETS, 

14 UNPACKING_PARENTS, 

15 VARARGS_PARENTS, 

16 is_vararg, 

17 syms, 

18) 

19from blib2to3.pgen2 import token 

20from blib2to3.pytree import Leaf, Node 

21 

22# types 

23LN = Union[Leaf, Node] 

24Depth = int 

25LeafID = int 

26NodeType = int 

27Priority = int 

28 

29 

30COMPREHENSION_PRIORITY: Final = 20 

31COMMA_PRIORITY: Final = 18 

32TERNARY_PRIORITY: Final = 16 

33LOGIC_PRIORITY: Final = 14 

34STRING_PRIORITY: Final = 12 

35COMPARATOR_PRIORITY: Final = 10 

36MATH_PRIORITIES: Final = { 

37 token.VBAR: 9, 

38 token.CIRCUMFLEX: 8, 

39 token.AMPER: 7, 

40 token.LEFTSHIFT: 6, 

41 token.RIGHTSHIFT: 6, 

42 token.PLUS: 5, 

43 token.MINUS: 5, 

44 token.STAR: 4, 

45 token.SLASH: 4, 

46 token.DOUBLESLASH: 4, 

47 token.PERCENT: 4, 

48 token.AT: 4, 

49 token.TILDE: 3, 

50 token.DOUBLESTAR: 2, 

51} 

52DOT_PRIORITY: Final = 1 

53 

54 

55class BracketMatchError(Exception): 

56 """Raised when an opening bracket is unable to be matched to a closing bracket.""" 

57 

58 

59@dataclass 

60class BracketTracker: 

61 """Keeps track of brackets on a line.""" 

62 

63 depth: int = 0 

64 bracket_match: dict[tuple[Depth, NodeType], Leaf] = field(default_factory=dict) 

65 delimiters: dict[LeafID, Priority] = field(default_factory=dict) 

66 previous: Optional[Leaf] = None 

67 _for_loop_depths: list[int] = field(default_factory=list) 

68 _lambda_argument_depths: list[int] = field(default_factory=list) 

69 invisible: list[Leaf] = field(default_factory=list) 

70 

71 def mark(self, leaf: Leaf) -> None: 

72 """Mark `leaf` with bracket-related metadata. Keep track of delimiters. 

73 

74 All leaves receive an int `bracket_depth` field that stores how deep 

75 within brackets a given leaf is. 0 means there are no enclosing brackets 

76 that started on this line. 

77 

78 If a leaf is itself a closing bracket and there is a matching opening 

79 bracket earlier, it receives an `opening_bracket` field with which it forms a 

80 pair. This is a one-directional link to avoid reference cycles. Closing 

81 bracket without opening happens on lines continued from previous 

82 breaks, e.g. `) -> "ReturnType":` as part of a funcdef where we place 

83 the return type annotation on its own line of the previous closing RPAR. 

84 

85 If a leaf is a delimiter (a token on which Black can split the line if 

86 needed) and it's on depth 0, its `id()` is stored in the tracker's 

87 `delimiters` field. 

88 """ 

89 if leaf.type == token.COMMENT: 

90 return 

91 

92 if ( 

93 self.depth == 0 

94 and leaf.type in CLOSING_BRACKETS 

95 and (self.depth, leaf.type) not in self.bracket_match 

96 ): 

97 return 

98 

99 self.maybe_decrement_after_for_loop_variable(leaf) 

100 self.maybe_decrement_after_lambda_arguments(leaf) 

101 if leaf.type in CLOSING_BRACKETS: 

102 self.depth -= 1 

103 try: 

104 opening_bracket = self.bracket_match.pop((self.depth, leaf.type)) 

105 except KeyError as e: 

106 raise BracketMatchError( 

107 "Unable to match a closing bracket to the following opening" 

108 f" bracket: {leaf}" 

109 ) from e 

110 leaf.opening_bracket = opening_bracket 

111 if not leaf.value: 

112 self.invisible.append(leaf) 

113 leaf.bracket_depth = self.depth 

114 if self.depth == 0: 

115 delim = is_split_before_delimiter(leaf, self.previous) 

116 if delim and self.previous is not None: 

117 self.delimiters[id(self.previous)] = delim 

118 else: 

119 delim = is_split_after_delimiter(leaf) 

120 if delim: 

121 self.delimiters[id(leaf)] = delim 

122 if leaf.type in OPENING_BRACKETS: 

123 self.bracket_match[self.depth, BRACKET[leaf.type]] = leaf 

124 self.depth += 1 

125 if not leaf.value: 

126 self.invisible.append(leaf) 

127 self.previous = leaf 

128 self.maybe_increment_lambda_arguments(leaf) 

129 self.maybe_increment_for_loop_variable(leaf) 

130 

131 def any_open_for_or_lambda(self) -> bool: 

132 """Return True if there is an open for or lambda expression on the line. 

133 

134 See maybe_increment_for_loop_variable and maybe_increment_lambda_arguments 

135 for details.""" 

136 return bool(self._for_loop_depths or self._lambda_argument_depths) 

137 

138 def any_open_brackets(self) -> bool: 

139 """Return True if there is an yet unmatched open bracket on the line.""" 

140 return bool(self.bracket_match) 

141 

142 def max_delimiter_priority(self, exclude: Iterable[LeafID] = ()) -> Priority: 

143 """Return the highest priority of a delimiter found on the line. 

144 

145 Values are consistent with what `is_split_*_delimiter()` return. 

146 Raises ValueError on no delimiters. 

147 """ 

148 return max(v for k, v in self.delimiters.items() if k not in exclude) 

149 

150 def delimiter_count_with_priority(self, priority: Priority = 0) -> int: 

151 """Return the number of delimiters with the given `priority`. 

152 

153 If no `priority` is passed, defaults to max priority on the line. 

154 """ 

155 if not self.delimiters: 

156 return 0 

157 

158 priority = priority or self.max_delimiter_priority() 

159 return sum(1 for p in self.delimiters.values() if p == priority) 

160 

161 def maybe_increment_for_loop_variable(self, leaf: Leaf) -> bool: 

162 """In a for loop, or comprehension, the variables are often unpacks. 

163 

164 To avoid splitting on the comma in this situation, increase the depth of 

165 tokens between `for` and `in`. 

166 """ 

167 if leaf.type == token.NAME and leaf.value == "for": 

168 self.depth += 1 

169 self._for_loop_depths.append(self.depth) 

170 return True 

171 

172 return False 

173 

174 def maybe_decrement_after_for_loop_variable(self, leaf: Leaf) -> bool: 

175 """See `maybe_increment_for_loop_variable` above for explanation.""" 

176 if ( 

177 self._for_loop_depths 

178 and self._for_loop_depths[-1] == self.depth 

179 and leaf.type == token.NAME 

180 and leaf.value == "in" 

181 ): 

182 self.depth -= 1 

183 self._for_loop_depths.pop() 

184 return True 

185 

186 return False 

187 

188 def maybe_increment_lambda_arguments(self, leaf: Leaf) -> bool: 

189 """In a lambda expression, there might be more than one argument. 

190 

191 To avoid splitting on the comma in this situation, increase the depth of 

192 tokens between `lambda` and `:`. 

193 """ 

194 if leaf.type == token.NAME and leaf.value == "lambda": 

195 self.depth += 1 

196 self._lambda_argument_depths.append(self.depth) 

197 return True 

198 

199 return False 

200 

201 def maybe_decrement_after_lambda_arguments(self, leaf: Leaf) -> bool: 

202 """See `maybe_increment_lambda_arguments` above for explanation.""" 

203 if ( 

204 self._lambda_argument_depths 

205 and self._lambda_argument_depths[-1] == self.depth 

206 and leaf.type == token.COLON 

207 ): 

208 self.depth -= 1 

209 self._lambda_argument_depths.pop() 

210 return True 

211 

212 return False 

213 

214 def get_open_lsqb(self) -> Optional[Leaf]: 

215 """Return the most recent opening square bracket (if any).""" 

216 return self.bracket_match.get((self.depth - 1, token.RSQB)) 

217 

218 

219def is_split_after_delimiter(leaf: Leaf) -> Priority: 

220 """Return the priority of the `leaf` delimiter, given a line break after it. 

221 

222 The delimiter priorities returned here are from those delimiters that would 

223 cause a line break after themselves. 

224 

225 Higher numbers are higher priority. 

226 """ 

227 if leaf.type == token.COMMA: 

228 return COMMA_PRIORITY 

229 

230 return 0 

231 

232 

233def is_split_before_delimiter(leaf: Leaf, previous: Optional[Leaf] = None) -> Priority: 

234 """Return the priority of the `leaf` delimiter, given a line break before it. 

235 

236 The delimiter priorities returned here are from those delimiters that would 

237 cause a line break before themselves. 

238 

239 Higher numbers are higher priority. 

240 """ 

241 if is_vararg(leaf, within=VARARGS_PARENTS | UNPACKING_PARENTS): 

242 # * and ** might also be MATH_OPERATORS but in this case they are not. 

243 # Don't treat them as a delimiter. 

244 return 0 

245 

246 if ( 

247 leaf.type == token.DOT 

248 and leaf.parent 

249 and leaf.parent.type not in {syms.import_from, syms.dotted_name} 

250 and (previous is None or previous.type in CLOSING_BRACKETS) 

251 ): 

252 return DOT_PRIORITY 

253 

254 if ( 

255 leaf.type in MATH_OPERATORS 

256 and leaf.parent 

257 and leaf.parent.type not in {syms.factor, syms.star_expr} 

258 ): 

259 return MATH_PRIORITIES[leaf.type] 

260 

261 if leaf.type in COMPARATORS: 

262 return COMPARATOR_PRIORITY 

263 

264 if ( 

265 leaf.type == token.STRING 

266 and previous is not None 

267 and previous.type == token.STRING 

268 ): 

269 return STRING_PRIORITY 

270 

271 if leaf.type not in {token.NAME, token.ASYNC}: 

272 return 0 

273 

274 if ( 

275 leaf.value == "for" 

276 and leaf.parent 

277 and leaf.parent.type in {syms.comp_for, syms.old_comp_for} 

278 or leaf.type == token.ASYNC 

279 ): 

280 if ( 

281 not isinstance(leaf.prev_sibling, Leaf) 

282 or leaf.prev_sibling.value != "async" 

283 ): 

284 return COMPREHENSION_PRIORITY 

285 

286 if ( 

287 leaf.value == "if" 

288 and leaf.parent 

289 and leaf.parent.type in {syms.comp_if, syms.old_comp_if} 

290 ): 

291 return COMPREHENSION_PRIORITY 

292 

293 if leaf.value in {"if", "else"} and leaf.parent and leaf.parent.type == syms.test: 

294 return TERNARY_PRIORITY 

295 

296 if leaf.value == "is": 

297 return COMPARATOR_PRIORITY 

298 

299 if ( 

300 leaf.value == "in" 

301 and leaf.parent 

302 and leaf.parent.type in {syms.comp_op, syms.comparison} 

303 and not ( 

304 previous is not None 

305 and previous.type == token.NAME 

306 and previous.value == "not" 

307 ) 

308 ): 

309 return COMPARATOR_PRIORITY 

310 

311 if ( 

312 leaf.value == "not" 

313 and leaf.parent 

314 and leaf.parent.type == syms.comp_op 

315 and not ( 

316 previous is not None 

317 and previous.type == token.NAME 

318 and previous.value == "is" 

319 ) 

320 ): 

321 return COMPARATOR_PRIORITY 

322 

323 if leaf.value in LOGIC_OPERATORS and leaf.parent: 

324 return LOGIC_PRIORITY 

325 

326 return 0 

327 

328 

329def max_delimiter_priority_in_atom(node: LN) -> Priority: 

330 """Return maximum delimiter priority inside `node`. 

331 

332 This is specific to atoms with contents contained in a pair of parentheses. 

333 If `node` isn't an atom or there are no enclosing parentheses, returns 0. 

334 """ 

335 if node.type != syms.atom: 

336 return 0 

337 

338 first = node.children[0] 

339 last = node.children[-1] 

340 if not (first.type == token.LPAR and last.type == token.RPAR): 

341 return 0 

342 

343 bt = BracketTracker() 

344 for c in node.children[1:-1]: 

345 if isinstance(c, Leaf): 

346 bt.mark(c) 

347 else: 

348 for leaf in c.leaves(): 

349 bt.mark(leaf) 

350 try: 

351 return bt.max_delimiter_priority() 

352 

353 except ValueError: 

354 return 0 

355 

356 

357def get_leaves_inside_matching_brackets(leaves: Sequence[Leaf]) -> set[LeafID]: 

358 """Return leaves that are inside matching brackets. 

359 

360 The input `leaves` can have non-matching brackets at the head or tail parts. 

361 Matching brackets are included. 

362 """ 

363 try: 

364 # Start with the first opening bracket and ignore closing brackets before. 

365 start_index = next( 

366 i for i, l in enumerate(leaves) if l.type in OPENING_BRACKETS 

367 ) 

368 except StopIteration: 

369 return set() 

370 bracket_stack = [] 

371 ids = set() 

372 for i in range(start_index, len(leaves)): 

373 leaf = leaves[i] 

374 if leaf.type in OPENING_BRACKETS: 

375 bracket_stack.append((BRACKET[leaf.type], i)) 

376 if leaf.type in CLOSING_BRACKETS: 

377 if bracket_stack and leaf.type == bracket_stack[-1][0]: 

378 _, start = bracket_stack.pop() 

379 for j in range(start, i + 1): 

380 ids.add(id(leaves[j])) 

381 else: 

382 break 

383 return ids