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

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

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

Powered by Google App Engine
This is Rietveld 408576698