stdlib.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. # Copyright (c) 2013-2014 LOGILAB S.A. (Paris, FRANCE) <contact@logilab.fr>
  2. # Copyright (c) 2013-2014 Google, Inc.
  3. # Copyright (c) 2014-2020 Claudiu Popa <pcmanticore@gmail.com>
  4. # Copyright (c) 2014 Cosmin Poieana <cmin@ropython.org>
  5. # Copyright (c) 2014 Vlad Temian <vladtemian@gmail.com>
  6. # Copyright (c) 2014 Arun Persaud <arun@nubati.net>
  7. # Copyright (c) 2015 Cezar <celnazli@bitdefender.com>
  8. # Copyright (c) 2015 Chris Rebert <code@rebertia.com>
  9. # Copyright (c) 2015 Ionel Cristian Maries <contact@ionelmc.ro>
  10. # Copyright (c) 2016 Jared Garst <cultofjared@gmail.com>
  11. # Copyright (c) 2017 Renat Galimov <renat2017@gmail.com>
  12. # Copyright (c) 2017 Martin <MartinBasti@users.noreply.github.com>
  13. # Copyright (c) 2017 Christopher Zurcher <zurcher@users.noreply.github.com>
  14. # Copyright (c) 2017 Łukasz Rogalski <rogalski.91@gmail.com>
  15. # Copyright (c) 2018 Lucas Cimon <lucas.cimon@gmail.com>
  16. # Copyright (c) 2018 Banjamin Freeman <befreeman@users.noreply.github.com>
  17. # Copyright (c) 2018 Ioana Tagirta <ioana.tagirta@gmail.com>
  18. # Copyright (c) 2019-2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  19. # Copyright (c) 2019 Julien Palard <julien@palard.fr>
  20. # Copyright (c) 2019 laike9m <laike9m@users.noreply.github.com>
  21. # Copyright (c) 2019 Hugo van Kemenade <hugovk@users.noreply.github.com>
  22. # Copyright (c) 2019 Robert Schweizer <robert_schweizer@gmx.de>
  23. # Copyright (c) 2019 fadedDexofan <fadedDexofan@gmail.com>
  24. # Copyright (c) 2020 Sorin Sbarnea <ssbarnea@redhat.com>
  25. # Copyright (c) 2020 Federico Bond <federicobond@gmail.com>
  26. # Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
  27. # Copyright (c) 2020 谭九鼎 <109224573@qq.com>
  28. # Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
  29. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  30. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  31. # Copyright (c) 2021 Yilei "Dolee" Yang <yileiyang@google.com>
  32. # Copyright (c) 2021 Matus Valo <matusvalo@users.noreply.github.com>
  33. # Copyright (c) 2021 victor <16359131+jiajunsu@users.noreply.github.com>
  34. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  35. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  36. """Checkers for various standard library functions."""
  37. import sys
  38. from collections.abc import Iterable
  39. from typing import Any, Dict, Optional, Set
  40. import astroid
  41. from astroid import nodes
  42. from pylint.checkers import BaseChecker, DeprecatedMixin, utils
  43. from pylint.interfaces import IAstroidChecker
  44. from pylint.lint import PyLinter
  45. OPEN_FILES_MODE = ("open", "file")
  46. OPEN_FILES_ENCODING = ("open", "read_text", "write_text")
  47. UNITTEST_CASE = "unittest.case"
  48. THREADING_THREAD = "threading.Thread"
  49. COPY_COPY = "copy.copy"
  50. OS_ENVIRON = "os._Environ"
  51. ENV_GETTERS = ("os.getenv",)
  52. SUBPROCESS_POPEN = "subprocess.Popen"
  53. SUBPROCESS_RUN = "subprocess.run"
  54. OPEN_MODULE = {"_io", "pathlib"}
  55. DEBUG_BREAKPOINTS = ("builtins.breakpoint", "sys.breakpointhook", "pdb.set_trace")
  56. DEPRECATED_MODULES = {
  57. (0, 0, 0): {"tkinter.tix", "fpectl"},
  58. (3, 2, 0): {"optparse"},
  59. (3, 4, 0): {"imp"},
  60. (3, 5, 0): {"formatter"},
  61. (3, 6, 0): {"asynchat", "asyncore"},
  62. (3, 7, 0): {"macpath"},
  63. (3, 9, 0): {"lib2to3", "parser", "symbol", "binhex"},
  64. }
  65. DEPRECATED_ARGUMENTS = {
  66. (0, 0, 0): {
  67. "int": ((None, "x"),),
  68. "bool": ((None, "x"),),
  69. "float": ((None, "x"),),
  70. },
  71. (3, 8, 0): {
  72. "asyncio.tasks.sleep": ((None, "loop"),),
  73. "asyncio.tasks.gather": ((None, "loop"),),
  74. "asyncio.tasks.shield": ((None, "loop"),),
  75. "asyncio.tasks.wait_for": ((None, "loop"),),
  76. "asyncio.tasks.wait": ((None, "loop"),),
  77. "asyncio.tasks.as_completed": ((None, "loop"),),
  78. "asyncio.subprocess.create_subprocess_exec": ((None, "loop"),),
  79. "asyncio.subprocess.create_subprocess_shell": ((4, "loop"),),
  80. "gettext.translation": ((5, "codeset"),),
  81. "gettext.install": ((2, "codeset"),),
  82. "functools.partialmethod": ((None, "func"),),
  83. "weakref.finalize": ((None, "func"), (None, "obj")),
  84. "profile.Profile.runcall": ((None, "func"),),
  85. "cProfile.Profile.runcall": ((None, "func"),),
  86. "bdb.Bdb.runcall": ((None, "func"),),
  87. "trace.Trace.runfunc": ((None, "func"),),
  88. "curses.wrapper": ((None, "func"),),
  89. "unittest.case.TestCase.addCleanup": ((None, "function"),),
  90. "concurrent.futures.thread.ThreadPoolExecutor.submit": ((None, "fn"),),
  91. "concurrent.futures.process.ProcessPoolExecutor.submit": ((None, "fn"),),
  92. "contextlib._BaseExitStack.callback": ((None, "callback"),),
  93. "contextlib.AsyncExitStack.push_async_callback": ((None, "callback"),),
  94. "multiprocessing.managers.Server.create": ((None, "c"), (None, "typeid")),
  95. "multiprocessing.managers.SharedMemoryServer.create": (
  96. (None, "c"),
  97. (None, "typeid"),
  98. ),
  99. },
  100. (3, 9, 0): {"random.Random.shuffle": ((1, "random"),)},
  101. }
  102. DEPRECATED_DECORATORS = {
  103. (3, 8, 0): {"asyncio.coroutine"},
  104. (3, 3, 0): {
  105. "abc.abstractclassmethod",
  106. "abc.abstractstaticmethod",
  107. "abc.abstractproperty",
  108. },
  109. }
  110. DEPRECATED_METHODS: Dict = {
  111. 0: {
  112. "cgi.parse_qs",
  113. "cgi.parse_qsl",
  114. "ctypes.c_buffer",
  115. "distutils.command.register.register.check_metadata",
  116. "distutils.command.sdist.sdist.check_metadata",
  117. "tkinter.Misc.tk_menuBar",
  118. "tkinter.Menu.tk_bindForTraversal",
  119. },
  120. 2: {
  121. (2, 6, 0): {
  122. "commands.getstatus",
  123. "os.popen2",
  124. "os.popen3",
  125. "os.popen4",
  126. "macostools.touched",
  127. },
  128. (2, 7, 0): {
  129. "unittest.case.TestCase.assertEquals",
  130. "unittest.case.TestCase.assertNotEquals",
  131. "unittest.case.TestCase.assertAlmostEquals",
  132. "unittest.case.TestCase.assertNotAlmostEquals",
  133. "unittest.case.TestCase.assert_",
  134. "xml.etree.ElementTree.Element.getchildren",
  135. "xml.etree.ElementTree.Element.getiterator",
  136. "xml.etree.ElementTree.XMLParser.getiterator",
  137. "xml.etree.ElementTree.XMLParser.doctype",
  138. },
  139. },
  140. 3: {
  141. (3, 0, 0): {
  142. "inspect.getargspec",
  143. "failUnlessEqual",
  144. "assertEquals",
  145. "failIfEqual",
  146. "assertNotEquals",
  147. "failUnlessAlmostEqual",
  148. "assertAlmostEquals",
  149. "failIfAlmostEqual",
  150. "assertNotAlmostEquals",
  151. "failUnless",
  152. "assert_",
  153. "failUnlessRaises",
  154. "failIf",
  155. "assertRaisesRegexp",
  156. "assertRegexpMatches",
  157. "assertNotRegexpMatches",
  158. },
  159. (3, 1, 0): {
  160. "base64.encodestring",
  161. "base64.decodestring",
  162. "ntpath.splitunc",
  163. "os.path.splitunc",
  164. "os.stat_float_times",
  165. },
  166. (3, 2, 0): {
  167. "cgi.escape",
  168. "configparser.RawConfigParser.readfp",
  169. "xml.etree.ElementTree.Element.getchildren",
  170. "xml.etree.ElementTree.Element.getiterator",
  171. "xml.etree.ElementTree.XMLParser.getiterator",
  172. "xml.etree.ElementTree.XMLParser.doctype",
  173. },
  174. (3, 3, 0): {
  175. "inspect.getmoduleinfo",
  176. "logging.warn",
  177. "logging.Logger.warn",
  178. "logging.LoggerAdapter.warn",
  179. "nntplib._NNTPBase.xpath",
  180. "platform.popen",
  181. "sqlite3.OptimizedUnicode",
  182. "time.clock",
  183. },
  184. (3, 4, 0): {
  185. "importlib.find_loader",
  186. "plistlib.readPlist",
  187. "plistlib.writePlist",
  188. "plistlib.readPlistFromBytes",
  189. "plistlib.writePlistToBytes",
  190. },
  191. (3, 4, 4): {"asyncio.tasks.async"},
  192. (3, 5, 0): {
  193. "fractions.gcd",
  194. "inspect.formatargspec",
  195. "inspect.getcallargs",
  196. "platform.linux_distribution",
  197. "platform.dist",
  198. },
  199. (3, 6, 0): {
  200. "importlib._bootstrap_external.FileLoader.load_module",
  201. "_ssl.RAND_pseudo_bytes",
  202. },
  203. (3, 7, 0): {
  204. "sys.set_coroutine_wrapper",
  205. "sys.get_coroutine_wrapper",
  206. "aifc.openfp",
  207. "threading.Thread.isAlive",
  208. "asyncio.Task.current_task",
  209. "asyncio.Task.all_task",
  210. "locale.format",
  211. "ssl.wrap_socket",
  212. "ssl.match_hostname",
  213. "sunau.openfp",
  214. "wave.openfp",
  215. },
  216. (3, 8, 0): {
  217. "gettext.lgettext",
  218. "gettext.ldgettext",
  219. "gettext.lngettext",
  220. "gettext.ldngettext",
  221. "gettext.bind_textdomain_codeset",
  222. "gettext.NullTranslations.output_charset",
  223. "gettext.NullTranslations.set_output_charset",
  224. "threading.Thread.isAlive",
  225. },
  226. (3, 9, 0): {
  227. "binascii.b2a_hqx",
  228. "binascii.a2b_hqx",
  229. "binascii.rlecode_hqx",
  230. "binascii.rledecode_hqx",
  231. },
  232. (3, 10, 0): {
  233. "_sqlite3.enable_shared_cache",
  234. "pathlib.Path.link_to",
  235. "zipimport.zipimporter.load_module",
  236. "zipimport.zipimporter.find_module",
  237. "zipimport.zipimporter.find_loader",
  238. "threading.currentThread",
  239. "threading.activeCount",
  240. "threading.Condition.notifyAll",
  241. "threading.Event.isSet",
  242. "threading.Thread.setName",
  243. "threading.Thread.getName",
  244. "threading.Thread.isDaemon",
  245. "threading.Thread.setDaemon",
  246. "cgi.log",
  247. },
  248. },
  249. }
  250. DEPRECATED_CLASSES = {
  251. (3, 3, 0): {
  252. "importlib.abc": {
  253. "Finder",
  254. },
  255. "pkgutil": {
  256. "ImpImporter",
  257. "ImpLoader",
  258. },
  259. "collections": {
  260. "Awaitable",
  261. "Coroutine",
  262. "AsyncIterable",
  263. "AsyncIterator",
  264. "AsyncGenerator",
  265. "Hashable",
  266. "Iterable",
  267. "Iterator",
  268. "Generator",
  269. "Reversible",
  270. "Sized",
  271. "Container",
  272. "Callable",
  273. "Collection",
  274. "Set",
  275. "MutableSet",
  276. "Mapping",
  277. "MutableMapping",
  278. "MappingView",
  279. "KeysView",
  280. "ItemsView",
  281. "ValuesView",
  282. "Sequence",
  283. "MutableSequence",
  284. "ByteString",
  285. },
  286. },
  287. (3, 9, 0): {
  288. "smtpd": {
  289. "MailmanProxy",
  290. }
  291. },
  292. }
  293. def _check_mode_str(mode):
  294. # check type
  295. if not isinstance(mode, str):
  296. return False
  297. # check syntax
  298. modes = set(mode)
  299. _mode = "rwatb+Ux"
  300. creating = "x" in modes
  301. if modes - set(_mode) or len(mode) > len(modes):
  302. return False
  303. # check logic
  304. reading = "r" in modes
  305. writing = "w" in modes
  306. appending = "a" in modes
  307. text = "t" in modes
  308. binary = "b" in modes
  309. if "U" in modes:
  310. if writing or appending or creating:
  311. return False
  312. reading = True
  313. if text and binary:
  314. return False
  315. total = reading + writing + appending + creating
  316. if total > 1:
  317. return False
  318. if not (reading or writing or appending or creating):
  319. return False
  320. return True
  321. class StdlibChecker(DeprecatedMixin, BaseChecker):
  322. __implements__ = (IAstroidChecker,)
  323. name = "stdlib"
  324. msgs = {
  325. "W1501": (
  326. '"%s" is not a valid mode for open.',
  327. "bad-open-mode",
  328. "Python supports: r, w, a[, x] modes with b, +, "
  329. "and U (only with r) options. "
  330. "See https://docs.python.org/2/library/functions.html#open",
  331. ),
  332. "W1502": (
  333. "Using datetime.time in a boolean context.",
  334. "boolean-datetime",
  335. "Using datetime.time in a boolean context can hide "
  336. "subtle bugs when the time they represent matches "
  337. "midnight UTC. This behaviour was fixed in Python 3.5. "
  338. "See https://bugs.python.org/issue13936 for reference.",
  339. {"maxversion": (3, 5)},
  340. ),
  341. "W1503": (
  342. "Redundant use of %s with constant value %r",
  343. "redundant-unittest-assert",
  344. "The first argument of assertTrue and assertFalse is "
  345. "a condition. If a constant is passed as parameter, that "
  346. "condition will be always true. In this case a warning "
  347. "should be emitted.",
  348. ),
  349. "W1505": (
  350. "Using deprecated method %s()",
  351. "deprecated-method",
  352. "The method is marked as deprecated and will be removed in "
  353. "a future version of Python. Consider looking for an "
  354. "alternative in the documentation.",
  355. ),
  356. "W1506": (
  357. "threading.Thread needs the target function",
  358. "bad-thread-instantiation",
  359. "The warning is emitted when a threading.Thread class "
  360. "is instantiated without the target function being passed. "
  361. "By default, the first parameter is the group param, not the target param. ",
  362. ),
  363. "W1507": (
  364. "Using copy.copy(os.environ). Use os.environ.copy() instead. ",
  365. "shallow-copy-environ",
  366. "os.environ is not a dict object but proxy object, so "
  367. "shallow copy has still effects on original object. "
  368. "See https://bugs.python.org/issue15373 for reference. ",
  369. ),
  370. "E1507": (
  371. "%s does not support %s type argument",
  372. "invalid-envvar-value",
  373. "Env manipulation functions support only string type arguments. "
  374. "See https://docs.python.org/3/library/os.html#os.getenv. ",
  375. ),
  376. "W1508": (
  377. "%s default type is %s. Expected str or None.",
  378. "invalid-envvar-default",
  379. "Env manipulation functions return None or str values. "
  380. "Supplying anything different as a default may cause bugs. "
  381. "See https://docs.python.org/3/library/os.html#os.getenv. ",
  382. ),
  383. "W1509": (
  384. "Using preexec_fn keyword which may be unsafe in the presence "
  385. "of threads",
  386. "subprocess-popen-preexec-fn",
  387. "The preexec_fn parameter is not safe to use in the presence "
  388. "of threads in your application. The child process could "
  389. "deadlock before exec is called. If you must use it, keep it "
  390. "trivial! Minimize the number of libraries you call into."
  391. "https://docs.python.org/3/library/subprocess.html#popen-constructor",
  392. ),
  393. "W1510": (
  394. "Using subprocess.run without explicitly set `check` is not recommended.",
  395. "subprocess-run-check",
  396. "The check parameter should always be used with explicitly set "
  397. "`check` keyword to make clear what the error-handling behavior is."
  398. "https://docs.python.org/3/library/subprocess.html#subprocess.run",
  399. ),
  400. "W1511": (
  401. "Using deprecated argument %s of method %s()",
  402. "deprecated-argument",
  403. "The argument is marked as deprecated and will be removed in the future.",
  404. ),
  405. "W1512": (
  406. "Using deprecated class %s of module %s",
  407. "deprecated-class",
  408. "The class is marked as deprecated and will be removed in the future.",
  409. ),
  410. "W1513": (
  411. "Using deprecated decorator %s()",
  412. "deprecated-decorator",
  413. "The decorator is marked as deprecated and will be removed in the future.",
  414. ),
  415. "W1514": (
  416. "Using open without explicitly specifying an encoding",
  417. "unspecified-encoding",
  418. "It is better to specify an encoding when opening documents. "
  419. "Using the system default implicitly can create problems on other operating systems. "
  420. "See https://www.python.org/dev/peps/pep-0597/",
  421. ),
  422. "W1515": (
  423. "Leaving functions creating breakpoints in production code is not recommended",
  424. "forgotten-debug-statement",
  425. "Calls to breakpoint(), sys.breakpointhook() and pdb.set_trace() should be removed "
  426. "from code that is not actively being debugged.",
  427. ),
  428. }
  429. def __init__(
  430. self, linter: Optional[PyLinter] = None
  431. ): # pylint: disable=super-init-not-called # See https://github.com/PyCQA/pylint/issues/4941
  432. BaseChecker.__init__(self, linter)
  433. self._deprecated_methods: Set[Any] = set()
  434. self._deprecated_methods.update(DEPRECATED_METHODS[0])
  435. for since_vers, func_list in DEPRECATED_METHODS[sys.version_info[0]].items():
  436. if since_vers <= sys.version_info:
  437. self._deprecated_methods.update(func_list)
  438. self._deprecated_attributes = {}
  439. for since_vers, func_list in DEPRECATED_ARGUMENTS.items():
  440. if since_vers <= sys.version_info:
  441. self._deprecated_attributes.update(func_list)
  442. self._deprecated_classes = {}
  443. for since_vers, class_list in DEPRECATED_CLASSES.items():
  444. if since_vers <= sys.version_info:
  445. self._deprecated_classes.update(class_list)
  446. self._deprecated_modules = set()
  447. for since_vers, mod_list in DEPRECATED_MODULES.items():
  448. if since_vers <= sys.version_info:
  449. self._deprecated_modules.update(mod_list)
  450. self._deprecated_decorators = set()
  451. for since_vers, decorator_list in DEPRECATED_DECORATORS.items():
  452. if since_vers <= sys.version_info:
  453. self._deprecated_decorators.update(decorator_list)
  454. def _check_bad_thread_instantiation(self, node):
  455. if not node.kwargs and not node.keywords and len(node.args) <= 1:
  456. self.add_message("bad-thread-instantiation", node=node)
  457. def _check_for_preexec_fn_in_popen(self, node):
  458. if node.keywords:
  459. for keyword in node.keywords:
  460. if keyword.arg == "preexec_fn":
  461. self.add_message("subprocess-popen-preexec-fn", node=node)
  462. def _check_for_check_kw_in_run(self, node):
  463. kwargs = {keyword.arg for keyword in (node.keywords or ())}
  464. if "check" not in kwargs:
  465. self.add_message("subprocess-run-check", node=node)
  466. def _check_shallow_copy_environ(self, node: nodes.Call) -> None:
  467. arg = utils.get_argument_from_call(node, position=0)
  468. try:
  469. inferred_args = arg.inferred()
  470. except astroid.InferenceError:
  471. return
  472. for inferred in inferred_args:
  473. if inferred.qname() == OS_ENVIRON:
  474. self.add_message("shallow-copy-environ", node=node)
  475. break
  476. @utils.check_messages(
  477. "bad-open-mode",
  478. "redundant-unittest-assert",
  479. "deprecated-method",
  480. "deprecated-argument",
  481. "bad-thread-instantiation",
  482. "shallow-copy-environ",
  483. "invalid-envvar-value",
  484. "invalid-envvar-default",
  485. "subprocess-popen-preexec-fn",
  486. "subprocess-run-check",
  487. "deprecated-class",
  488. "unspecified-encoding",
  489. "forgotten-debug-statement",
  490. )
  491. def visit_call(self, node: nodes.Call) -> None:
  492. """Visit a Call node."""
  493. self.check_deprecated_class_in_call(node)
  494. for inferred in utils.infer_all(node.func):
  495. if inferred is astroid.Uninferable:
  496. continue
  497. if inferred.root().name in OPEN_MODULE:
  498. if (
  499. isinstance(node.func, nodes.Name)
  500. and node.func.name in OPEN_FILES_MODE
  501. ):
  502. self._check_open_mode(node)
  503. if (
  504. isinstance(node.func, nodes.Name)
  505. and node.func.name in OPEN_FILES_ENCODING
  506. or isinstance(node.func, nodes.Attribute)
  507. and node.func.attrname in OPEN_FILES_ENCODING
  508. ):
  509. self._check_open_encoded(node, inferred.root().name)
  510. elif inferred.root().name == UNITTEST_CASE:
  511. self._check_redundant_assert(node, inferred)
  512. elif isinstance(inferred, nodes.ClassDef):
  513. if inferred.qname() == THREADING_THREAD:
  514. self._check_bad_thread_instantiation(node)
  515. elif inferred.qname() == SUBPROCESS_POPEN:
  516. self._check_for_preexec_fn_in_popen(node)
  517. elif isinstance(inferred, nodes.FunctionDef):
  518. name = inferred.qname()
  519. if name == COPY_COPY:
  520. self._check_shallow_copy_environ(node)
  521. elif name in ENV_GETTERS:
  522. self._check_env_function(node, inferred)
  523. elif name == SUBPROCESS_RUN:
  524. self._check_for_check_kw_in_run(node)
  525. elif name in DEBUG_BREAKPOINTS:
  526. self.add_message("forgotten-debug-statement", node=node)
  527. self.check_deprecated_method(node, inferred)
  528. @utils.check_messages("boolean-datetime")
  529. def visit_unaryop(self, node: nodes.UnaryOp) -> None:
  530. if node.op == "not":
  531. self._check_datetime(node.operand)
  532. @utils.check_messages("boolean-datetime")
  533. def visit_if(self, node: nodes.If) -> None:
  534. self._check_datetime(node.test)
  535. @utils.check_messages("boolean-datetime")
  536. def visit_ifexp(self, node: nodes.IfExp) -> None:
  537. self._check_datetime(node.test)
  538. @utils.check_messages("boolean-datetime")
  539. def visit_boolop(self, node: nodes.BoolOp) -> None:
  540. for value in node.values:
  541. self._check_datetime(value)
  542. def _check_redundant_assert(self, node, infer):
  543. if (
  544. isinstance(infer, astroid.BoundMethod)
  545. and node.args
  546. and isinstance(node.args[0], nodes.Const)
  547. and infer.name in {"assertTrue", "assertFalse"}
  548. ):
  549. self.add_message(
  550. "redundant-unittest-assert",
  551. args=(infer.name, node.args[0].value),
  552. node=node,
  553. )
  554. def _check_datetime(self, node):
  555. """Check that a datetime was inferred.
  556. If so, emit boolean-datetime warning.
  557. """
  558. try:
  559. inferred = next(node.infer())
  560. except astroid.InferenceError:
  561. return
  562. if (
  563. isinstance(inferred, astroid.Instance)
  564. and inferred.qname() == "datetime.time"
  565. ):
  566. self.add_message("boolean-datetime", node=node)
  567. def _check_open_mode(self, node):
  568. """Check that the mode argument of an open or file call is valid."""
  569. try:
  570. mode_arg = utils.get_argument_from_call(node, position=1, keyword="mode")
  571. except utils.NoSuchArgumentError:
  572. return
  573. if mode_arg:
  574. mode_arg = utils.safe_infer(mode_arg)
  575. if isinstance(mode_arg, nodes.Const) and not _check_mode_str(
  576. mode_arg.value
  577. ):
  578. self.add_message("bad-open-mode", node=node, args=mode_arg.value)
  579. def _check_open_encoded(self, node: nodes.Call, open_module: str) -> None:
  580. """Check that the encoded argument of an open call is valid."""
  581. mode_arg = None
  582. try:
  583. if open_module == "_io":
  584. mode_arg = utils.get_argument_from_call(
  585. node, position=1, keyword="mode"
  586. )
  587. elif open_module == "pathlib":
  588. mode_arg = utils.get_argument_from_call(
  589. node, position=0, keyword="mode"
  590. )
  591. except utils.NoSuchArgumentError:
  592. pass
  593. if mode_arg:
  594. mode_arg = utils.safe_infer(mode_arg)
  595. if (
  596. not mode_arg
  597. or isinstance(mode_arg, nodes.Const)
  598. and "b" not in mode_arg.value
  599. ):
  600. encoding_arg = None
  601. try:
  602. if open_module == "pathlib" and node.func.attrname == "read_text":
  603. encoding_arg = utils.get_argument_from_call(
  604. node, position=0, keyword="encoding"
  605. )
  606. else:
  607. encoding_arg = utils.get_argument_from_call(
  608. node, position=None, keyword="encoding"
  609. )
  610. except utils.NoSuchArgumentError:
  611. self.add_message("unspecified-encoding", node=node)
  612. if encoding_arg:
  613. encoding_arg = utils.safe_infer(encoding_arg)
  614. if isinstance(encoding_arg, nodes.Const) and encoding_arg.value is None:
  615. self.add_message("unspecified-encoding", node=node)
  616. def _check_env_function(self, node, infer):
  617. env_name_kwarg = "key"
  618. env_value_kwarg = "default"
  619. if node.keywords:
  620. kwargs = {keyword.arg: keyword.value for keyword in node.keywords}
  621. else:
  622. kwargs = None
  623. if node.args:
  624. env_name_arg = node.args[0]
  625. elif kwargs and env_name_kwarg in kwargs:
  626. env_name_arg = kwargs[env_name_kwarg]
  627. else:
  628. env_name_arg = None
  629. if env_name_arg:
  630. self._check_invalid_envvar_value(
  631. node=node,
  632. message="invalid-envvar-value",
  633. call_arg=utils.safe_infer(env_name_arg),
  634. infer=infer,
  635. allow_none=False,
  636. )
  637. if len(node.args) == 2:
  638. env_value_arg = node.args[1]
  639. elif kwargs and env_value_kwarg in kwargs:
  640. env_value_arg = kwargs[env_value_kwarg]
  641. else:
  642. env_value_arg = None
  643. if env_value_arg:
  644. self._check_invalid_envvar_value(
  645. node=node,
  646. infer=infer,
  647. message="invalid-envvar-default",
  648. call_arg=utils.safe_infer(env_value_arg),
  649. allow_none=True,
  650. )
  651. def _check_invalid_envvar_value(self, node, infer, message, call_arg, allow_none):
  652. if call_arg in (astroid.Uninferable, None):
  653. return
  654. name = infer.qname()
  655. if isinstance(call_arg, nodes.Const):
  656. emit = False
  657. if call_arg.value is None:
  658. emit = not allow_none
  659. elif not isinstance(call_arg.value, str):
  660. emit = True
  661. if emit:
  662. self.add_message(message, node=node, args=(name, call_arg.pytype()))
  663. else:
  664. self.add_message(message, node=node, args=(name, call_arg.pytype()))
  665. def deprecated_modules(self):
  666. """Callback returning the deprecated modules."""
  667. return self._deprecated_modules
  668. def deprecated_methods(self):
  669. return self._deprecated_methods
  670. def deprecated_arguments(self, method: str):
  671. return self._deprecated_attributes.get(method, ())
  672. def deprecated_classes(self, module: str):
  673. return self._deprecated_classes.get(module, ())
  674. def deprecated_decorators(self) -> Iterable:
  675. return self._deprecated_decorators
  676. def register(linter):
  677. """required method to auto register this checker"""
  678. linter.register_checker(StdlibChecker(linter))