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

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

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