emptystring.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. # Copyright (c) 2016 Alexander Todorov <atodorov@otb.bg>
  2. # Copyright (c) 2017-2018, 2020 Claudiu Popa <pcmanticore@gmail.com>
  3. # Copyright (c) 2019, 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
  4. # Copyright (c) 2020 hippo91 <guillaume.peillex@gmail.com>
  5. # Copyright (c) 2020 Anthony Sottile <asottile@umich.edu>
  6. # Copyright (c) 2021 Jaehoon Hwang <jaehoonhwang@users.noreply.github.com>
  7. # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
  8. # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
  9. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  10. # For details: https://github.com/PyCQA/pylint/blob/main/LICENSE
  11. """Looks for comparisons to empty string."""
  12. import itertools
  13. from typing import Any, Iterable
  14. from astroid import nodes
  15. from pylint import checkers, interfaces
  16. from pylint.checkers import utils
  17. class CompareToEmptyStringChecker(checkers.BaseChecker):
  18. """Checks for comparisons to empty string.
  19. Most of the times you should use the fact that empty strings are false.
  20. An exception to this rule is when an empty string value is allowed in the program
  21. and has a different meaning than None!
  22. """
  23. __implements__ = (interfaces.IAstroidChecker,)
  24. # configuration section name
  25. name = "compare-to-empty-string"
  26. msgs = {
  27. "C1901": (
  28. "Avoid comparisons to empty string",
  29. "compare-to-empty-string",
  30. "Used when Pylint detects comparison to an empty string constant.",
  31. )
  32. }
  33. priority = -2
  34. options = ()
  35. @utils.check_messages("compare-to-empty-string")
  36. def visit_compare(self, node: nodes.Compare) -> None:
  37. _operators = ["!=", "==", "is not", "is"]
  38. # note: astroid.Compare has the left most operand in node.left
  39. # while the rest are a list of tuples in node.ops
  40. # the format of the tuple is ('compare operator sign', node)
  41. # here we squash everything into `ops` to make it easier for processing later
  42. ops = [("", node.left)]
  43. ops.extend(node.ops)
  44. iter_ops: Iterable[Any] = iter(ops)
  45. ops = list(itertools.chain(*iter_ops))
  46. for ops_idx in range(len(ops) - 2):
  47. op_1 = ops[ops_idx]
  48. op_2 = ops[ops_idx + 1]
  49. op_3 = ops[ops_idx + 2]
  50. error_detected = False
  51. # x ?? ""
  52. if utils.is_empty_str_literal(op_1) and op_2 in _operators:
  53. error_detected = True
  54. # '' ?? X
  55. elif op_2 in _operators and utils.is_empty_str_literal(op_3):
  56. error_detected = True
  57. if error_detected:
  58. self.add_message("compare-to-empty-string", node=node)
  59. def register(linter):
  60. """Required method to auto register this checker."""
  61. linter.register_checker(CompareToEmptyStringChecker(linter))