design_analysis.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Check for signs of poor design."""
  5. from __future__ import annotations
  6. import re
  7. from collections import defaultdict
  8. from collections.abc import Iterator
  9. from typing import TYPE_CHECKING
  10. from astroid import nodes
  11. from pylint.checkers import BaseChecker
  12. from pylint.checkers.utils import is_enum, only_required_for_messages
  13. from pylint.interfaces import HIGH
  14. from pylint.typing import MessageDefinitionTuple
  15. if TYPE_CHECKING:
  16. from pylint.lint import PyLinter
  17. MSGS: dict[str, MessageDefinitionTuple] = (
  18. { # pylint: disable=consider-using-namedtuple-or-dataclass
  19. "R0901": (
  20. "Too many ancestors (%s/%s)",
  21. "too-many-ancestors",
  22. "Used when class has too many parent classes, try to reduce "
  23. "this to get a simpler (and so easier to use) class.",
  24. ),
  25. "R0902": (
  26. "Too many instance attributes (%s/%s)",
  27. "too-many-instance-attributes",
  28. "Used when class has too many instance attributes, try to reduce "
  29. "this to get a simpler (and so easier to use) class.",
  30. ),
  31. "R0903": (
  32. "Too few public methods (%s/%s)",
  33. "too-few-public-methods",
  34. "Used when class has too few public methods, so be sure it's "
  35. "really worth it.",
  36. ),
  37. "R0904": (
  38. "Too many public methods (%s/%s)",
  39. "too-many-public-methods",
  40. "Used when class has too many public methods, try to reduce "
  41. "this to get a simpler (and so easier to use) class.",
  42. ),
  43. "R0911": (
  44. "Too many return statements (%s/%s)",
  45. "too-many-return-statements",
  46. "Used when a function or method has too many return statement, "
  47. "making it hard to follow.",
  48. ),
  49. "R0912": (
  50. "Too many branches (%s/%s)",
  51. "too-many-branches",
  52. "Used when a function or method has too many branches, "
  53. "making it hard to follow.",
  54. ),
  55. "R0913": (
  56. "Too many arguments (%s/%s)",
  57. "too-many-arguments",
  58. "Used when a function or method takes too many arguments.",
  59. ),
  60. "R0914": (
  61. "Too many local variables (%s/%s)",
  62. "too-many-locals",
  63. "Used when a function or method has too many local variables.",
  64. ),
  65. "R0915": (
  66. "Too many statements (%s/%s)",
  67. "too-many-statements",
  68. "Used when a function or method has too many statements. You "
  69. "should then split it in smaller functions / methods.",
  70. ),
  71. "R0916": (
  72. "Too many boolean expressions in if statement (%s/%s)",
  73. "too-many-boolean-expressions",
  74. "Used when an if statement contains too many boolean expressions.",
  75. ),
  76. "R0917": (
  77. "Too many positional arguments (%s/%s)",
  78. "too-many-positional-arguments",
  79. "Used when a function has too many positional arguments.",
  80. ),
  81. }
  82. )
  83. SPECIAL_OBJ = re.compile("^_{2}[a-z]+_{2}$")
  84. DATACLASSES_DECORATORS = frozenset({"dataclass", "attrs"})
  85. DATACLASS_IMPORT = "dataclasses"
  86. ATTRS_DECORATORS = frozenset({"define", "frozen"})
  87. ATTRS_IMPORT = "attrs"
  88. TYPING_NAMEDTUPLE = "typing.NamedTuple"
  89. TYPING_TYPEDDICT = "typing.TypedDict"
  90. TYPING_EXTENSIONS_TYPEDDICT = "typing_extensions.TypedDict"
  91. # Set of stdlib classes to ignore when calculating number of ancestors
  92. STDLIB_CLASSES_IGNORE_ANCESTOR = frozenset(
  93. (
  94. "builtins.object",
  95. "builtins.tuple",
  96. "builtins.dict",
  97. "builtins.list",
  98. "builtins.set",
  99. "bulitins.frozenset",
  100. "collections.ChainMap",
  101. "collections.Counter",
  102. "collections.OrderedDict",
  103. "collections.UserDict",
  104. "collections.UserList",
  105. "collections.UserString",
  106. "collections.defaultdict",
  107. "collections.deque",
  108. "collections.namedtuple",
  109. "_collections_abc.Awaitable",
  110. "_collections_abc.Coroutine",
  111. "_collections_abc.AsyncIterable",
  112. "_collections_abc.AsyncIterator",
  113. "_collections_abc.AsyncGenerator",
  114. "_collections_abc.Hashable",
  115. "_collections_abc.Iterable",
  116. "_collections_abc.Iterator",
  117. "_collections_abc.Generator",
  118. "_collections_abc.Reversible",
  119. "_collections_abc.Sized",
  120. "_collections_abc.Container",
  121. "_collections_abc.Collection",
  122. "_collections_abc.Set",
  123. "_collections_abc.MutableSet",
  124. "_collections_abc.Mapping",
  125. "_collections_abc.MutableMapping",
  126. "_collections_abc.MappingView",
  127. "_collections_abc.KeysView",
  128. "_collections_abc.ItemsView",
  129. "_collections_abc.ValuesView",
  130. "_collections_abc.Sequence",
  131. "_collections_abc.MutableSequence",
  132. "_collections_abc.ByteString",
  133. "typing.Tuple",
  134. "typing.List",
  135. "typing.Dict",
  136. "typing.Set",
  137. "typing.FrozenSet",
  138. "typing.Deque",
  139. "typing.DefaultDict",
  140. "typing.OrderedDict",
  141. "typing.Counter",
  142. "typing.ChainMap",
  143. "typing.Awaitable",
  144. "typing.Coroutine",
  145. "typing.AsyncIterable",
  146. "typing.AsyncIterator",
  147. "typing.AsyncGenerator",
  148. "typing.Iterable",
  149. "typing.Iterator",
  150. "typing.Generator",
  151. "typing.Reversible",
  152. "typing.Container",
  153. "typing.Collection",
  154. "typing.AbstractSet",
  155. "typing.MutableSet",
  156. "typing.Mapping",
  157. "typing.MutableMapping",
  158. "typing.Sequence",
  159. "typing.MutableSequence",
  160. "typing.ByteString",
  161. "typing.MappingView",
  162. "typing.KeysView",
  163. "typing.ItemsView",
  164. "typing.ValuesView",
  165. "typing.ContextManager",
  166. "typing.AsyncContextManager",
  167. "typing.Hashable",
  168. "typing.Sized",
  169. TYPING_NAMEDTUPLE,
  170. TYPING_TYPEDDICT,
  171. TYPING_EXTENSIONS_TYPEDDICT,
  172. )
  173. )
  174. def _is_exempt_from_public_methods(node: nodes.ClassDef) -> bool:
  175. """Check if a class is exempt from too-few-public-methods."""
  176. # If it's a typing.Namedtuple, typing.TypedDict or an Enum
  177. for ancestor in node.ancestors():
  178. if is_enum(ancestor):
  179. return True
  180. if ancestor.qname() in (
  181. TYPING_NAMEDTUPLE,
  182. TYPING_TYPEDDICT,
  183. TYPING_EXTENSIONS_TYPEDDICT,
  184. ):
  185. return True
  186. # Or if it's a dataclass
  187. if not node.decorators:
  188. return False
  189. root_locals = set(node.root().locals)
  190. for decorator in node.decorators.nodes:
  191. if isinstance(decorator, nodes.Call):
  192. decorator = decorator.func
  193. match decorator:
  194. case nodes.Name(name=name) | nodes.Attribute(attrname=name):
  195. pass
  196. case _:
  197. continue
  198. if name in DATACLASSES_DECORATORS and (
  199. root_locals.intersection(DATACLASSES_DECORATORS)
  200. or DATACLASS_IMPORT in root_locals
  201. ):
  202. return True
  203. if name in ATTRS_DECORATORS and (
  204. root_locals.intersection(ATTRS_DECORATORS) or ATTRS_IMPORT in root_locals
  205. ):
  206. return True
  207. return False
  208. def _count_boolean_expressions(bool_op: nodes.BoolOp) -> int:
  209. """Counts the number of boolean expressions in BoolOp `bool_op` (recursive).
  210. example: a and (b or c or (d and e)) ==> 5 boolean expressions
  211. """
  212. nb_bool_expr = 0
  213. for bool_expr in bool_op.get_children():
  214. if isinstance(bool_expr, nodes.BoolOp):
  215. nb_bool_expr += _count_boolean_expressions(bool_expr)
  216. else:
  217. nb_bool_expr += 1
  218. return nb_bool_expr
  219. def _count_methods_in_class(node: nodes.ClassDef) -> int:
  220. all_methods = sum(1 for method in node.methods() if not method.name.startswith("_"))
  221. # Special methods count towards the number of public methods,
  222. # but don't count towards there being too many methods.
  223. for method in node.mymethods():
  224. if SPECIAL_OBJ.search(method.name) and method.name != "__init__":
  225. all_methods += 1
  226. return all_methods
  227. def _get_parents_iter(
  228. node: nodes.ClassDef, ignored_parents: frozenset[str]
  229. ) -> Iterator[nodes.ClassDef]:
  230. r"""Get parents of ``node``, excluding ancestors of ``ignored_parents``.
  231. If we have the following inheritance diagram:
  232. F
  233. /
  234. D E
  235. \/
  236. B C
  237. \/
  238. A # class A(B, C): ...
  239. And ``ignored_parents`` is ``{"E"}``, then this function will return
  240. ``{A, B, C, D}`` -- both ``E`` and its ancestors are excluded.
  241. """
  242. parents: set[nodes.ClassDef] = set()
  243. to_explore = list(node.ancestors(recurs=False))
  244. while to_explore:
  245. parent = to_explore.pop()
  246. if parent.qname() in ignored_parents:
  247. continue
  248. if parent not in parents:
  249. # This guard might appear to be performing the same function as
  250. # adding the resolved parents to a set to eliminate duplicates
  251. # (legitimate due to diamond inheritance patterns), but its
  252. # additional purpose is to prevent cycles (not normally possible,
  253. # but potential due to inference) and thus guarantee termination
  254. # of the while-loop
  255. yield parent
  256. parents.add(parent)
  257. to_explore.extend(parent.ancestors(recurs=False))
  258. def _get_parents(
  259. node: nodes.ClassDef, ignored_parents: frozenset[str]
  260. ) -> set[nodes.ClassDef]:
  261. return set(_get_parents_iter(node, ignored_parents))
  262. class MisdesignChecker(BaseChecker):
  263. """Checker of potential misdesigns.
  264. Checks for sign of poor/misdesign:
  265. * number of methods, attributes, local variables...
  266. * size, complexity of functions, methods
  267. """
  268. # configuration section name
  269. name = "design"
  270. # messages
  271. msgs = MSGS
  272. # configuration options
  273. options = (
  274. (
  275. "max-args",
  276. {
  277. "default": 5,
  278. "type": "int",
  279. "metavar": "<int>",
  280. "help": "Maximum number of arguments for function / method.",
  281. },
  282. ),
  283. (
  284. "max-positional-arguments",
  285. {
  286. "default": 5,
  287. "type": "int",
  288. "metavar": "<int>",
  289. "help": "Maximum number of positional arguments for function / method.",
  290. },
  291. ),
  292. (
  293. "max-locals",
  294. {
  295. "default": 15,
  296. "type": "int",
  297. "metavar": "<int>",
  298. "help": "Maximum number of locals for function / method body.",
  299. },
  300. ),
  301. (
  302. "max-returns",
  303. {
  304. "default": 6,
  305. "type": "int",
  306. "metavar": "<int>",
  307. "help": "Maximum number of return / yield for function / "
  308. "method body.",
  309. },
  310. ),
  311. (
  312. "max-branches",
  313. {
  314. "default": 12,
  315. "type": "int",
  316. "metavar": "<int>",
  317. "help": "Maximum number of branch for function / method body.",
  318. },
  319. ),
  320. (
  321. "max-statements",
  322. {
  323. "default": 50,
  324. "type": "int",
  325. "metavar": "<int>",
  326. "help": "Maximum number of statements in function / method body.",
  327. },
  328. ),
  329. (
  330. "max-parents",
  331. {
  332. "default": 7,
  333. "type": "int",
  334. "metavar": "<num>",
  335. "help": "Maximum number of parents for a class (see R0901).",
  336. },
  337. ),
  338. (
  339. "ignored-parents",
  340. {
  341. "default": (),
  342. "type": "csv",
  343. "metavar": "<comma separated list of class names>",
  344. "help": "List of qualified class names to ignore when counting class parents (see R0901)",
  345. },
  346. ),
  347. (
  348. "max-attributes",
  349. {
  350. "default": 7,
  351. "type": "int",
  352. "metavar": "<num>",
  353. "help": "Maximum number of attributes for a class \
  354. (see R0902).",
  355. },
  356. ),
  357. (
  358. "min-public-methods",
  359. {
  360. "default": 2,
  361. "type": "int",
  362. "metavar": "<num>",
  363. "help": "Minimum number of public methods for a class \
  364. (see R0903).",
  365. },
  366. ),
  367. (
  368. "max-public-methods",
  369. {
  370. "default": 20,
  371. "type": "int",
  372. "metavar": "<num>",
  373. "help": "Maximum number of public methods for a class \
  374. (see R0904).",
  375. },
  376. ),
  377. (
  378. "max-bool-expr",
  379. {
  380. "default": 5,
  381. "type": "int",
  382. "metavar": "<num>",
  383. "help": "Maximum number of boolean expressions in an if "
  384. "statement (see R0916).",
  385. },
  386. ),
  387. (
  388. "exclude-too-few-public-methods",
  389. {
  390. "default": [],
  391. "type": "regexp_csv",
  392. "metavar": "<pattern>[,<pattern>...]",
  393. "help": "List of regular expressions of class ancestor names "
  394. "to ignore when counting public methods (see R0903)",
  395. },
  396. ),
  397. )
  398. def __init__(self, linter: PyLinter) -> None:
  399. super().__init__(linter)
  400. self._returns: list[int]
  401. self._branches: defaultdict[nodes.LocalsDictNodeNG, int]
  402. self._stmts: list[int]
  403. def open(self) -> None:
  404. """Initialize visit variables."""
  405. self.linter.stats.reset_node_count()
  406. self._returns = []
  407. self._branches = defaultdict(int)
  408. self._stmts = []
  409. self._exclude_too_few_public_methods = (
  410. self.linter.config.exclude_too_few_public_methods
  411. )
  412. def _inc_all_stmts(self, amount: int) -> None:
  413. for i, _ in enumerate(self._stmts):
  414. self._stmts[i] += amount
  415. @only_required_for_messages(
  416. "too-many-ancestors",
  417. "too-many-instance-attributes",
  418. "too-few-public-methods",
  419. "too-many-public-methods",
  420. )
  421. def visit_classdef(self, node: nodes.ClassDef) -> None:
  422. """Check size of inheritance hierarchy and number of instance attributes."""
  423. parents = _get_parents(
  424. node,
  425. STDLIB_CLASSES_IGNORE_ANCESTOR.union(self.linter.config.ignored_parents),
  426. )
  427. nb_parents = len(parents)
  428. if nb_parents > self.linter.config.max_parents:
  429. self.add_message(
  430. "too-many-ancestors",
  431. node=node,
  432. args=(nb_parents, self.linter.config.max_parents),
  433. )
  434. # Something at inference time is modifying instance_attrs to add
  435. # properties from parent classes. Given how much we cache inference
  436. # results, mutating instance_attrs can become a real mess. Filter
  437. # them out here until the root cause is solved.
  438. # https://github.com/pylint-dev/astroid/issues/2273
  439. root = node.root()
  440. filtered_attrs = [
  441. k for (k, v) in node.instance_attrs.items() if v[0].root() is root
  442. ]
  443. if len(filtered_attrs) > self.linter.config.max_attributes:
  444. self.add_message(
  445. "too-many-instance-attributes",
  446. node=node,
  447. args=(len(filtered_attrs), self.linter.config.max_attributes),
  448. )
  449. @only_required_for_messages("too-few-public-methods", "too-many-public-methods")
  450. def leave_classdef(self, node: nodes.ClassDef) -> None:
  451. """Check number of public methods."""
  452. my_methods = sum(
  453. 1 for method in node.mymethods() if not method.name.startswith("_")
  454. )
  455. # Does the class contain less than n public methods ?
  456. # This checks only the methods defined in the current class,
  457. # since the user might not have control over the classes
  458. # from the ancestors. It avoids some false positives
  459. # for classes such as unittest.TestCase, which provides
  460. # a lot of assert methods. It doesn't make sense to warn
  461. # when the user subclasses TestCase to add his own tests.
  462. if my_methods > self.linter.config.max_public_methods:
  463. self.add_message(
  464. "too-many-public-methods",
  465. node=node,
  466. args=(my_methods, self.linter.config.max_public_methods),
  467. )
  468. # Stop here if the class is excluded via configuration.
  469. if node.type == "class" and self._exclude_too_few_public_methods:
  470. for ancestor in node.ancestors():
  471. if any(
  472. pattern.match(ancestor.qname())
  473. for pattern in self._exclude_too_few_public_methods
  474. ):
  475. return
  476. # Stop here for exception, metaclass, interface classes and other
  477. # classes for which we don't need to count the methods.
  478. if node.type != "class" or _is_exempt_from_public_methods(node):
  479. return
  480. # Does the class contain more than n public methods ?
  481. # This checks all the methods defined by ancestors and
  482. # by the current class.
  483. all_methods = _count_methods_in_class(node)
  484. if all_methods < self.linter.config.min_public_methods:
  485. self.add_message(
  486. "too-few-public-methods",
  487. node=node,
  488. args=(all_methods, self.linter.config.min_public_methods),
  489. )
  490. @only_required_for_messages(
  491. "too-many-return-statements",
  492. "too-many-branches",
  493. "too-many-arguments",
  494. "too-many-locals",
  495. "too-many-positional-arguments",
  496. "too-many-statements",
  497. "keyword-arg-before-vararg",
  498. )
  499. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  500. """Check function name, docstring, arguments, redefinition,
  501. variable names, max locals.
  502. """
  503. # init branch and returns counters
  504. self._returns.append(0)
  505. # check number of arguments
  506. args = node.args.args + node.args.posonlyargs + node.args.kwonlyargs
  507. pos_args = node.args.args + node.args.posonlyargs
  508. ignored_argument_names = self.linter.config.ignored_argument_names
  509. if args is not None:
  510. ignored_args_num = 0
  511. if ignored_argument_names:
  512. ignored_pos_args_num = sum(
  513. 1 for arg in pos_args if ignored_argument_names.match(arg.name)
  514. )
  515. ignored_kwonly_args_num = sum(
  516. 1
  517. for arg in node.args.kwonlyargs
  518. if ignored_argument_names.match(arg.name)
  519. )
  520. ignored_args_num = ignored_pos_args_num + ignored_kwonly_args_num
  521. argnum = len(args) - ignored_args_num
  522. if argnum > self.linter.config.max_args:
  523. self.add_message(
  524. "too-many-arguments",
  525. node=node,
  526. args=(len(args), self.linter.config.max_args),
  527. )
  528. pos_args_count = (
  529. len(args) - len(node.args.kwonlyargs) - ignored_pos_args_num
  530. )
  531. if pos_args_count > self.linter.config.max_positional_arguments:
  532. self.add_message(
  533. "too-many-positional-arguments",
  534. node=node,
  535. args=(pos_args_count, self.linter.config.max_positional_arguments),
  536. confidence=HIGH,
  537. )
  538. else:
  539. ignored_args_num = 0
  540. # check number of local variables
  541. locnum = len(node.locals) - ignored_args_num
  542. # decrement number of local variables if '_' is one of them
  543. if "_" in node.locals:
  544. locnum -= 1
  545. if locnum > self.linter.config.max_locals:
  546. self.add_message(
  547. "too-many-locals",
  548. node=node,
  549. args=(locnum, self.linter.config.max_locals),
  550. )
  551. # init new statements counter
  552. self._stmts.append(1)
  553. visit_asyncfunctiondef = visit_functiondef
  554. @only_required_for_messages(
  555. "too-many-return-statements",
  556. "too-many-branches",
  557. "too-many-arguments",
  558. "too-many-locals",
  559. "too-many-statements",
  560. )
  561. def leave_functiondef(self, node: nodes.FunctionDef) -> None:
  562. """Most of the work is done here on close:
  563. checks for max returns, branch, return in __init__.
  564. """
  565. returns = self._returns.pop()
  566. if returns > self.linter.config.max_returns:
  567. self.add_message(
  568. "too-many-return-statements",
  569. node=node,
  570. args=(returns, self.linter.config.max_returns),
  571. )
  572. branches = self._branches[node]
  573. if branches > self.linter.config.max_branches:
  574. self.add_message(
  575. "too-many-branches",
  576. node=node,
  577. args=(branches, self.linter.config.max_branches),
  578. )
  579. # check number of statements
  580. stmts = self._stmts.pop()
  581. if stmts > self.linter.config.max_statements:
  582. self.add_message(
  583. "too-many-statements",
  584. node=node,
  585. args=(stmts, self.linter.config.max_statements),
  586. )
  587. leave_asyncfunctiondef = leave_functiondef
  588. def visit_return(self, _: nodes.Return) -> None:
  589. """Count number of returns."""
  590. if not self._returns:
  591. return # return outside function, reported by the base checker
  592. self._returns[-1] += 1
  593. def visit_default(self, node: nodes.NodeNG) -> None:
  594. """Default visit method -> increments the statements counter if
  595. necessary.
  596. """
  597. if node.is_statement:
  598. self._inc_all_stmts(1)
  599. def visit_try(self, node: nodes.Try) -> None:
  600. """Increments the branches counter."""
  601. branches = len(node.handlers)
  602. if node.orelse:
  603. branches += 1
  604. if node.finalbody:
  605. branches += 1
  606. self._inc_branch(node, branches)
  607. self._inc_all_stmts(branches)
  608. @only_required_for_messages("too-many-boolean-expressions", "too-many-branches")
  609. def visit_if(self, node: nodes.If) -> None:
  610. """Increments the branches counter and checks boolean expressions."""
  611. self._check_boolean_expressions(node)
  612. branches = 1
  613. # don't double count If nodes coming from some 'elif'
  614. if node.orelse and not (
  615. len(node.orelse) == 1 and isinstance(node.orelse[0], nodes.If)
  616. ):
  617. branches += 1
  618. self._inc_branch(node, branches)
  619. self._inc_all_stmts(branches)
  620. def _check_boolean_expressions(self, node: nodes.If) -> None:
  621. """Go through "if" node `node` and count its boolean expressions
  622. if the 'if' node test is a BoolOp node.
  623. """
  624. condition = node.test
  625. if not isinstance(condition, nodes.BoolOp):
  626. return
  627. nb_bool_expr = _count_boolean_expressions(condition)
  628. if nb_bool_expr > self.linter.config.max_bool_expr:
  629. self.add_message(
  630. "too-many-boolean-expressions",
  631. node=condition,
  632. args=(nb_bool_expr, self.linter.config.max_bool_expr),
  633. )
  634. def visit_while(self, node: nodes.While) -> None:
  635. """Increments the branches counter."""
  636. branches = 1
  637. if node.orelse:
  638. branches += 1
  639. self._inc_branch(node, branches)
  640. visit_for = visit_while
  641. def visit_match(self, node: nodes.Match) -> None:
  642. """Increments the branches counter."""
  643. self._inc_all_stmts(1)
  644. self._inc_branch(node, len(node.cases))
  645. def _inc_branch(self, node: nodes.NodeNG, branchesnum: int = 1) -> None:
  646. """Increments the branches counter."""
  647. self._branches[node.scope()] += branchesnum
  648. def register(linter: PyLinter) -> None:
  649. linter.register_checker(MisdesignChecker(linter))