Save 37% off PRO during our Black Friday Sale! »

My First Linter (plugin)

887048987be67f10649a9dfacced6606?s=47 Adam
May 15, 2014

My First Linter (plugin)

Lightning talk for the Python Meetup on making a linter plugin for PyLint and Flake8.

887048987be67f10649a9dfacced6606?s=128

Adam

May 15, 2014
Tweet

Transcript

  1. Adam Hitchcock @northisup My First Linter (plugin)

  2. or… how I got sick of crashing production with stupid

    mistakes
  3. problem: pdb.set_trace() ๏ sometimes you are debugging ๏ and solve

    the problem! ๏ and forget to remove all of your debug code ๏ then code review ๏ or worse… git push heroku
  4. solution! ๏ ack --py "import pdb; pdb.set_trace()” ๏ fragile, prone

    to regex errors ๏ not really cool enough for a lightning talk ! ๏ custom linter! ๏ testable ๏ cool enough for a lightning talk
  5. what is a linter ๏ tells you that you are

    bad at your job ๏ stylistically (pep8) ๏ unused variables ๏ just plain incorrect syntax ๏ overly complicated code ๏ not a compiler ๏ tries to look at the code structure ๏ builds an AST
  6. what is an AST anyways? ๏ _ast.Call ๏ args ๏

    col_offset ๏ func ๏ keywords ๏ kwargs ๏ lineno ๏ starargs http://en.wikipedia.org/wiki/Abstract_syntax_tree
  7. linter plugins are hard ๏ What took me so long?

    ๏ bad (and hard to find) docs ๏ not on readthedocs ๏ lots about how to use, not how they work ๏ https://launchpad.net/pyflakes ๏ https://launchpad.net/pylint ๏ Flake8 extension framework is minimal and new-ish ๏ pep8-naming extension did good work to make this manageable #copypasta
  8. Example!

  9. PyLint example class DFUChecker(BaseChecker):! ! __implements__ = IAstroidChecker! ! name

    = 'dont-fudge-up'! msgs = {! 'W4201': ('print at %s:%d', 'print', 'you left a print in your code'),! 'W4202': ('pdb imported %s:%d', 'import-pdb', 'pdb imported, probably not nee 'F4203': ('import debug at %s:%d', 'import-debug', 'import debug will be fata 'F4204': ('pdb.set_trace at %s:%d', 'pdb-set_trace', 'pdb.set_trace will be f }! ! # this is important so that your checker is executed before others! priority = -1! ! def visit_print(self, node):! self.add_message('W4201', node=node, args=snip)! ! def visit_callfunc(self, node):! if node.as_string() == 'pdb.set_trace()':! self.add_message('F4204', node=node, args=snip)! ! def visit_import(self, node):! if node.as_string() == 'import pdb':! self.add_message('W4202', node=node, args=snip)! elif node.as_string() == 'import debug':! self.add_message('F4203', node=node, args=snip)!
  10. “Dude, I usually use flake8”

  11. Flake8 example class DebuggerCheck(DFUASTCheck):! ! cautioned_set_trace = 'D102'! forbidden_set_trace =

    'D502'! ! def visit_call(self, node, parrents):! func_name = getattr(node.func, 'attr', None) or \! ! ! ! getattr(node.func, 'id', None)! func_module = getattr(node.func, 'value', None)! ! if func_name == 'set_trace':! if func_module and func_module.id in ('pdb', 'ipdb'):! yield self.err(node, self.forbidden_set_trace)! else:! yield self.err(node, self.cautioned_set_trace)
  12. get it now ๏ github.com/NorthIsUp/dont-fudge-up ๏ pip install dont-fudge-up

  13. psst, we’re hiring disqus.com/jobs If this was interesting to you...

    dev-ops, django, js, iOS