flake8_plugin.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import ast
  2. from collections import namedtuple
  3. from functools import partial
  4. class SentryVisitor(ast.NodeVisitor):
  5. def __init__(self):
  6. self.errors = []
  7. def visit_ImportFrom(self, node):
  8. if node.module in S003.modules:
  9. for nameproxy in node.names:
  10. if nameproxy.name in S003.names:
  11. self.errors.append(S003(node.lineno, node.col_offset))
  12. break
  13. def visit_Import(self, node):
  14. for alias in node.names:
  15. if alias.name.split(".", 1)[0] in S003.modules:
  16. self.errors.append(S003(node.lineno, node.col_offset))
  17. def visit_Attribute(self, node):
  18. if node.attr in S001.methods:
  19. self.errors.append(S001(node.lineno, node.col_offset, vars=(node.attr,)))
  20. def visit_Name(self, node):
  21. if node.id == "print":
  22. self.errors.append(S002(lineno=node.lineno, col=node.col_offset))
  23. class SentryCheck:
  24. name = "sentry-flake8"
  25. version = "0"
  26. def __init__(self, tree: ast.AST) -> None:
  27. self.tree = tree
  28. def run(self):
  29. visitor = SentryVisitor()
  30. visitor.visit(self.tree)
  31. for e in visitor.errors:
  32. yield self.adapt_error(e)
  33. @classmethod
  34. def adapt_error(cls, e):
  35. """Adapts the extended error namedtuple to be compatible with Flake8."""
  36. return e._replace(message=e.message.format(*e.vars))[:4]
  37. error = namedtuple("error", "lineno col message type vars")
  38. Error = partial(partial, error, message="", type=SentryCheck, vars=())
  39. S001 = Error(
  40. message="S001: Avoid using the {} mock call as it is "
  41. "confusing and prone to causing invalid test "
  42. "behavior."
  43. )
  44. S001.methods = {
  45. "not_called",
  46. "called_once",
  47. "called_once_with",
  48. }
  49. S002 = Error(message="S002: print functions or statements are not allowed.")
  50. S003 = Error(message="S003: Use ``from sentry.utils import json`` instead.")
  51. S003.modules = {"json", "simplejson"}
  52. S003.names = {
  53. "load",
  54. "loads",
  55. "dump",
  56. "dumps",
  57. "JSONEncoder",
  58. "JSONDecodeError",
  59. "_default_encoder",
  60. }