root/masterdriverz/snakeoil/lintplugin/snakeoil_lint.py @ masterdriverz%2540gentoo.org-20070511172218-e5pdfd30yi8zykva

Revision masterdriverz%2540gentoo.org-20070511172218-e5pdfd30yi8zykva, 7.3 kB (checked in by Charlie Shepherd <masterdriverz@…>, 20 months ago)

Make pylint recurse properly this time (marienz ftw) and fix a custom check

Line 
1
2"""Pylint plugin checking for trailing whitespace."""
3
4
5import sys
6import __builtin__
7
8from pylint import interfaces, checkers
9from logilab.astng import (nodes, raw_building, utils,
10    Name, Getattr, CallFunc, And, Or, Node)
11
12from snakeoil.lists import iflatten_func
13
14builtins = tuple(x for x in dir(__builtin__) if x[0].islower())
15ignore_shadowing = ('all', 'any')
16
17class SnakeoilChecker(checkers.BaseChecker):
18
19    __implements__ = (interfaces.IRawChecker, interfaces.IASTNGChecker)
20
21    name = 'snakeoil'
22
23    # XXX move some of those over to RewriteDemandload somehow
24    # (current monkey patch running the rewriter does not support that)
25
26    msgs = {
27        'CPC01': ('line too long: length %d',
28                  'More complete version of the standard line too long check.'),
29        'CPC02': ('trailing whitespace', 'trailing whitespace sucks.'),
30        'WPC01': ('demandload with arglen < 2 ignored',
31                  'A call which is probably a demandload has too little'
32                  'arguments.'),
33        'WPC02': ('demandload with non-string-constant arg ignored',
34                  'A call which is probably a demandload has a second arg '
35                  'that is not a string constant. Fix the code to cooperate '
36                  'with the dumb checker.'),
37        'WPC03': ('old-style demandload call',
38                  'A call which uses the old way of callling demandload,'
39                  'with spaces.'),
40        'WPC04': ('non new-style class',
41                  'All classes should be new-style classes.'),
42        'WPC05': ('raise statement with two args',
43                  'A raise statement which has a message raised with the '
44                  'exception instead of as an argument to the exception '
45                  'class.'),
46        'WPC06': ('raise of Exception base class',
47                  'A raise statement in which Exception is raised- make a '
48                  'subclass of it and raise that instead.'),
49        'WPC07': ('Shadowing builtin %s',
50                  'Assignment to a builtin name'),
51        'WPC08': ('Iterating over dict.keys()',
52                  'Iterating over dict.keys()- use `for x in dict` or '
53                  '`for x in d.iteritems()` if you need the vals too'),
54        'WPC09': ('Usage of bool(len(seq))',
55                  'Testing for emptiness with len(seq) instead of bool(seq)'),
56        }
57
58    def process_module(self, stream):
59        for linenr, line in enumerate(stream):
60            line = line.rstrip('\r\n')
61            if len(line) > 80:
62                self.add_message('CPC01', linenr, args=len(line))
63            if line.endswith(' ') or line.endswith('\t'):
64                self.add_message('CPC02', linenr)
65
66    def visit_class(self, node):
67        if not node.bases:
68            self.add_message('WPC04', node=node)
69
70    def visit_raise(self, node):
71        if node.expr2 is not None:
72            self.add_message('WPC05', node=node)
73        expr = node.expr1
74        if expr is not None and isinstance(expr, Name) and \
75            expr.name == "Exception":
76            self.add_message('WPC06', node=node)
77
78    def visit_assname(self, node):
79        if node.name in builtins and node.name not in ignore_shadowing:
80            self.add_message('WPC07', args=node.name, node=node)
81
82    def visit_for(self, node):
83        expr = node.getChildNodes()[1]
84        if isinstance(expr, CallFunc):
85            expr = expr.getChildNodes()[0]
86            if isinstance(expr, Getattr) and expr.attrname == 'keys':
87                self.add_message('WPC08', node=node)
88
89    def visit_if(self, node):
90        f = lambda node: \
91                isinstance(node, basestring) or \
92                node is None or \
93                (isinstance(node, Node) and isinstance(node.parent, (Or,
94                                                                     And)))
95
96        for expr in iflatten_func(node.getChildNodes()[0], f):
97            if isinstance(expr, CallFunc):
98                expr = expr.getChildNodes()[0]
99                if isinstance(expr, Name) and expr.name == 'len':
100                    self.add_message('WPC09', node=node)
101
102
103class RewriteDemandload(utils.ASTWalker):
104
105    def __init__(self, linter):
106        utils.ASTWalker.__init__(self, self)
107        self.linter = linter
108
109    def visit_callfunc(self, node):
110        """Hack fake imports into the tree after demandload calls."""
111        # XXX inaccurate hack
112        if not node.node.as_string().endswith('demandload'):
113            return
114        # sanity check.
115        if len(node.args) < 2:
116            self.linter.add_message('WPC01', node=node)
117            return
118        if not isinstance(node.args[1], nodes.Const):
119            self.linter.add_message('WPC02', node=node)
120            return
121        if node.args[1].value.find(" ") != -1:
122            self.linter.add_message('WPC03', node=node)
123            return
124        for mod in (module.value for module in node.args[1:]):
125            if not isinstance(mod, str):
126                self.linter.add_message('WPC02', node=node)
127                continue
128            col = mod.find(':')
129            if col == -1:
130                # Argument to Import probably works like this:
131                # "import foo, foon as spork" is
132                # nodes.Import([('foo', None), ('foon', 'spork')])
133                # (not entirely sure though, have not found documentation.
134                # The asname/importedname might be the other way around fex).
135                newstuff = nodes.Import([(mod, None)])
136                newstuff.fromlineno = node.fromlineno
137                raw_building._attach_local_node(node.frame(), newstuff, mod)
138            else:
139                for name in mod[col+1:].split(','):
140                    if sys.version_info < (2, 5):
141                        newstuff = nodes.From(mod[:col], ((name, None),))
142                    else:
143                        newstuff = nodes.From(mod[:col], ((name, None),), 0)
144                    newstuff.fromlineno = 1
145                    raw_building._attach_local_node(node.frame(), newstuff,
146                                                    name)
147
148
149def register(linter):
150    """Required method to get our checker registered."""
151
152    rewriter = RewriteDemandload(linter)
153    # XXX HACK: monkeypatch the linter to transform the tree before
154    # the astng checkers get at it.
155    #
156    # Why do we do this? Because a whole bunch of places work with
157    # copies of astng data, not the data itself, by the time a normal
158    # checker runs it is too late to manipulate the data reliably. And
159    # pylint does not provide a hook that gets run at the right point
160    # to do this tree rewriting. So we monkeypatch in the hook.
161    #
162    # Ideally we would do something like
163    #
164    # linter.register_preprocessor(rewriter)
165    #
166    # and the linter would call walk(astng) on everything registered
167    # that way before the IASTNGCheckers run (not sure if it should be
168    # before or after the raw checkers run, probably does not matter).
169    # Perhaps give those preprocessors a priority attribute too.
170    # Definitely give them a msgs attribute.
171
172    original_check_astng_module = linter.check_astng_module
173    def snakeoil_check_astng_module(astng, checkers):
174        rewriter.walk(astng)
175        return original_check_astng_module(astng, checkers)
176    linter.check_astng_module = snakeoil_check_astng_module
177
178    linter.register_checker(SnakeoilChecker(linter))
179   
Note: See TracBrowser for help on using the browser.