| 1 | |
|---|
| 2 | """Pylint plugin checking for trailing whitespace.""" |
|---|
| 3 | |
|---|
| 4 | |
|---|
| 5 | from pylint import interfaces, checkers |
|---|
| 6 | from logilab.astng import nodes, raw_building, utils |
|---|
| 7 | from logilab.common import interface |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | class BasicLinesChecker(checkers.BaseChecker): |
|---|
| 11 | |
|---|
| 12 | __implements__ = (interfaces.IRawChecker, interfaces.IASTNGChecker) |
|---|
| 13 | |
|---|
| 14 | name = 'pkgcore-lines' |
|---|
| 15 | |
|---|
| 16 | # XXX move some of those over to RewriteDemandload somehow |
|---|
| 17 | # (current monkey patch running the rewriter does not support that) |
|---|
| 18 | |
|---|
| 19 | msgs = { |
|---|
| 20 | 'CPC01': ('line too long', |
|---|
| 21 | 'More complete version of the standard line too long check'), |
|---|
| 22 | 'CPC02': ('trailing whitespace', 'trailing whitespace sucks.'), |
|---|
| 23 | 'WPC01': ('demandload with arglen != 2 ignored', |
|---|
| 24 | 'A call which is probably a demandload has the wrong number ' |
|---|
| 25 | 'of arguments. Either fix the checker to not detect it as ' |
|---|
| 26 | 'demandload when it is really not or fix the code to call ' |
|---|
| 27 | 'demandload correctly.'), |
|---|
| 28 | 'WPC02': ('demandload with non-string-constant arg ignored.', |
|---|
| 29 | 'A call which is probably a demandload has a second arg ' |
|---|
| 30 | 'that is not a string constant. Fix the code to cooperate ' |
|---|
| 31 | 'with the dumb checker.'), |
|---|
| 32 | } |
|---|
| 33 | |
|---|
| 34 | def process_module(self, stream): |
|---|
| 35 | for linenr, line in enumerate(stream): |
|---|
| 36 | line = line.rstrip('\r\n') |
|---|
| 37 | if len(line) > 80: |
|---|
| 38 | self.add_message('CPC01', linenr) |
|---|
| 39 | if line.endswith(' ') or line.endswith('\t'): |
|---|
| 40 | self.add_message('CPC02', linenr) |
|---|
| 41 | |
|---|
| 42 | |
|---|
| 43 | class RewriteDemandload(utils.ASTWalker): |
|---|
| 44 | |
|---|
| 45 | def __init__(self, linter): |
|---|
| 46 | utils.ASTWalker.__init__(self, self) |
|---|
| 47 | self.linter = linter |
|---|
| 48 | |
|---|
| 49 | def visit_callfunc(self, node): |
|---|
| 50 | """Hack fake imports into the tree after demandload calls.""" |
|---|
| 51 | # XXX inaccurate hack |
|---|
| 52 | if not node.node.as_string().endswith('demandload'): |
|---|
| 53 | return |
|---|
| 54 | # sanity check. |
|---|
| 55 | if len(node.args) != 2: |
|---|
| 56 | self.linter.add_message('WPC01', node=node) |
|---|
| 57 | return |
|---|
| 58 | if not isinstance(node.args[1], nodes.Const): |
|---|
| 59 | self.linter.add_message('WPC02', node=node) |
|---|
| 60 | return |
|---|
| 61 | modules = node.args[1].value |
|---|
| 62 | if not isinstance(modules, str): |
|---|
| 63 | self.linter.add_message('WPC02', node=node) |
|---|
| 64 | return |
|---|
| 65 | for mod in modules.split(): |
|---|
| 66 | col = mod.find(':') |
|---|
| 67 | if col == -1: |
|---|
| 68 | # Argument to Import probably works like this: |
|---|
| 69 | # "import foo, foon as spork" is |
|---|
| 70 | # nodes.Import([('foo', None), ('foon', 'spork')]) |
|---|
| 71 | # (not entirely sure though, have not found documentation. |
|---|
| 72 | # The asname/importedname might be the other way around fex). |
|---|
| 73 | newstuff = nodes.Import([(mod, None)]) |
|---|
| 74 | raw_building._attach_local_node(node.frame(), newstuff, mod) |
|---|
| 75 | else: |
|---|
| 76 | for name in mod[col+1:].split(','): |
|---|
| 77 | raw_building.attach_import_node(node.frame(), mod[:col], |
|---|
| 78 | name) |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | def register(linter): |
|---|
| 82 | """Required method to get our checker registered.""" |
|---|
| 83 | |
|---|
| 84 | rewriter = RewriteDemandload(linter) |
|---|
| 85 | # XXX HACK: monkeypatch the linter to transform the tree before |
|---|
| 86 | # the astng checkers get at it. |
|---|
| 87 | # |
|---|
| 88 | # Why do we do this? Because a whole bunch of places work with |
|---|
| 89 | # copies of astng data, not the data itself, by the time a normal |
|---|
| 90 | # checker runs it is too late to manipulate the data reliably. And |
|---|
| 91 | # pylint does not provide a hook that gets run at the right point |
|---|
| 92 | # to do this tree rewriting. So we monkeypatch in the hook. |
|---|
| 93 | # |
|---|
| 94 | # Ideally we would do something like |
|---|
| 95 | # |
|---|
| 96 | # linter.register_preprocessor(rewriter) |
|---|
| 97 | # |
|---|
| 98 | # and the linter would call walk(astng) on everything registered |
|---|
| 99 | # that way before the IASTNGCheckers run (not sure if it should be |
|---|
| 100 | # before or after the raw checkers run, probably does not matter). |
|---|
| 101 | # Perhaps give those preprocessors a priority attribute too. |
|---|
| 102 | # Definitely give them a msgs attribute. |
|---|
| 103 | |
|---|
| 104 | original_check_astng_module = linter.check_astng_module |
|---|
| 105 | def pkgcore_check_astng_module(astng, checkers): |
|---|
| 106 | rewriter.walk(astng) |
|---|
| 107 | original_check_astng_module(astng, checkers) |
|---|
| 108 | linter.check_astng_module = pkgcore_check_astng_module |
|---|
| 109 | |
|---|
| 110 | linter.register_checker(BasicLinesChecker(linter)) |
|---|