Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(153)

Side by Side Diff: third_party/pylint/lint.py

Issue 10447014: Add pylint to depot_tools. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools
Patch Set: Fix unittests. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « third_party/pylint/interfaces.py ('k') | third_party/pylint/pyreverse/__init__.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com).
2 # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE).
3 # http://www.logilab.fr/ -- mailto:contact@logilab.fr
4 #
5 # This program is free software; you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 2 of the License, or (at your option) any later
8 # version.
9 #
10 # This program is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 """ %prog [options] module_or_package
18
19 Check that a module satisfy a coding standard (and more !).
20
21 %prog --help
22
23 Display this help message and exit.
24
25 %prog --help-msg <msg-id>[,<msg-id>]
26
27 Display help messages about given message identifiers and exit.
28 """
29
30 # import this first to avoid builtin namespace pollution
31 from pylint.checkers import utils
32
33 import sys
34 import os
35 import re
36 import tokenize
37 from warnings import warn
38
39 from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn
40 from logilab.common.optik_ext import check_csv
41 from logilab.common.modutils import load_module_from_name
42 from logilab.common.interface import implements
43 from logilab.common.textutils import splitstrip
44 from logilab.common.ureports import Table, Text, Section
45 from logilab.common.__pkginfo__ import version as common_version
46
47 from logilab.astng import MANAGER, nodes, ASTNGBuildingException
48 from logilab.astng.__pkginfo__ import version as astng_version
49
50 from pylint.utils import PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn,\
51 ReportsHandlerMixIn, MSG_TYPES, expand_modules
52 from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker
53 from pylint.checkers import BaseRawChecker, EmptyReport, \
54 table_lines_from_stats
55 from pylint.reporters.text import TextReporter, ParseableTextReporter, \
56 VSTextReporter, ColorizedTextReporter
57 from pylint.reporters.html import HTMLReporter
58 from pylint import config
59
60 from pylint.__pkginfo__ import version
61
62
63 OPTION_RGX = re.compile('\s*#*\s*pylint:(.*)')
64 REPORTER_OPT_MAP = {'text': TextReporter,
65 'parseable': ParseableTextReporter,
66 'msvs': VSTextReporter,
67 'colorized': ColorizedTextReporter,
68 'html': HTMLReporter,}
69
70
71 # Python Linter class #########################################################
72
73 MSGS = {
74 'F0001': ('%s',
75 'Used when an error occurred preventing the analysis of a \
76 module (unable to find it for instance).'),
77 'F0002': ('%s: %s',
78 'Used when an unexpected error occurred while building the ASTNG \
79 representation. This is usually accompanied by a traceback. \
80 Please report such errors !'),
81 'F0003': ('ignored builtin module %s',
82 'Used to indicate that the user asked to analyze a builtin module\
83 which has been skipped.'),
84 'F0004': ('unexpected inferred value %s',
85 'Used to indicate that some value of an unexpected type has been \
86 inferred.'),
87 'F0010': ('error while code parsing: %s',
88 'Used when an exception occured while building the ASTNG \
89 representation which could be handled by astng.'),
90
91
92 'I0001': ('Unable to run raw checkers on built-in module %s',
93 'Used to inform that a built-in module has not been checked \
94 using the raw checkers.'),
95
96 'I0010': ('Unable to consider inline option %r',
97 'Used when an inline option is either badly formatted or can\'t \
98 be used inside modules.'),
99
100 'I0011': ('Locally disabling %s',
101 'Used when an inline option disables a message or a messages \
102 category.'),
103 'I0012': ('Locally enabling %s',
104 'Used when an inline option enables a message or a messages \
105 category.'),
106 'I0013': ('Ignoring entire file',
107 'Used to inform that the file will not be checked'),
108
109
110 'E0001': ('%s',
111 'Used when a syntax error is raised for a module.'),
112
113 'E0011': ('Unrecognized file option %r',
114 'Used when an unknown inline option is encountered.'),
115 'E0012': ('Bad option value %r',
116 'Used when a bad value for an inline option is encountered.'),
117 }
118
119
120 class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
121 BaseRawChecker):
122 """lint Python modules using external checkers.
123
124 This is the main checker controlling the other ones and the reports
125 generation. It is itself both a raw checker and an astng checker in order
126 to:
127 * handle message activation / deactivation at the module level
128 * handle some basic but necessary stats'data (number of classes, methods...)
129
130 IDE plugins developpers: you may have to call
131 `logilab.astng.builder.MANAGER.astng_cache.clear()` accross run if you want
132 to ensure the latest code version is actually checked.
133 """
134
135 __implements__ = (ILinter, IRawChecker)
136
137 name = 'master'
138 priority = 0
139 level = 0
140 msgs = MSGS
141 may_be_disabled = False
142
143 @staticmethod
144 def make_options():
145 return (('ignore',
146 {'type' : 'csv', 'metavar' : '<file>[,<file>...]',
147 'dest' : 'black_list', 'default' : ('CVS',),
148 'help' : 'Add files or directories to the blacklist. \
149 They should be base names, not paths.'}),
150 ('persistent',
151 {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>',
152 'level': 1,
153 'help' : 'Pickle collected data for later comparisons.'}),
154
155 ('load-plugins',
156 {'type' : 'csv', 'metavar' : '<modules>', 'default' : (),
157 'level': 1,
158 'help' : 'List of plugins (as comma separated values of \
159 python modules names) to load, usually to register additional checkers.'}),
160
161 ('output-format',
162 {'default': 'text', 'type': 'choice', 'metavar' : '<format>',
163 'choices': REPORTER_OPT_MAP.keys(),
164 'short': 'f',
165 'group': 'Reports',
166 'help' : 'Set the output format. Available formats are text,\
167 parseable, colorized, msvs (visual studio) and html'}),
168
169 ('include-ids',
170 {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
171 'short': 'i',
172 'group': 'Reports',
173 'help' : 'Include message\'s id in output'}),
174
175 ('files-output',
176 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
177 'group': 'Reports', 'level': 1,
178 'help' : 'Put messages in a separate file for each module / \
179 package specified on the command line instead of printing them on stdout. \
180 Reports (if any) will be written in a file name "pylint_global.[txt|html]".'}),
181
182 ('reports',
183 {'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>',
184 'short': 'r',
185 'group': 'Reports',
186 'help' : 'Tells whether to display a full report or only the\
187 messages'}),
188
189 ('evaluation',
190 {'type' : 'string', 'metavar' : '<python_expression>',
191 'group': 'Reports', 'level': 1,
192 'default': '10.0 - ((float(5 * error + warning + refactor + \
193 convention) / statement) * 10)',
194 'help' : 'Python expression which should return a note less \
195 than 10 (10 is the highest note). You have access to the variables errors \
196 warning, statement which respectively contain the number of errors / warnings\
197 messages and the total number of statements analyzed. This is used by the \
198 global evaluation report (RP0004).'}),
199
200 ('comment',
201 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
202 'group': 'Reports', 'level': 1,
203 'help' : 'Add a comment according to your evaluation note. \
204 This is used by the global evaluation report (RP0004).'}),
205
206 ('enable',
207 {'type' : 'csv', 'metavar': '<msg ids>',
208 'short': 'e',
209 'group': 'Messages control',
210 'help' : 'Enable the message, report, category or checker with the '
211 'given id(s). You can either give multiple identifier '
212 'separated by comma (,) or put this option multiple time.'}),
213
214 ('disable',
215 {'type' : 'csv', 'metavar': '<msg ids>',
216 'short': 'd',
217 'group': 'Messages control',
218 'help' : 'Disable the message, report, category or checker '
219 'with the given id(s). You can either give multiple identifier '
220 ' separated by comma (,) or put this option multiple time '
221 '(only on the command line, not in the configuration file '
222 'where it should appear only once).'}),
223 )
224
225 option_groups = (
226 ('Messages control', 'Options controling analysis messages'),
227 ('Reports', 'Options related to output formating and reporting'),
228 )
229
230 def __init__(self, options=(), reporter=None, option_groups=(),
231 pylintrc=None):
232 # some stuff has to be done before ancestors initialization...
233 #
234 # checkers / reporter / astng manager
235 self.reporter = None
236 self._checkers = {}
237 self._ignore_file = False
238 # visit variables
239 self.base_name = None
240 self.base_file = None
241 self.current_name = None
242 self.current_file = None
243 self.stats = None
244 # init options
245 self.options = options + PyLinter.make_options()
246 self.option_groups = option_groups + PyLinter.option_groups
247 self._options_methods = {
248 'enable': self.enable,
249 'disable': self.disable}
250 self._bw_options_methods = {'disable-msg': self.disable,
251 'enable-msg': self.enable}
252 full_version = '%%prog %s, \nastng %s, common %s\nPython %s' % (
253 version, astng_version, common_version, sys.version)
254 OptionsManagerMixIn.__init__(self, usage=__doc__,
255 version=full_version,
256 config_file=pylintrc or config.PYLINTRC)
257 MessagesHandlerMixIn.__init__(self)
258 ReportsHandlerMixIn.__init__(self)
259 BaseRawChecker.__init__(self)
260 # provided reports
261 self.reports = (('RP0001', 'Messages by category',
262 report_total_messages_stats),
263 ('RP0002', '% errors / warnings by module',
264 report_messages_by_module_stats),
265 ('RP0003', 'Messages',
266 report_messages_stats),
267 ('RP0004', 'Global evaluation',
268 self.report_evaluation),
269 )
270 self.register_checker(self)
271 self._dynamic_plugins = []
272 self.load_provider_defaults()
273 self.set_reporter(reporter or TextReporter(sys.stdout))
274
275 def load_default_plugins(self):
276 from pylint import checkers
277 checkers.initialize(self)
278
279 def load_plugin_modules(self, modnames):
280 """take a list of module names which are pylint plugins and load
281 and register them
282 """
283 for modname in modnames:
284 if modname in self._dynamic_plugins:
285 continue
286 self._dynamic_plugins.append(modname)
287 module = load_module_from_name(modname)
288 module.register(self)
289
290 def set_reporter(self, reporter):
291 """set the reporter used to display messages and reports"""
292 self.reporter = reporter
293 reporter.linter = self
294
295 def set_option(self, optname, value, action=None, optdict=None):
296 """overridden from configuration.OptionsProviderMixin to handle some
297 special options
298 """
299 if optname in self._options_methods or optname in self._bw_options_metho ds:
300 if value:
301 try:
302 meth = self._options_methods[optname]
303 except KeyError:
304 meth = self._bw_options_methods[optname]
305 warn('%s is deprecated, replace it by %s' % (
306 optname, optname.split('-')[0]), DeprecationWarning)
307 value = check_csv(None, optname, value)
308 if isinstance(value, (list, tuple)):
309 for _id in value :
310 meth(_id)
311 else :
312 meth(value)
313 elif optname == 'output-format':
314 self.set_reporter(REPORTER_OPT_MAP[value.lower()]())
315 try:
316 BaseRawChecker.set_option(self, optname, value, action, optdict)
317 except UnsupportedAction:
318 print >> sys.stderr, 'option %s can\'t be read from config file' % \
319 optname
320
321 # checkers manipulation methods ############################################
322
323 def register_checker(self, checker):
324 """register a new checker
325
326 checker is an object implementing IRawChecker or / and IASTNGChecker
327 """
328 assert checker.priority <= 0, 'checker priority can\'t be >= 0'
329 self._checkers.setdefault(checker.name, []).append(checker)
330 for r_id, r_title, r_cb in checker.reports:
331 self.register_report(r_id, r_title, r_cb, checker)
332 self.register_options_provider(checker)
333 if hasattr(checker, 'msgs'):
334 self.register_messages(checker)
335 checker.load_defaults()
336
337 def disable_noerror_messages(self):
338 for msgcat, msgids in self._msgs_by_category.iteritems():
339 if msgcat == 'E':
340 for msgid in msgids:
341 self.enable(msgid)
342 else:
343 for msgid in msgids:
344 self.disable(msgid)
345
346 def disable_reporters(self):
347 """disable all reporters"""
348 for reporters in self._reports.values():
349 for report_id, _title, _cb in reporters:
350 self.disable_report(report_id)
351
352 def error_mode(self):
353 """error mode: enable only errors; no reports, no persistent"""
354 self.disable_noerror_messages()
355 self.disable('miscellaneous')
356 self.set_option('reports', False)
357 self.set_option('persistent', False)
358
359 # block level option handling #############################################
360 #
361 # see func_block_disable_msg.py test case for expected behaviour
362
363 def process_tokens(self, tokens):
364 """process tokens from the current module to search for module/block
365 level options
366 """
367 comment = tokenize.COMMENT
368 newline = tokenize.NEWLINE
369 for (tok_type, _, start, _, line) in tokens:
370 if tok_type not in (comment, newline):
371 continue
372 match = OPTION_RGX.search(line)
373 if match is None:
374 continue
375 if match.group(1).strip() == "disable-all":
376 self.add_message('I0013', line=start[0])
377 self._ignore_file = True
378 return
379 try:
380 opt, value = match.group(1).split('=', 1)
381 except ValueError:
382 self.add_message('I0010', args=match.group(1).strip(),
383 line=start[0])
384 continue
385 opt = opt.strip()
386 if opt in self._options_methods or opt in self._bw_options_methods:
387 try:
388 meth = self._options_methods[opt]
389 except KeyError:
390 meth = self._bw_options_methods[opt]
391 warn('%s is deprecated, replace it by %s (%s, line %s)' % (
392 opt, opt.split('-')[0], self.current_file, line),
393 DeprecationWarning)
394 for msgid in splitstrip(value):
395 try:
396 meth(msgid, 'module', start[0])
397 except UnknownMessage:
398 self.add_message('E0012', args=msgid, line=start[0])
399 else:
400 self.add_message('E0011', args=opt, line=start[0])
401
402 def collect_block_lines(self, node, msg_state):
403 """walk ast to collect block level options line numbers"""
404 # recurse on children (depth first)
405 for child in node.get_children():
406 self.collect_block_lines(child, msg_state)
407 first = node.fromlineno
408 last = node.tolineno
409 # first child line number used to distinguish between disable
410 # which are the first child of scoped node with those defined later.
411 # For instance in the code below:
412 #
413 # 1. def meth8(self):
414 # 2. """test late disabling"""
415 # 3. # pylint: disable=E1102
416 # 4. print self.blip
417 # 5. # pylint: disable=E1101
418 # 6. print self.bla
419 #
420 # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6
421 #
422 # this is necessary to disable locally messages applying to class /
423 # function using their fromlineno
424 if isinstance(node, (nodes.Module, nodes.Class, nodes.Function)) and nod e.body:
425 firstchildlineno = node.body[0].fromlineno
426 else:
427 firstchildlineno = last
428 for msgid, lines in msg_state.iteritems():
429 for lineno, state in lines.items():
430 if first <= lineno <= last:
431 if lineno > firstchildlineno:
432 state = True
433 # set state for all lines for this block
434 first, last = node.block_range(lineno)
435 for line in xrange(first, last+1):
436 # do not override existing entries
437 if not line in self._module_msgs_state.get(msgid, ()):
438 if line in lines: # state change in the same block
439 state = lines[line]
440 try:
441 self._module_msgs_state[msgid][line] = state
442 except KeyError:
443 self._module_msgs_state[msgid] = {line: state}
444 del lines[lineno]
445
446
447 # code checking methods ###################################################
448
449 def get_checkers(self):
450 """return all available checkers as a list"""
451 return [self] + [c for checkers in self._checkers.values()
452 for c in checkers if c is not self]
453
454 def prepare_checkers(self):
455 """return checkers needed for activated messages and reports"""
456 if not self.config.reports:
457 self.disable_reporters()
458 # get needed checkers
459 neededcheckers = [self]
460 for checker in self.get_checkers()[1:]:
461 messages = set(msg for msg in checker.msgs
462 if self.is_message_enabled(msg))
463 if (messages or
464 any(self.report_is_enabled(r[0]) for r in checker.reports)):
465 neededcheckers.append(checker)
466 checker.active_msgs = messages
467 return neededcheckers
468
469 def check(self, files_or_modules):
470 """main checking entry: check a list of files or modules from their
471 name.
472 """
473 self.reporter.include_ids = self.config.include_ids
474 if not isinstance(files_or_modules, (list, tuple)):
475 files_or_modules = (files_or_modules,)
476 walker = PyLintASTWalker(self)
477 checkers = self.prepare_checkers()
478 rawcheckers = [c for c in checkers if implements(c, IRawChecker)
479 and c is not self]
480 # notify global begin
481 for checker in checkers:
482 checker.open()
483 if implements(checker, IASTNGChecker):
484 walker.add_checker(checker)
485 # build ast and check modules or packages
486 for descr in self.expand_files(files_or_modules):
487 modname, filepath = descr['name'], descr['path']
488 self.set_current_module(modname, filepath)
489 # get the module representation
490 astng = self.get_astng(filepath, modname)
491 if astng is None:
492 continue
493 self.base_name = descr['basename']
494 self.base_file = descr['basepath']
495 if self.config.files_output:
496 reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension)
497 self.reporter.set_output(open(reportfile, 'w'))
498 self._ignore_file = False
499 # fix the current file (if the source file was not available or
500 # if it's actually a c extension)
501 self.current_file = astng.file
502 self.check_astng_module(astng, walker, rawcheckers)
503 # notify global end
504 self.set_current_module('')
505 self.stats['statement'] = walker.nbstatements
506 checkers.reverse()
507 for checker in checkers:
508 checker.close()
509
510 def expand_files(self, modules):
511 """get modules and errors from a list of modules and handle errors
512 """
513 result, errors = expand_modules(modules, self.config.black_list)
514 for error in errors:
515 message = modname = error["mod"]
516 key = error["key"]
517 self.set_current_module(modname)
518 if key == "F0001":
519 message = str(error["ex"]).replace(os.getcwd() + os.sep, '')
520 self.add_message(key, args=message)
521 return result
522
523 def set_current_module(self, modname, filepath=None):
524 """set the name of the currently analyzed module and
525 init statistics for it
526 """
527 if not modname and filepath is None:
528 return
529 self.current_name = modname
530 self.current_file = filepath or modname
531 self.stats['by_module'][modname] = {}
532 self.stats['by_module'][modname]['statement'] = 0
533 for msg_cat in MSG_TYPES.values():
534 self.stats['by_module'][modname][msg_cat] = 0
535 # XXX hack, to be correct we need to keep module_msgs_state
536 # for every analyzed module (the problem stands with localized
537 # messages which are only detected in the .close step)
538 if modname:
539 self._module_msgs_state = {}
540 self._module_msg_cats_state = {}
541
542 def get_astng(self, filepath, modname):
543 """return a astng representation for a module"""
544 try:
545 return MANAGER.astng_from_file(filepath, modname, source=True)
546 except SyntaxError, ex:
547 self.add_message('E0001', line=ex.lineno, args=ex.msg)
548 except ASTNGBuildingException, ex:
549 self.add_message('F0010', args=ex)
550 except Exception, ex:
551 import traceback
552 traceback.print_exc()
553 self.add_message('F0002', args=(ex.__class__, ex))
554
555 def check_astng_module(self, astng, walker, rawcheckers):
556 """check a module from its astng representation, real work"""
557 # call raw checkers if possible
558 if not astng.pure_python:
559 self.add_message('I0001', args=astng.name)
560 else:
561 #assert astng.file.endswith('.py')
562 # invoke IRawChecker interface on self to fetch module/block
563 # level options
564 self.process_module(astng)
565 if self._ignore_file:
566 return False
567 # walk ast to collect line numbers
568 orig_state = self._module_msgs_state.copy()
569 self._module_msgs_state = {}
570 self.collect_block_lines(astng, orig_state)
571 for checker in rawcheckers:
572 checker.process_module(astng)
573 # generate events to astng checkers
574 walker.walk(astng)
575 return True
576
577 # IASTNGChecker interface #################################################
578
579 def open(self):
580 """initialize counters"""
581 self.stats = { 'by_module' : {},
582 'by_msg' : {},
583 }
584 for msg_cat in MSG_TYPES.values():
585 self.stats[msg_cat] = 0
586
587 def close(self):
588 """close the whole package /module, it's time to make reports !
589
590 if persistent run, pickle results for later comparison
591 """
592 if self.base_name is not None:
593 # load old results if any
594 old_stats = config.load_results(self.base_name)
595 if self.config.reports:
596 self.make_reports(self.stats, old_stats)
597 elif self.config.output_format == 'html':
598 self.reporter.display_results(Section())
599 # save results if persistent run
600 if self.config.persistent:
601 config.save_results(self.stats, self.base_name)
602
603 # specific reports ########################################################
604
605 def report_evaluation(self, sect, stats, old_stats):
606 """make the global evaluation report"""
607 # check with at least check 1 statements (usually 0 when there is a
608 # syntax error preventing pylint from further processing)
609 if stats['statement'] == 0:
610 raise EmptyReport()
611 # get a global note for the code
612 evaluation = self.config.evaluation
613 try:
614 note = eval(evaluation, {}, self.stats)
615 except Exception, ex:
616 msg = 'An exception occurred while rating: %s' % ex
617 else:
618 stats['global_note'] = note
619 msg = 'Your code has been rated at %.2f/10' % note
620 if 'global_note' in old_stats:
621 msg += ' (previous run: %.2f/10)' % old_stats['global_note']
622 if self.config.comment:
623 msg = '%s\n%s' % (msg, config.get_note_message(note))
624 sect.append(Text(msg))
625
626 # some reporting functions ####################################################
627
628 def report_total_messages_stats(sect, stats, old_stats):
629 """make total errors / warnings report"""
630 lines = ['type', 'number', 'previous', 'difference']
631 lines += table_lines_from_stats(stats, old_stats,
632 ('convention', 'refactor',
633 'warning', 'error'))
634 sect.append(Table(children=lines, cols=4, rheaders=1))
635
636 def report_messages_stats(sect, stats, _):
637 """make messages type report"""
638 if not stats['by_msg']:
639 # don't print this report when we didn't detected any errors
640 raise EmptyReport()
641 in_order = sorted([(value, msg_id)
642 for msg_id, value in stats['by_msg'].items()
643 if not msg_id.startswith('I')])
644 in_order.reverse()
645 lines = ('message id', 'occurrences')
646 for value, msg_id in in_order:
647 lines += (msg_id, str(value))
648 sect.append(Table(children=lines, cols=2, rheaders=1))
649
650 def report_messages_by_module_stats(sect, stats, _):
651 """make errors / warnings by modules report"""
652 if len(stats['by_module']) == 1:
653 # don't print this report when we are analysing a single module
654 raise EmptyReport()
655 by_mod = {}
656 for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'):
657 total = stats[m_type]
658 for module in stats['by_module'].keys():
659 mod_total = stats['by_module'][module][m_type]
660 if total == 0:
661 percent = 0
662 else:
663 percent = float((mod_total)*100) / total
664 by_mod.setdefault(module, {})[m_type] = percent
665 sorted_result = []
666 for module, mod_info in by_mod.items():
667 sorted_result.append((mod_info['error'],
668 mod_info['warning'],
669 mod_info['refactor'],
670 mod_info['convention'],
671 module))
672 sorted_result.sort()
673 sorted_result.reverse()
674 lines = ['module', 'error', 'warning', 'refactor', 'convention']
675 for line in sorted_result:
676 if line[0] == 0 and line[1] == 0:
677 break
678 lines.append(line[-1])
679 for val in line[:-1]:
680 lines.append('%.2f' % val)
681 if len(lines) == 5:
682 raise EmptyReport()
683 sect.append(Table(children=lines, cols=5, rheaders=1))
684
685
686 # utilities ###################################################################
687
688 # this may help to import modules using gettext
689
690 try:
691 __builtins__._ = str
692 except AttributeError:
693 __builtins__['_'] = str
694
695
696 class ArgumentPreprocessingError(Exception):
697 """Raised if an error occurs during argument preprocessing."""
698
699
700 def preprocess_options(args, search_for):
701 """look for some options (keys of <search_for>) which have to be processed
702 before others
703
704 values of <search_for> are callback functions to call when the option is
705 found
706 """
707 i = 0
708 while i < len(args):
709 arg = args[i]
710 if arg.startswith('--'):
711 try:
712 option, val = arg[2:].split('=', 1)
713 except ValueError:
714 option, val = arg[2:], None
715 try:
716 cb, takearg = search_for[option]
717 del args[i]
718 if takearg and val is None:
719 if i >= len(args) or args[i].startswith('-'):
720 raise ArgumentPreprocessingError(arg)
721 val = args[i]
722 del args[i]
723 cb(option, val)
724 except KeyError:
725 i += 1
726 else:
727 i += 1
728
729 class Run:
730 """helper class to use as main for pylint :
731
732 run(*sys.argv[1:])
733 """
734 LinterClass = PyLinter
735 option_groups = (
736 ('Commands', 'Options which are actually commands. Options in this \
737 group are mutually exclusive.'),
738 )
739
740 def __init__(self, args, reporter=None, exit=True):
741 self._rcfile = None
742 self._plugins = []
743 try:
744 preprocess_options(args, {
745 # option: (callback, takearg)
746 'rcfile': (self.cb_set_rcfile, True),
747 'load-plugins': (self.cb_add_plugins, True),
748 })
749 except ArgumentPreprocessingError, e:
750 print >> sys.stderr, 'Argument %s expects a value.' % (e.args[0],)
751 sys.exit(32)
752
753 self.linter = linter = self.LinterClass((
754 ('rcfile',
755 {'action' : 'callback', 'callback' : lambda *args: 1,
756 'type': 'string', 'metavar': '<file>',
757 'help' : 'Specify a configuration file.'}),
758
759 ('init-hook',
760 {'action' : 'callback', 'type' : 'string', 'metavar': '<code>',
761 'callback' : cb_init_hook, 'level': 1,
762 'help' : 'Python code to execute, usually for sys.path \
763 manipulation such as pygtk.require().'}),
764
765 ('help-msg',
766 {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
767 'callback' : self.cb_help_message,
768 'group': 'Commands',
769 'help' : '''Display a help message for the given message id and \
770 exit. The value may be a comma separated list of message ids.'''}),
771
772 ('list-msgs',
773 {'action' : 'callback', 'metavar': '<msg-id>',
774 'callback' : self.cb_list_messages,
775 'group': 'Commands', 'level': 1,
776 'help' : "Generate pylint's messages."}),
777
778 ('full-documentation',
779 {'action' : 'callback', 'metavar': '<msg-id>',
780 'callback' : self.cb_full_documentation,
781 'group': 'Commands', 'level': 1,
782 'help' : "Generate pylint's full documentation."}),
783
784 ('generate-rcfile',
785 {'action' : 'callback', 'callback' : self.cb_generate_config,
786 'group': 'Commands',
787 'help' : '''Generate a sample configuration file according to \
788 the current configuration. You can put other options before this one to get \
789 them in the generated configuration.'''}),
790
791 ('generate-man',
792 {'action' : 'callback', 'callback' : self.cb_generate_manpage,
793 'group': 'Commands',
794 'help' : "Generate pylint's man page.",'hide': True}),
795
796 ('errors-only',
797 {'action' : 'callback', 'callback' : self.cb_error_mode,
798 'short': 'E',
799 'help' : '''In error mode, checkers without error messages are \
800 disabled and for others, only the ERROR messages are displayed, and no reports \
801 are done by default'''}),
802
803 ('profile',
804 {'type' : 'yn', 'metavar' : '<y_or_n>',
805 'default': False, 'hide': True,
806 'help' : 'Profiled execution.'}),
807
808 ), option_groups=self.option_groups,
809 reporter=reporter, pylintrc=self._rcfile)
810 # register standard checkers
811 linter.load_default_plugins()
812 # load command line plugins
813 linter.load_plugin_modules(self._plugins)
814 # add some help section
815 linter.add_help_section('Environment variables', config.ENV_HELP, level= 1)
816 linter.add_help_section('Output', '''
817 Using the default text output, the message format is :
818
819 MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE
820
821 There are 5 kind of message types :
822 * (C) convention, for programming standard violation
823 * (R) refactor, for bad code smell
824 * (W) warning, for python specific problems
825 * (E) error, for probable bugs in the code
826 * (F) fatal, if an error occurred which prevented pylint from doing further
827 processing.
828 ''', level=1)
829 linter.add_help_section('Output status code', '''
830 Pylint should leave with following status code:
831 * 0 if everything went fine
832 * 1 if a fatal message was issued
833 * 2 if an error message was issued
834 * 4 if a warning message was issued
835 * 8 if a refactor message was issued
836 * 16 if a convention message was issued
837 * 32 on usage error
838
839 status 1 to 16 will be bit-ORed so you can know which different categories has
840 been issued by analysing pylint output status code
841 ''', level=1)
842 # read configuration
843 linter.disable('W0704')
844 linter.read_config_file()
845 # is there some additional plugins in the file configuration, in
846 config_parser = linter.cfgfile_parser
847 if config_parser.has_option('MASTER', 'load-plugins'):
848 plugins = splitstrip(config_parser.get('MASTER', 'load-plugins'))
849 linter.load_plugin_modules(plugins)
850 # now we can load file config and command line, plugins (which can
851 # provide options) have been registered
852 linter.load_config_file()
853 if reporter:
854 # if a custom reporter is provided as argument, it may be overridden
855 # by file parameters, so re-set it here, but before command line
856 # parsing so it's still overrideable by command line option
857 linter.set_reporter(reporter)
858 try:
859 args = linter.load_command_line_configuration(args)
860 except SystemExit, exc:
861 if exc.code == 2: # bad options
862 exc.code = 32
863 raise
864 if not args:
865 print linter.help()
866 sys.exit(32)
867 # insert current working directory to the python path to have a correct
868 # behaviour
869 sys.path.insert(0, os.getcwd())
870 if self.linter.config.profile:
871 print >> sys.stderr, '** profiled run'
872 import cProfile, pstats
873 cProfile.runctx('linter.check(%r)' % args, globals(), locals(), 'sto nes.prof' )
874 data = pstats.Stats('stones.prof')
875 data.strip_dirs()
876 data.sort_stats('time', 'calls')
877 data.print_stats(30)
878 else:
879 linter.check(args)
880 sys.path.pop(0)
881 if exit:
882 sys.exit(self.linter.msg_status)
883
884 def cb_set_rcfile(self, name, value):
885 """callback for option preprocessing (i.e. before optik parsing)"""
886 self._rcfile = value
887
888 def cb_add_plugins(self, name, value):
889 """callback for option preprocessing (i.e. before optik parsing)"""
890 self._plugins.extend(splitstrip(value))
891
892 def cb_error_mode(self, *args, **kwargs):
893 """error mode:
894 * disable all but error messages
895 * disable the 'miscellaneous' checker which can be safely deactivated in
896 debug
897 * disable reports
898 * do not save execution information
899 """
900 self.linter.error_mode()
901
902 def cb_generate_config(self, *args, **kwargs):
903 """optik callback for sample config file generation"""
904 self.linter.generate_config(skipsections=('COMMANDS',))
905 sys.exit(0)
906
907 def cb_generate_manpage(self, *args, **kwargs):
908 """optik callback for sample config file generation"""
909 from pylint import __pkginfo__
910 self.linter.generate_manpage(__pkginfo__)
911 sys.exit(0)
912
913 def cb_help_message(self, option, optname, value, parser):
914 """optik callback for printing some help about a particular message"""
915 self.linter.help_message(splitstrip(value))
916 sys.exit(0)
917
918 def cb_full_documentation(self, option, optname, value, parser):
919 """optik callback for printing full documentation"""
920 self.linter.print_full_documentation()
921 sys.exit(0)
922
923 def cb_list_messages(self, option, optname, value, parser): # FIXME
924 """optik callback for printing available messages"""
925 self.linter.list_messages()
926 sys.exit(0)
927
928 def cb_init_hook(option, optname, value, parser):
929 """exec arbitrary code to set sys.path for instance"""
930 exec value
931
932
933 if __name__ == '__main__':
934 Run(sys.argv[1:])
OLDNEW
« no previous file with comments | « third_party/pylint/interfaces.py ('k') | third_party/pylint/pyreverse/__init__.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698