Upgrade to Pro — share decks privately, control downloads, hide ads and more …

My First Linter (plugin)

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for Adam Adam
May 15, 2014

My First Linter (plugin)

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

Avatar for Adam

Adam

May 15, 2014
Tweet

More Decks by Adam

Other Decks in Technology

Transcript

  1. 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
  2. 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
  3. 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
  4. what is an AST anyways? ๏ _ast.Call ๏ args ๏

    col_offset ๏ func ๏ keywords ๏ kwargs ๏ lineno ๏ starargs http://en.wikipedia.org/wiki/Abstract_syntax_tree
  5. 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
  6. 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)!
  7. 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)