basic_checker.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  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. """Basic checker for Python code."""
  5. from __future__ import annotations
  6. import collections
  7. import itertools
  8. from collections.abc import Iterator
  9. from typing import TYPE_CHECKING, Literal, cast
  10. import astroid
  11. from astroid import bases, nodes, objects, util
  12. from pylint import utils as lint_utils
  13. from pylint.checkers import BaseChecker, utils
  14. from pylint.interfaces import HIGH, INFERENCE, Confidence
  15. from pylint.reporters.ureports import nodes as reporter_nodes
  16. from pylint.utils import LinterStats
  17. if TYPE_CHECKING:
  18. from pylint.lint.pylinter import PyLinter
  19. class _BasicChecker(BaseChecker):
  20. """Permits separating multiple checks with the same checker name into
  21. classes/file.
  22. """
  23. name = "basic"
  24. REVERSED_PROTOCOL_METHOD = "__reversed__"
  25. SEQUENCE_PROTOCOL_METHODS = ("__getitem__", "__len__")
  26. REVERSED_METHODS = (SEQUENCE_PROTOCOL_METHODS, (REVERSED_PROTOCOL_METHOD,))
  27. # A mapping from qname -> symbol, to be used when generating messages
  28. # about dangerous default values as arguments
  29. DEFAULT_ARGUMENT_SYMBOLS = dict(
  30. zip(
  31. [".".join(["builtins", x]) for x in ("set", "dict", "list")],
  32. ["set()", "{}", "[]"],
  33. ),
  34. **{
  35. x: f"{x}()"
  36. for x in (
  37. "collections.deque",
  38. "collections.ChainMap",
  39. "collections.Counter",
  40. "collections.OrderedDict",
  41. "collections.defaultdict",
  42. "collections.UserDict",
  43. "collections.UserList",
  44. )
  45. },
  46. )
  47. def report_by_type_stats(
  48. sect: reporter_nodes.Section,
  49. stats: LinterStats,
  50. old_stats: LinterStats | None,
  51. ) -> None:
  52. """Make a report of.
  53. * percentage of different types documented
  54. * percentage of different types with a bad name
  55. """
  56. # percentage of different types documented and/or with a bad name
  57. nice_stats: dict[str, dict[str, str]] = {}
  58. for node_type in ("module", "class", "method", "function"):
  59. total = stats.get_node_count(node_type)
  60. nice_stats[node_type] = {}
  61. if total != 0:
  62. undocumented_node = stats.get_undocumented(node_type)
  63. documented = total - undocumented_node
  64. percent = (documented * 100.0) / total
  65. nice_stats[node_type]["percent_documented"] = f"{percent:.2f}"
  66. badname_node = stats.get_bad_names(node_type)
  67. percent = (badname_node * 100.0) / total
  68. nice_stats[node_type]["percent_badname"] = f"{percent:.2f}"
  69. lines = ["type", "number", "old number", "difference", "%documented", "%badname"]
  70. for node_type in ("module", "class", "method", "function"):
  71. node_type = cast(Literal["function", "class", "method", "module"], node_type)
  72. new = stats.get_node_count(node_type)
  73. old = old_stats.get_node_count(node_type) if old_stats else None
  74. diff_str = lint_utils.diff_string(old, new) if old else None
  75. lines += [
  76. node_type,
  77. str(new),
  78. str(old) if old else "NC",
  79. diff_str if diff_str else "NC",
  80. nice_stats[node_type].get("percent_documented", "0"),
  81. nice_stats[node_type].get("percent_badname", "0"),
  82. ]
  83. sect.append(reporter_nodes.Table(children=lines, cols=6, rheaders=1))
  84. # pylint: disable-next = too-many-public-methods
  85. class BasicChecker(_BasicChecker):
  86. """Basic checker.
  87. Checks for :
  88. * doc strings
  89. * number of arguments, local variables, branches, returns and statements in
  90. functions, methods
  91. * required module attributes
  92. * dangerous default values as arguments
  93. * redefinition of function / method / class
  94. * uses of the global statement
  95. """
  96. name = "basic"
  97. msgs = {
  98. "W0101": (
  99. "Unreachable code",
  100. "unreachable",
  101. 'Used when there is some code behind a "return" or "raise" '
  102. "statement, which will never be accessed.",
  103. ),
  104. "W0102": (
  105. "Dangerous default value %s as argument",
  106. "dangerous-default-value",
  107. "Used when a mutable value as list or dictionary is detected in "
  108. "a default value for an argument.",
  109. ),
  110. "W0104": (
  111. "Statement seems to have no effect",
  112. "pointless-statement",
  113. "Used when a statement doesn't have (or at least seems to) any effect.",
  114. ),
  115. "W0105": (
  116. "String statement has no effect",
  117. "pointless-string-statement",
  118. "Used when a string is used as a statement (which of course "
  119. "has no effect). This is a particular case of W0104 with its "
  120. "own message so you can easily disable it if you're using "
  121. "those strings as documentation, instead of comments.",
  122. ),
  123. "W0106": (
  124. 'Expression "%s" is assigned to nothing',
  125. "expression-not-assigned",
  126. "Used when an expression that is not a function call is assigned "
  127. "to nothing. Probably something else was intended.",
  128. ),
  129. "W0108": (
  130. "Lambda may not be necessary",
  131. "unnecessary-lambda",
  132. "Used when the body of a lambda expression is a function call "
  133. "on the same argument list as the lambda itself; such lambda "
  134. "expressions are in all but a few cases replaceable with the "
  135. "function being called in the body of the lambda.",
  136. ),
  137. "W0109": (
  138. "Duplicate key %r in dictionary",
  139. "duplicate-key",
  140. "Used when a dictionary expression binds the same key multiple times.",
  141. ),
  142. "W0122": (
  143. "Use of exec",
  144. "exec-used",
  145. "Raised when the 'exec' statement is used. It's dangerous to use this "
  146. "function for a user input, and it's also slower than actual code in "
  147. "general. This doesn't mean you should never use it, but you should "
  148. "consider alternatives first and restrict the functions available.",
  149. ),
  150. "W0123": (
  151. "Use of eval",
  152. "eval-used",
  153. 'Used when you use the "eval" function, to discourage its '
  154. "usage. Consider using `ast.literal_eval` for safely evaluating "
  155. "strings containing Python expressions "
  156. "from untrusted sources.",
  157. ),
  158. "W0150": (
  159. "%s statement in finally block may swallow exception",
  160. "lost-exception",
  161. "Used when a break or a return statement is found inside the "
  162. "finally clause of a try...finally block: the exceptions raised "
  163. "in the try clause will be silently swallowed instead of being "
  164. "re-raised.",
  165. ),
  166. "W0199": (
  167. "Assert called on a populated tuple. Did you mean 'assert x,y'?",
  168. "assert-on-tuple",
  169. "A call of assert on a tuple will always evaluate to true if "
  170. "the tuple is not empty, and will always evaluate to false if "
  171. "it is.",
  172. ),
  173. "W0124": (
  174. 'Following "as" with another context manager looks like a tuple.',
  175. "confusing-with-statement",
  176. "Emitted when a `with` statement component returns multiple values "
  177. "and uses name binding with `as` only for a part of those values, "
  178. "as in with ctx() as a, b. This can be misleading, since it's not "
  179. "clear if the context manager returns a tuple or if the node without "
  180. "a name binding is another context manager.",
  181. ),
  182. "W0125": (
  183. "Using a conditional statement with a constant value",
  184. "using-constant-test",
  185. "Emitted when a conditional statement (If or ternary if) "
  186. "uses a constant value for its test. This might not be what "
  187. "the user intended to do.",
  188. ),
  189. "W0126": (
  190. "Using a conditional statement with potentially wrong function or method call due to "
  191. "missing parentheses",
  192. "missing-parentheses-for-call-in-test",
  193. "Emitted when a conditional statement (If or ternary if) "
  194. "seems to wrongly call a function due to missing parentheses",
  195. ),
  196. "W0127": (
  197. "Assigning the same variable %r to itself",
  198. "self-assigning-variable",
  199. "Emitted when we detect that a variable is assigned to itself",
  200. ),
  201. "W0128": (
  202. "Redeclared variable %r in assignment",
  203. "redeclared-assigned-name",
  204. "Emitted when we detect that a variable was redeclared in the same assignment.",
  205. ),
  206. "E0111": (
  207. "The first reversed() argument is not a sequence",
  208. "bad-reversed-sequence",
  209. "Used when the first argument to reversed() builtin "
  210. "isn't a sequence (does not implement __reversed__, "
  211. "nor __getitem__ and __len__",
  212. ),
  213. "E0119": (
  214. "format function is not called on str",
  215. "misplaced-format-function",
  216. "Emitted when format function is not called on str object. "
  217. 'e.g doing print("value: {}").format(123) instead of '
  218. 'print("value: {}".format(123)). This might not be what the user '
  219. "intended to do.",
  220. ),
  221. "W0129": (
  222. "Assert statement has a string literal as its first argument. The assert will %s fail.",
  223. "assert-on-string-literal",
  224. "Used when an assert statement has a string literal as its first argument, which will "
  225. "cause the assert to always pass.",
  226. ),
  227. "W0130": (
  228. "Duplicate value %r in set",
  229. "duplicate-value",
  230. "This message is emitted when a set contains the same value two or more times.",
  231. ),
  232. "W0131": (
  233. "Named expression used without context",
  234. "named-expr-without-context",
  235. "Emitted if named expression is used to do a regular assignment "
  236. "outside a context like if, for, while, or a comprehension.",
  237. ),
  238. "W0133": (
  239. "Exception statement has no effect",
  240. "pointless-exception-statement",
  241. "Used when an exception is created without being assigned, raised or returned "
  242. "for subsequent use elsewhere.",
  243. ),
  244. "W0134": (
  245. "'return' shadowed by the 'finally' clause.",
  246. "return-in-finally",
  247. "Emitted when a 'return' statement is found in a 'finally' block. This will overwrite "
  248. "the return value of a function and should be avoided.",
  249. ),
  250. }
  251. reports = (("RP0101", "Statistics by type", report_by_type_stats),)
  252. def __init__(self, linter: PyLinter) -> None:
  253. super().__init__(linter)
  254. self._trys: list[nodes.Try]
  255. def open(self) -> None:
  256. """Initialize visit variables and statistics."""
  257. py_version = self.linter.config.py_version
  258. self._py38_plus = py_version >= (3, 8)
  259. self._trys = []
  260. self.linter.stats.reset_node_count()
  261. @utils.only_required_for_messages(
  262. "using-constant-test", "missing-parentheses-for-call-in-test"
  263. )
  264. def visit_if(self, node: nodes.If) -> None:
  265. self._check_using_constant_test(node, node.test)
  266. @utils.only_required_for_messages(
  267. "using-constant-test", "missing-parentheses-for-call-in-test"
  268. )
  269. def visit_ifexp(self, node: nodes.IfExp) -> None:
  270. self._check_using_constant_test(node, node.test)
  271. @utils.only_required_for_messages(
  272. "using-constant-test", "missing-parentheses-for-call-in-test"
  273. )
  274. def visit_comprehension(self, node: nodes.Comprehension) -> None:
  275. if node.ifs:
  276. for if_test in node.ifs:
  277. self._check_using_constant_test(node, if_test)
  278. def _check_using_constant_test(
  279. self,
  280. node: nodes.If | nodes.IfExp | nodes.Comprehension,
  281. test: nodes.NodeNG | None,
  282. ) -> None:
  283. const_nodes = (
  284. nodes.Module,
  285. nodes.GeneratorExp,
  286. nodes.Lambda,
  287. nodes.FunctionDef,
  288. nodes.ClassDef,
  289. bases.Generator,
  290. astroid.UnboundMethod,
  291. astroid.BoundMethod,
  292. nodes.Module,
  293. )
  294. structs = (nodes.Dict, nodes.Tuple, nodes.Set, nodes.List)
  295. # These nodes are excepted, since they are not constant
  296. # values, requiring a computation to happen.
  297. except_nodes = (
  298. nodes.Call,
  299. nodes.BinOp,
  300. nodes.BoolOp,
  301. nodes.UnaryOp,
  302. nodes.Subscript,
  303. )
  304. inferred = None
  305. emit = isinstance(test, (nodes.Const, *structs, *const_nodes))
  306. maybe_generator_call = None
  307. if not isinstance(test, except_nodes):
  308. inferred = utils.safe_infer(test)
  309. if isinstance(inferred, util.UninferableBase) and isinstance(
  310. test, nodes.Name
  311. ):
  312. emit, maybe_generator_call = BasicChecker._name_holds_generator(test)
  313. # Emit if calling a function that only returns GeneratorExp (always tests True)
  314. elif isinstance(test, nodes.Call):
  315. maybe_generator_call = test
  316. if maybe_generator_call:
  317. inferred_call = utils.safe_infer(maybe_generator_call.func)
  318. if isinstance(inferred_call, nodes.FunctionDef):
  319. # Can't use all(x) or not any(not x) for this condition, because it
  320. # will return True for empty generators, which is not what we want.
  321. all_returns_were_generator = None
  322. for return_node in inferred_call._get_return_nodes_skip_functions():
  323. if not isinstance(return_node.value, nodes.GeneratorExp):
  324. all_returns_were_generator = False
  325. break
  326. all_returns_were_generator = True
  327. if all_returns_were_generator:
  328. self.add_message(
  329. "using-constant-test", node=node, confidence=INFERENCE
  330. )
  331. return
  332. if emit:
  333. self.add_message("using-constant-test", node=test, confidence=INFERENCE)
  334. elif isinstance(inferred, const_nodes):
  335. # If the constant node is a FunctionDef or Lambda then
  336. # it may be an illicit function call due to missing parentheses
  337. call_inferred = None
  338. try:
  339. # Just forcing the generator to infer all elements.
  340. # astroid.exceptions.InferenceError are false positives
  341. # see https://github.com/pylint-dev/pylint/pull/8185
  342. if isinstance(inferred, (nodes.FunctionDef, nodes.Lambda)):
  343. call_inferred = list(inferred.infer_call_result(node))
  344. except astroid.InferenceError:
  345. call_inferred = None
  346. if call_inferred:
  347. self.add_message(
  348. "missing-parentheses-for-call-in-test",
  349. node=test,
  350. confidence=INFERENCE,
  351. )
  352. self.add_message("using-constant-test", node=test, confidence=INFERENCE)
  353. @staticmethod
  354. def _name_holds_generator(test: nodes.Name) -> tuple[bool, nodes.Call | None]:
  355. """Return whether `test` tests a name certain to hold a generator, or optionally
  356. a call that should be then tested to see if *it* returns only generators.
  357. """
  358. assert isinstance(test, nodes.Name)
  359. emit = False
  360. maybe_generator_call = None
  361. lookup_result = test.frame().lookup(test.name)
  362. if not lookup_result:
  363. return emit, maybe_generator_call
  364. maybe_generator_assigned = (
  365. isinstance(assign_name.parent.value, nodes.GeneratorExp)
  366. for assign_name in lookup_result[1]
  367. if isinstance(assign_name.parent, nodes.Assign)
  368. )
  369. first_item = next(maybe_generator_assigned, None)
  370. if first_item is not None:
  371. # Emit if this variable is certain to hold a generator
  372. if all(itertools.chain((first_item,), maybe_generator_assigned)):
  373. emit = True
  374. # If this variable holds the result of a call, save it for next test
  375. elif (
  376. len(lookup_result[1]) == 1
  377. and isinstance(lookup_result[1][0].parent, nodes.Assign)
  378. and isinstance(lookup_result[1][0].parent.value, nodes.Call)
  379. ):
  380. maybe_generator_call = lookup_result[1][0].parent.value
  381. return emit, maybe_generator_call
  382. def visit_module(self, _: nodes.Module) -> None:
  383. """Check module name, docstring and required arguments."""
  384. self.linter.stats.node_count["module"] += 1
  385. def visit_classdef(self, _: nodes.ClassDef) -> None:
  386. """Check module name, docstring and redefinition
  387. increment branch counter.
  388. """
  389. self.linter.stats.node_count["klass"] += 1
  390. @utils.only_required_for_messages(
  391. "pointless-statement",
  392. "pointless-exception-statement",
  393. "pointless-string-statement",
  394. "expression-not-assigned",
  395. "named-expr-without-context",
  396. )
  397. def visit_expr(self, node: nodes.Expr) -> None:
  398. """Check for various kind of statements without effect."""
  399. expr = node.value
  400. if isinstance(expr, nodes.Const) and isinstance(expr.value, str):
  401. # treat string statement in a separated message
  402. # Handle PEP-257 attribute docstrings.
  403. # An attribute docstring is defined as being a string right after
  404. # an assignment at the module level, class level or __init__ level.
  405. scope = expr.scope()
  406. if isinstance(scope, (nodes.ClassDef, nodes.Module, nodes.FunctionDef)):
  407. if isinstance(scope, nodes.FunctionDef) and scope.name != "__init__":
  408. pass
  409. else:
  410. sibling = expr.previous_sibling()
  411. if (
  412. sibling is not None
  413. and sibling.scope() is scope
  414. and isinstance(
  415. sibling, (nodes.Assign, nodes.AnnAssign, nodes.TypeAlias)
  416. )
  417. ):
  418. return
  419. self.add_message("pointless-string-statement", node=node)
  420. return
  421. # Warn W0133 for exceptions that are used as statements
  422. if isinstance(expr, nodes.Call):
  423. name = ""
  424. if isinstance(expr.func, nodes.Name):
  425. name = expr.func.name
  426. elif isinstance(expr.func, nodes.Attribute):
  427. name = expr.func.attrname
  428. # Heuristic: only run inference for names that begin with an uppercase char
  429. # This reduces W0133's coverage, but retains acceptable runtime performance
  430. # For more details, see: https://github.com/pylint-dev/pylint/issues/8073
  431. inferred = utils.safe_infer(expr) if name[:1].isupper() else None
  432. if isinstance(inferred, objects.ExceptionInstance):
  433. self.add_message(
  434. "pointless-exception-statement", node=node, confidence=INFERENCE
  435. )
  436. return
  437. # Ignore if this is :
  438. # * the unique child of a try/except body
  439. # * a yield statement
  440. # * an ellipsis (which can be used on Python 3 instead of pass)
  441. # warn W0106 if we have any underlying function call (we can't predict
  442. # side effects), else pointless-statement
  443. if (
  444. isinstance(expr, (nodes.Yield, nodes.Await))
  445. or (
  446. isinstance(node.parent, (nodes.Try, nodes.TryStar))
  447. and node.parent.body == [node]
  448. )
  449. or (isinstance(expr, nodes.Const) and expr.value is Ellipsis)
  450. ):
  451. return
  452. if isinstance(expr, nodes.NamedExpr):
  453. self.add_message("named-expr-without-context", node=node, confidence=HIGH)
  454. elif any(expr.nodes_of_class(nodes.Call)):
  455. self.add_message(
  456. "expression-not-assigned", node=node, args=expr.as_string()
  457. )
  458. else:
  459. self.add_message("pointless-statement", node=node)
  460. @staticmethod
  461. def _filter_vararg(
  462. node: nodes.Lambda, call_args: list[nodes.NodeNG]
  463. ) -> Iterator[nodes.NodeNG]:
  464. # Return the arguments for the given call which are
  465. # not passed as vararg.
  466. for arg in call_args:
  467. if isinstance(arg, nodes.Starred):
  468. if (
  469. isinstance(arg.value, nodes.Name)
  470. and arg.value.name != node.args.vararg
  471. ):
  472. yield arg
  473. else:
  474. yield arg
  475. @staticmethod
  476. def _has_variadic_argument(
  477. args: list[nodes.Starred | nodes.Keyword], variadic_name: str
  478. ) -> bool:
  479. return not args or any(
  480. (isinstance(a.value, nodes.Name) and a.value.name != variadic_name)
  481. or not isinstance(a.value, nodes.Name)
  482. for a in args
  483. )
  484. @utils.only_required_for_messages("unnecessary-lambda")
  485. def visit_lambda(self, node: nodes.Lambda) -> None:
  486. """Check whether the lambda is suspicious."""
  487. # if the body of the lambda is a call expression with the same
  488. # argument list as the lambda itself, then the lambda is
  489. # possibly unnecessary and at least suspicious.
  490. if node.args.defaults:
  491. # If the arguments of the lambda include defaults, then a
  492. # judgment cannot be made because there is no way to check
  493. # that the defaults defined by the lambda are the same as
  494. # the defaults defined by the function called in the body
  495. # of the lambda.
  496. return
  497. call = node.body
  498. if not isinstance(call, nodes.Call):
  499. # The body of the lambda must be a function call expression
  500. # for the lambda to be unnecessary.
  501. return
  502. match call.func:
  503. case nodes.Attribute(expr=nodes.Call()):
  504. # Chained call, the intermediate call might
  505. # return something else (but we don't check that, yet).
  506. return
  507. ordinary_args = list(node.args.args)
  508. new_call_args = list(self._filter_vararg(node, call.args))
  509. if node.args.kwarg:
  510. if self._has_variadic_argument(call.keywords, node.args.kwarg):
  511. return
  512. elif call.keywords:
  513. return
  514. if node.args.vararg:
  515. if self._has_variadic_argument(call.starargs, node.args.vararg):
  516. return
  517. elif call.starargs:
  518. return
  519. # The "ordinary" arguments must be in a correspondence such that:
  520. # ordinary_args[i].name == call.args[i].name.
  521. if len(ordinary_args) != len(new_call_args):
  522. return
  523. for arg, passed_arg in zip(ordinary_args, new_call_args):
  524. if not isinstance(passed_arg, nodes.Name):
  525. return
  526. if arg.name != passed_arg.name:
  527. return
  528. # The lambda is necessary if it uses its parameter in the function it is
  529. # calling in the lambda's body
  530. # e.g. lambda foo: (func1 if foo else func2)(foo)
  531. for name in call.func.nodes_of_class(nodes.Name):
  532. if name.lookup(name.name)[0] is node:
  533. return
  534. self.add_message("unnecessary-lambda", line=node.fromlineno, node=node)
  535. @utils.only_required_for_messages("dangerous-default-value")
  536. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  537. """Check function name, docstring, arguments, redefinition,
  538. variable names, max locals.
  539. """
  540. if node.is_method():
  541. self.linter.stats.node_count["method"] += 1
  542. else:
  543. self.linter.stats.node_count["function"] += 1
  544. self._check_dangerous_default(node)
  545. visit_asyncfunctiondef = visit_functiondef
  546. def _check_dangerous_default(self, node: nodes.FunctionDef) -> None:
  547. """Check for dangerous default values as arguments."""
  548. def is_iterable(internal_node: nodes.NodeNG) -> bool:
  549. return isinstance(internal_node, (nodes.List, nodes.Set, nodes.Dict))
  550. defaults = (node.args.defaults or []) + (node.args.kw_defaults or [])
  551. for default in defaults:
  552. if not default:
  553. continue
  554. try:
  555. value = next(default.infer())
  556. except astroid.InferenceError:
  557. continue
  558. if (
  559. isinstance(value, astroid.Instance)
  560. and value.qname() in DEFAULT_ARGUMENT_SYMBOLS
  561. ):
  562. if value is default:
  563. msg = DEFAULT_ARGUMENT_SYMBOLS[value.qname()]
  564. elif isinstance(value, astroid.Instance) or is_iterable(value):
  565. # We are here in the following situation(s):
  566. # * a dict/set/list/tuple call which wasn't inferred
  567. # to a syntax node ({}, () etc.). This can happen
  568. # when the arguments are invalid or unknown to
  569. # the inference.
  570. # * a variable from somewhere else, which turns out to be a list
  571. # or a dict.
  572. if is_iterable(default):
  573. msg = value.pytype()
  574. elif isinstance(default, nodes.Call):
  575. msg = f"{value.name}() ({value.qname()})"
  576. else:
  577. msg = f"{default.as_string()} ({value.qname()})"
  578. else:
  579. # this argument is a name
  580. msg = f"{default.as_string()} ({DEFAULT_ARGUMENT_SYMBOLS[value.qname()]})"
  581. self.add_message("dangerous-default-value", node=node, args=(msg,))
  582. @utils.only_required_for_messages("unreachable", "lost-exception")
  583. def visit_return(self, node: nodes.Return) -> None:
  584. """Return node visitor.
  585. 1 - check if the node has a right sibling (if so, that's some
  586. unreachable code)
  587. 2 - check if the node is inside the 'finally' clause of a 'try...finally'
  588. block
  589. """
  590. self._check_unreachable(node)
  591. # Is it inside final body of a try...finally block ?
  592. self._check_not_in_finally(node, "return", (nodes.FunctionDef,))
  593. @utils.only_required_for_messages("unreachable")
  594. def visit_continue(self, node: nodes.Continue) -> None:
  595. """Check is the node has a right sibling (if so, that's some unreachable
  596. code).
  597. """
  598. self._check_unreachable(node)
  599. @utils.only_required_for_messages("unreachable", "lost-exception")
  600. def visit_break(self, node: nodes.Break) -> None:
  601. """Break node visitor.
  602. 1 - check if the node has a right sibling (if so, that's some
  603. unreachable code)
  604. 2 - check if the node is inside the 'finally' clause of a 'try...finally'
  605. block
  606. """
  607. # 1 - Is it right sibling ?
  608. self._check_unreachable(node)
  609. # 2 - Is it inside final body of a try...finally block ?
  610. self._check_not_in_finally(node, "break", (nodes.For, nodes.While))
  611. @utils.only_required_for_messages("unreachable")
  612. def visit_raise(self, node: nodes.Raise) -> None:
  613. """Check if the node has a right sibling (if so, that's some unreachable
  614. code).
  615. """
  616. self._check_unreachable(node)
  617. def _check_misplaced_format_function(self, call_node: nodes.Call) -> None:
  618. if not isinstance(call_node.func, nodes.Attribute):
  619. return
  620. if call_node.func.attrname != "format":
  621. return
  622. expr = utils.safe_infer(call_node.func.expr)
  623. if isinstance(expr, util.UninferableBase):
  624. return
  625. if not expr:
  626. # we are doubtful on inferred type of node, so here just check if format
  627. # was called on print()
  628. match call_node.func.expr:
  629. case nodes.Call(func=nodes.Name(name="print")):
  630. self.add_message("misplaced-format-function", node=call_node)
  631. @utils.only_required_for_messages(
  632. "eval-used",
  633. "exec-used",
  634. "bad-reversed-sequence",
  635. "misplaced-format-function",
  636. "unreachable",
  637. )
  638. def visit_call(self, node: nodes.Call) -> None:
  639. """Visit a Call node."""
  640. if utils.is_terminating_func(node):
  641. self._check_unreachable(node, confidence=INFERENCE)
  642. self._check_misplaced_format_function(node)
  643. if isinstance(node.func, nodes.Name):
  644. name = node.func.name
  645. # ignore the name if it's not a builtin (i.e. not defined in the
  646. # locals nor globals scope)
  647. if not (name in node.frame() or name in node.root()):
  648. match name:
  649. case "exec":
  650. self.add_message("exec-used", node=node)
  651. case "reversed":
  652. self._check_reversed(node)
  653. case "eval":
  654. self.add_message("eval-used", node=node)
  655. @utils.only_required_for_messages("assert-on-tuple", "assert-on-string-literal")
  656. def visit_assert(self, node: nodes.Assert) -> None:
  657. """Check whether assert is used on a tuple or string literal."""
  658. match node.test:
  659. case nodes.Tuple(elts=elts) if len(elts) > 0:
  660. self.add_message("assert-on-tuple", node=node, confidence=HIGH)
  661. case nodes.Const(value=str() as val):
  662. when = "never" if val else "always"
  663. self.add_message("assert-on-string-literal", node=node, args=(when,))
  664. @utils.only_required_for_messages("duplicate-key")
  665. def visit_dict(self, node: nodes.Dict) -> None:
  666. """Check duplicate key in dictionary."""
  667. keys = set()
  668. for k, _ in node.items:
  669. match k:
  670. case nodes.Const():
  671. key = k.value
  672. case nodes.Attribute():
  673. key = k.as_string()
  674. case _:
  675. continue
  676. if key in keys:
  677. self.add_message("duplicate-key", node=node, args=key)
  678. keys.add(key)
  679. @utils.only_required_for_messages("duplicate-value")
  680. def visit_set(self, node: nodes.Set) -> None:
  681. """Check duplicate value in set."""
  682. values = set()
  683. for v in node.elts:
  684. if isinstance(v, nodes.Const):
  685. value = v.value
  686. else:
  687. continue
  688. if value in values:
  689. self.add_message(
  690. "duplicate-value", node=node, args=value, confidence=HIGH
  691. )
  692. values.add(value)
  693. def visit_try(self, node: nodes.Try) -> None:
  694. """Update try block flag."""
  695. self._trys.append(node)
  696. for final_node in node.finalbody:
  697. for return_node in final_node.nodes_of_class(nodes.Return):
  698. self.add_message("return-in-finally", node=return_node, confidence=HIGH)
  699. def leave_try(self, _: nodes.Try) -> None:
  700. """Update try block flag."""
  701. self._trys.pop()
  702. def _check_unreachable(
  703. self,
  704. node: nodes.Return | nodes.Continue | nodes.Break | nodes.Raise | nodes.Call,
  705. confidence: Confidence = HIGH,
  706. ) -> None:
  707. """Check unreachable code."""
  708. unreachable_statement = node.next_sibling()
  709. if unreachable_statement is not None:
  710. if (
  711. isinstance(node, nodes.Return)
  712. and isinstance(unreachable_statement, nodes.Expr)
  713. and isinstance(unreachable_statement.value, nodes.Yield)
  714. ):
  715. # Don't add 'unreachable' for empty generators.
  716. # Only add warning if 'yield' is followed by another node.
  717. unreachable_statement = unreachable_statement.next_sibling()
  718. if unreachable_statement is None:
  719. return
  720. self.add_message(
  721. "unreachable", node=unreachable_statement, confidence=confidence
  722. )
  723. def _check_not_in_finally(
  724. self,
  725. node: nodes.Break | nodes.Return,
  726. node_name: str,
  727. breaker_classes: tuple[nodes.NodeNG, ...] = (),
  728. ) -> None:
  729. """Check that a node is not inside a 'finally' clause of a
  730. 'try...finally' statement.
  731. If we find a parent which type is in breaker_classes before
  732. a 'try...finally' block we skip the whole check.
  733. """
  734. # if self._trys is empty, we're not an in try block
  735. if not self._trys:
  736. return
  737. # the node could be a grand-grand...-child of the 'try...finally'
  738. _parent = node.parent
  739. _node = node
  740. while _parent and not isinstance(_parent, breaker_classes):
  741. if hasattr(_parent, "finalbody") and _node in _parent.finalbody:
  742. self.add_message("lost-exception", node=node, args=node_name)
  743. return
  744. _node = _parent
  745. _parent = _node.parent
  746. def _check_reversed(self, node: nodes.Call) -> None:
  747. """Check that the argument to `reversed` is a sequence."""
  748. try:
  749. argument = utils.safe_infer(utils.get_argument_from_call(node, position=0))
  750. except utils.NoSuchArgumentError:
  751. pass
  752. else:
  753. match argument:
  754. case util.UninferableBase():
  755. return
  756. case None:
  757. # Nothing was inferred.
  758. # Try to see if we have iter().
  759. if isinstance(node.args[0], nodes.Call):
  760. try:
  761. func = next(node.args[0].func.infer())
  762. except astroid.InferenceError:
  763. return
  764. if getattr(
  765. func, "name", None
  766. ) == "iter" and utils.is_builtin_object(func):
  767. self.add_message("bad-reversed-sequence", node=node)
  768. return
  769. case nodes.List() | nodes.Tuple():
  770. return
  771. case astroid.Instance() if not self._py38_plus:
  772. # dicts are reversible, but only from Python 3.8 onward. Prior to
  773. # that, any class based on dict must explicitly provide a
  774. # __reversed__ method
  775. if any(
  776. ancestor.name == "dict" and utils.is_builtin_object(ancestor)
  777. for ancestor in itertools.chain(
  778. (argument._proxied,), argument._proxied.ancestors()
  779. )
  780. ):
  781. try:
  782. argument.locals[REVERSED_PROTOCOL_METHOD]
  783. except KeyError:
  784. self.add_message("bad-reversed-sequence", node=node)
  785. return
  786. if hasattr(argument, "getattr"):
  787. # everything else is not a proper sequence for reversed()
  788. for methods in REVERSED_METHODS:
  789. for meth in methods:
  790. try:
  791. argument.getattr(meth)
  792. except astroid.NotFoundError:
  793. break
  794. else:
  795. break
  796. else:
  797. self.add_message("bad-reversed-sequence", node=node)
  798. else:
  799. self.add_message("bad-reversed-sequence", node=node)
  800. @utils.only_required_for_messages("confusing-with-statement")
  801. def visit_with(self, node: nodes.With) -> None:
  802. # a "with" statement with multiple managers corresponds
  803. # to one AST "With" node with multiple items
  804. pairs = node.items
  805. if pairs:
  806. for prev_pair, pair in itertools.pairwise(pairs):
  807. if isinstance(prev_pair[1], nodes.AssignName) and (
  808. pair[1] is None and not isinstance(pair[0], nodes.Call)
  809. ):
  810. # Don't emit a message if the second is a function call
  811. # there's no way that can be mistaken for a name assignment.
  812. # If the line number doesn't match
  813. # we assume it's a nested "with".
  814. self.add_message("confusing-with-statement", node=node)
  815. def _check_self_assigning_variable(self, node: nodes.Assign) -> None:
  816. # Detect assigning to the same variable.
  817. scope = node.scope()
  818. scope_locals = scope.locals
  819. rhs_names = []
  820. targets = node.targets
  821. if isinstance(targets[0], nodes.Tuple):
  822. if len(targets) != 1:
  823. # A complex assignment, so bail out early.
  824. return
  825. targets = targets[0].elts
  826. if len(targets) == 1:
  827. # Unpacking a variable into the same name.
  828. return
  829. match node.value:
  830. case nodes.Name():
  831. if len(targets) != 1:
  832. return
  833. rhs_names = [node.value]
  834. case nodes.Tuple():
  835. rhs_count = len(node.value.elts)
  836. if len(targets) != rhs_count or rhs_count == 1:
  837. return
  838. rhs_names = node.value.elts
  839. for target, lhs_name in zip(targets, rhs_names):
  840. if not isinstance(lhs_name, nodes.Name):
  841. continue
  842. if not isinstance(target, nodes.AssignName):
  843. continue
  844. # Check that the scope is different from a class level, which is usually
  845. # a pattern to expose module level attributes as class level ones.
  846. if isinstance(scope, nodes.ClassDef) and target.name in scope_locals:
  847. continue
  848. if target.name == lhs_name.name:
  849. self.add_message(
  850. "self-assigning-variable", args=(target.name,), node=target
  851. )
  852. def _check_redeclared_assign_name(self, targets: list[nodes.NodeNG | None]) -> None:
  853. dummy_variables_rgx = self.linter.config.dummy_variables_rgx
  854. for target in targets:
  855. if not isinstance(target, nodes.Tuple):
  856. continue
  857. found_names = []
  858. for element in target.elts:
  859. if isinstance(element, nodes.Tuple):
  860. self._check_redeclared_assign_name([element])
  861. elif isinstance(element, nodes.AssignName) and element.name != "_":
  862. if dummy_variables_rgx and dummy_variables_rgx.match(element.name):
  863. return
  864. found_names.append(element.name)
  865. names = collections.Counter(found_names)
  866. for name, count in names.most_common():
  867. if count > 1:
  868. self.add_message(
  869. "redeclared-assigned-name", args=(name,), node=target
  870. )
  871. @utils.only_required_for_messages(
  872. "self-assigning-variable", "redeclared-assigned-name"
  873. )
  874. def visit_assign(self, node: nodes.Assign) -> None:
  875. self._check_self_assigning_variable(node)
  876. self._check_redeclared_assign_name(node.targets)
  877. @utils.only_required_for_messages("redeclared-assigned-name")
  878. def visit_for(self, node: nodes.For) -> None:
  879. self._check_redeclared_assign_name([node.target])