gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / pylint / lint.py @ 1227
History | View | Annotate | Download (56.8 KB)
1 |
# Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE).
|
---|---|
2 |
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
3 |
#
|
4 |
# 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 |
# Foundation; either version 2 of the License, or (at your option) any later
|
7 |
# version.
|
8 |
#
|
9 |
# 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 |
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
|
12 |
#
|
13 |
# 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 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
16 |
""" %prog [options] module_or_package
|
17 |
|
18 |
Check that a module satisfies a coding standard (and more !).
|
19 |
|
20 |
%prog --help
|
21 |
|
22 |
Display this help message and exit.
|
23 |
|
24 |
%prog --help-msg <msg-id>[,<msg-id>]
|
25 |
|
26 |
Display help messages about given message identifiers and exit.
|
27 |
"""
|
28 |
from __future__ import print_function |
29 |
|
30 |
import collections |
31 |
import contextlib |
32 |
import operator |
33 |
import os |
34 |
try:
|
35 |
import multiprocessing |
36 |
except ImportError: |
37 |
multiprocessing = None
|
38 |
import sys |
39 |
import tokenize |
40 |
import warnings |
41 |
|
42 |
import six |
43 |
|
44 |
import astroid |
45 |
from astroid.__pkginfo__ import version as astroid_version |
46 |
from astroid import modutils |
47 |
from pylint import checkers |
48 |
from pylint import interfaces |
49 |
from pylint import reporters |
50 |
from pylint import utils |
51 |
from pylint import config |
52 |
from pylint.__pkginfo__ import version |
53 |
from pylint.reporters.ureports import nodes as report_nodes |
54 |
|
55 |
|
56 |
MANAGER = astroid.MANAGER |
57 |
INCLUDE_IDS_HELP = ("Deprecated. It was used to include message\'s "
|
58 |
"id in output. Use --msg-template instead.")
|
59 |
SYMBOLS_HELP = ("Deprecated. It was used to include symbolic ids of "
|
60 |
"messages in output. Use --msg-template instead.")
|
61 |
|
62 |
def sys_path(): |
63 |
if sys.getClassLoader()!=None: |
64 |
path = getattr(sys.getClassLoader(),"path",None) |
65 |
if path != None: |
66 |
return path(sys.path)
|
67 |
path = [ folder for folder in sys.path if not folder.startswith("__") ] |
68 |
return path
|
69 |
|
70 |
def _get_new_args(message): |
71 |
location = ( |
72 |
message.abspath, |
73 |
message.path, |
74 |
message.module, |
75 |
message.obj, |
76 |
message.line, |
77 |
message.column, |
78 |
) |
79 |
return (
|
80 |
message.msg_id, |
81 |
message.symbol, |
82 |
location, |
83 |
message.msg, |
84 |
message.confidence, |
85 |
) |
86 |
|
87 |
def _get_python_path(filepath): |
88 |
dirname = os.path.realpath(os.path.expanduser(filepath)) |
89 |
if not os.path.isdir(dirname): |
90 |
dirname = os.path.dirname(dirname) |
91 |
while True: |
92 |
if not os.path.exists(os.path.join(dirname, "__init__.py")): |
93 |
return dirname
|
94 |
old_dirname = dirname |
95 |
dirname = os.path.dirname(dirname) |
96 |
if old_dirname == dirname:
|
97 |
return os.getcwd()
|
98 |
|
99 |
|
100 |
def _merge_stats(stats): |
101 |
merged = {} |
102 |
by_msg = collections.Counter() |
103 |
for stat in stats: |
104 |
message_stats = stat.pop('by_msg', {})
|
105 |
by_msg.update(message_stats) |
106 |
|
107 |
for key, item in six.iteritems(stat): |
108 |
if key not in merged: |
109 |
merged[key] = item |
110 |
else:
|
111 |
if isinstance(item, dict): |
112 |
merged[key].update(item) |
113 |
else:
|
114 |
merged[key] = merged[key] + item |
115 |
|
116 |
merged['by_msg'] = by_msg
|
117 |
return merged
|
118 |
|
119 |
|
120 |
@contextlib.contextmanager
|
121 |
def _patch_sysmodules(): |
122 |
# Context manager that permits running pylint, on Windows, with -m switch
|
123 |
# and with --jobs, as in 'python -2 -m pylint .. --jobs'.
|
124 |
# For more details why this is needed,
|
125 |
# see Python issue http://bugs.python.org/issue10845.
|
126 |
|
127 |
mock_main = __name__ != '__main__' # -m switch |
128 |
if mock_main:
|
129 |
sys.modules['__main__'] = sys.modules[__name__]
|
130 |
|
131 |
try:
|
132 |
yield
|
133 |
finally:
|
134 |
if mock_main:
|
135 |
sys.modules.pop('__main__')
|
136 |
|
137 |
|
138 |
# Python Linter class #########################################################
|
139 |
|
140 |
MSGS = { |
141 |
'F0001': ('%s', |
142 |
'fatal',
|
143 |
'Used when an error occurred preventing the analysis of a \
|
144 |
module (unable to find it for instance).'),
|
145 |
'F0002': ('%s: %s', |
146 |
'astroid-error',
|
147 |
'Used when an unexpected error occurred while building the '
|
148 |
'Astroid representation. This is usually accompanied by a '
|
149 |
'traceback. Please report such errors !'),
|
150 |
'F0010': ('error while code parsing: %s', |
151 |
'parse-error',
|
152 |
'Used when an exception occured while building the Astroid '
|
153 |
'representation which could be handled by astroid.'),
|
154 |
|
155 |
'I0001': ('Unable to run raw checkers on built-in module %s', |
156 |
'raw-checker-failed',
|
157 |
'Used to inform that a built-in module has not been checked '
|
158 |
'using the raw checkers.'),
|
159 |
|
160 |
'I0010': ('Unable to consider inline option %r', |
161 |
'bad-inline-option',
|
162 |
'Used when an inline option is either badly formatted or can\'t '
|
163 |
'be used inside modules.'),
|
164 |
|
165 |
'I0011': ('Locally disabling %s (%s)', |
166 |
'locally-disabled',
|
167 |
'Used when an inline option disables a message or a messages '
|
168 |
'category.'),
|
169 |
'I0012': ('Locally enabling %s (%s)', |
170 |
'locally-enabled',
|
171 |
'Used when an inline option enables a message or a messages '
|
172 |
'category.'),
|
173 |
'I0013': ('Ignoring entire file', |
174 |
'file-ignored',
|
175 |
'Used to inform that the file will not be checked'),
|
176 |
'I0020': ('Suppressed %s (from line %d)', |
177 |
'suppressed-message',
|
178 |
'A message was triggered on a line, but suppressed explicitly '
|
179 |
'by a disable= comment in the file. This message is not '
|
180 |
'generated for messages that are ignored due to configuration '
|
181 |
'settings.'),
|
182 |
'I0021': ('Useless suppression of %s', |
183 |
'useless-suppression',
|
184 |
'Reported when a message is explicitly disabled for a line or '
|
185 |
'a block of code, but never triggered.'),
|
186 |
'I0022': ('Pragma "%s" is deprecated, use "%s" instead', |
187 |
'deprecated-pragma',
|
188 |
'Some inline pylint options have been renamed or reworked, '
|
189 |
'only the most recent form should be used. '
|
190 |
'NOTE:skip-all is only available with pylint >= 0.26',
|
191 |
{'old_names': [('I0014', 'deprecated-disable-all')]}), |
192 |
|
193 |
'E0001': ('%s', |
194 |
'syntax-error',
|
195 |
'Used when a syntax error is raised for a module.'),
|
196 |
|
197 |
'E0011': ('Unrecognized file option %r', |
198 |
'unrecognized-inline-option',
|
199 |
'Used when an unknown inline option is encountered.'),
|
200 |
'E0012': ('Bad option value %r', |
201 |
'bad-option-value',
|
202 |
'Used when a bad value for an inline option is encountered.'),
|
203 |
} |
204 |
|
205 |
|
206 |
if multiprocessing is not None: |
207 |
class ChildLinter(multiprocessing.Process): |
208 |
def run(self): |
209 |
# pylint: disable=no-member, unbalanced-tuple-unpacking
|
210 |
tasks_queue, results_queue, self._config = self._args |
211 |
|
212 |
self._config["jobs"] = 1 # Child does not parallelize any further. |
213 |
self._python3_porting_mode = self._config.pop( |
214 |
'python3_porting_mode', None) |
215 |
self._plugins = self._config.pop('plugins', None) |
216 |
|
217 |
# Run linter for received files/modules.
|
218 |
for file_or_module in iter(tasks_queue.get, 'STOP'): |
219 |
result = self._run_linter(file_or_module[0]) |
220 |
try:
|
221 |
results_queue.put(result) |
222 |
except Exception as ex: |
223 |
print("internal error with sending report for module %s" %
|
224 |
file_or_module, file=sys.stderr) |
225 |
print(ex, file=sys.stderr) |
226 |
results_queue.put({}) |
227 |
|
228 |
def _run_linter(self, file_or_module): |
229 |
linter = PyLinter() |
230 |
|
231 |
# Register standard checkers.
|
232 |
linter.load_default_plugins() |
233 |
# Load command line plugins.
|
234 |
if self._plugins: |
235 |
linter.load_plugin_modules(self._plugins)
|
236 |
|
237 |
linter.load_configuration(**self._config)
|
238 |
linter.set_reporter(reporters.CollectingReporter()) |
239 |
|
240 |
# Enable the Python 3 checker mode. This option is
|
241 |
# passed down from the parent linter up to here, since
|
242 |
# the Python 3 porting flag belongs to the Run class,
|
243 |
# instead of the Linter class.
|
244 |
if self._python3_porting_mode: |
245 |
linter.python3_porting_mode() |
246 |
|
247 |
# Run the checks.
|
248 |
linter.check(file_or_module) |
249 |
|
250 |
msgs = [_get_new_args(m) for m in linter.reporter.messages] |
251 |
return (file_or_module, linter.file_state.base_name, linter.current_name,
|
252 |
msgs, linter.stats, linter.msg_status) |
253 |
|
254 |
|
255 |
class PyLinter(config.OptionsManagerMixIn, |
256 |
utils.MessagesHandlerMixIn, |
257 |
utils.ReportsHandlerMixIn, |
258 |
checkers.BaseTokenChecker): |
259 |
"""lint Python modules using external checkers.
|
260 |
|
261 |
This is the main checker controlling the other ones and the reports
|
262 |
generation. It is itself both a raw checker and an astroid checker in order
|
263 |
to:
|
264 |
* handle message activation / deactivation at the module level
|
265 |
* handle some basic but necessary stats'data (number of classes, methods...)
|
266 |
|
267 |
IDE plugins developpers: you may have to call
|
268 |
`astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want
|
269 |
to ensure the latest code version is actually checked.
|
270 |
"""
|
271 |
|
272 |
__implements__ = (interfaces.ITokenChecker, ) |
273 |
|
274 |
name = 'master'
|
275 |
priority = 0
|
276 |
level = 0
|
277 |
msgs = MSGS |
278 |
|
279 |
@staticmethod
|
280 |
def make_options(): |
281 |
return (('ignore', |
282 |
{'type' : 'csv', 'metavar' : '<file>[,<file>...]', |
283 |
'dest' : 'black_list', 'default' : ('CVS',), |
284 |
'help' : 'Add files or directories to the blacklist. ' |
285 |
'They should be base names, not paths.'}),
|
286 |
('persistent',
|
287 |
{'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>', |
288 |
'level': 1, |
289 |
'help' : 'Pickle collected data for later comparisons.'}), |
290 |
|
291 |
('load-plugins',
|
292 |
{'type' : 'csv', 'metavar' : '<modules>', 'default' : (), |
293 |
'level': 1, |
294 |
'help' : 'List of plugins (as comma separated values of ' |
295 |
'python modules names) to load, usually to register '
|
296 |
'additional checkers.'}),
|
297 |
|
298 |
('output-format',
|
299 |
{'default': 'text', 'type': 'string', 'metavar' : '<format>', |
300 |
'short': 'f', |
301 |
'group': 'Reports', |
302 |
'help' : 'Set the output format. Available formats are text,' |
303 |
' parseable, colorized, msvs (visual studio) and html. You '
|
304 |
'can also give a reporter class, eg mypackage.mymodule.'
|
305 |
'MyReporterClass.'}),
|
306 |
|
307 |
('files-output',
|
308 |
{'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', |
309 |
'group': 'Reports', 'level': 1, |
310 |
'help' : 'Put messages in a separate file for each module / ' |
311 |
'package specified on the command line instead of printing '
|
312 |
'them on stdout. Reports (if any) will be written in a file '
|
313 |
'name "pylint_global.[txt|html]".'}),
|
314 |
|
315 |
('reports',
|
316 |
{'default': 1, 'type' : 'yn', 'metavar' : '<y_or_n>', |
317 |
'short': 'r', |
318 |
'group': 'Reports', |
319 |
'help' : 'Tells whether to display a full report or only the ' |
320 |
'messages'}),
|
321 |
|
322 |
('evaluation',
|
323 |
{'type' : 'string', 'metavar' : '<python_expression>', |
324 |
'group': 'Reports', 'level': 1, |
325 |
'default': '10.0 - ((float(5 * error + warning + refactor + ' |
326 |
'convention) / statement) * 10)',
|
327 |
'help' : 'Python expression which should return a note less ' |
328 |
'than 10 (10 is the highest note). You have access '
|
329 |
'to the variables errors warning, statement which '
|
330 |
'respectively contain the number of errors / '
|
331 |
'warnings messages and the total number of '
|
332 |
'statements analyzed. This is used by the global '
|
333 |
'evaluation report (RP0004).'}),
|
334 |
|
335 |
('comment', utils.deprecated_option(opt_type='yn')), |
336 |
|
337 |
('confidence',
|
338 |
{'type' : 'multiple_choice', 'metavar': '<levels>', |
339 |
'default': '', |
340 |
'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS], |
341 |
'group': 'Messages control', |
342 |
'help' : 'Only show warnings with the listed confidence levels.' |
343 |
' Leave empty to show all. Valid levels: %s' % (
|
344 |
', '.join(c.name for c in interfaces.CONFIDENCE_LEVELS),)}), |
345 |
|
346 |
('enable',
|
347 |
{'type' : 'csv', 'metavar': '<msg ids>', |
348 |
'short': 'e', |
349 |
'group': 'Messages control', |
350 |
'help' : 'Enable the message, report, category or checker with the ' |
351 |
'given id(s). You can either give multiple identifier '
|
352 |
'separated by comma (,) or put this option multiple time. '
|
353 |
'See also the "--disable" option for examples. '}),
|
354 |
|
355 |
('disable',
|
356 |
{'type' : 'csv', 'metavar': '<msg ids>', |
357 |
'short': 'd', |
358 |
'group': 'Messages control', |
359 |
'help' : 'Disable the message, report, category or checker ' |
360 |
'with the given id(s). You can either give multiple identifiers'
|
361 |
' separated by comma (,) or put this option multiple times '
|
362 |
'(only on the command line, not in the configuration file '
|
363 |
'where it should appear only once).'
|
364 |
'You can also use "--disable=all" to disable everything first '
|
365 |
'and then reenable specific checks. For example, if you want '
|
366 |
'to run only the similarities checker, you can use '
|
367 |
'"--disable=all --enable=similarities". '
|
368 |
'If you want to run only the classes checker, but have no '
|
369 |
'Warning level messages displayed, use'
|
370 |
'"--disable=all --enable=classes --disable=W"'}),
|
371 |
|
372 |
('msg-template',
|
373 |
{'type' : 'string', 'metavar': '<template>', |
374 |
'group': 'Reports', |
375 |
'help' : ('Template used to display messages. ' |
376 |
'This is a python new-style format string '
|
377 |
'used to format the message information. '
|
378 |
'See doc for all details')
|
379 |
}), |
380 |
|
381 |
('include-ids', utils.deprecated_option('i', 'yn', INCLUDE_IDS_HELP)), |
382 |
('symbols', utils.deprecated_option('s', 'yn', SYMBOLS_HELP)), |
383 |
|
384 |
('jobs',
|
385 |
{'type' : 'int', 'metavar': '<n-processes>', |
386 |
'short': 'j', |
387 |
'default': 1, |
388 |
'help' : '''Use multiple processes to speed up Pylint.''', |
389 |
}), |
390 |
|
391 |
('unsafe-load-any-extension',
|
392 |
{'type': 'yn', 'metavar': '<yn>', 'default': False, 'hide': True, |
393 |
'help': ('Allow loading of arbitrary C extensions. Extensions' |
394 |
' are imported into the active Python interpreter and'
|
395 |
' may run arbitrary code.')}),
|
396 |
|
397 |
('extension-pkg-whitelist',
|
398 |
{'type': 'csv', 'metavar': '<pkg[,pkg]>', 'default': [], |
399 |
'help': ('A comma-separated list of package or module names' |
400 |
' from where C extensions may be loaded. Extensions are'
|
401 |
' loading into the active Python interpreter and may run'
|
402 |
' arbitrary code')}
|
403 |
), |
404 |
|
405 |
('optimize-ast',
|
406 |
{'type': 'yn', 'metavar': '<yn>', 'default': False, |
407 |
'help': ('Allow optimization of some AST trees. This will ' |
408 |
'activate a peephole AST optimizer, which will '
|
409 |
'apply various small optimizations. For instance, '
|
410 |
'it can be used to obtain the result of joining '
|
411 |
'multiple strings with the addition operator. '
|
412 |
'Joining a lot of strings can lead to a maximum '
|
413 |
'recursion error in Pylint and this flag can prevent '
|
414 |
'that. It has one side effect, the resulting AST '
|
415 |
'will be different than the one from reality.')}
|
416 |
), |
417 |
) |
418 |
|
419 |
option_groups = ( |
420 |
('Messages control', 'Options controling analysis messages'), |
421 |
('Reports', 'Options related to output formating and reporting'), |
422 |
) |
423 |
|
424 |
def __init__(self, options=(), reporter=None, option_groups=(), |
425 |
pylintrc=None):
|
426 |
# some stuff has to be done before ancestors initialization...
|
427 |
#
|
428 |
# messages store / checkers / reporter / astroid manager
|
429 |
self.msgs_store = utils.MessagesStore()
|
430 |
self.reporter = None |
431 |
self._reporter_name = None |
432 |
self._reporters = {}
|
433 |
self._checkers = collections.defaultdict(list) |
434 |
self._pragma_lineno = {}
|
435 |
self._ignore_file = False |
436 |
# visit variables
|
437 |
self.file_state = utils.FileState()
|
438 |
self.current_name = None |
439 |
self.current_file = None |
440 |
self.stats = None |
441 |
# init options
|
442 |
self._external_opts = options
|
443 |
self.options = options + PyLinter.make_options()
|
444 |
self.option_groups = option_groups + PyLinter.option_groups
|
445 |
self._options_methods = {
|
446 |
'enable': self.enable, |
447 |
'disable': self.disable} |
448 |
self._bw_options_methods = {'disable-msg': self.disable, |
449 |
'enable-msg': self.enable} |
450 |
full_version = '%%prog %s, \nastroid %s\nPython %s' % (
|
451 |
version, astroid_version, sys.version) |
452 |
utils.MessagesHandlerMixIn.__init__(self)
|
453 |
utils.ReportsHandlerMixIn.__init__(self)
|
454 |
super(PyLinter, self).__init__( |
455 |
usage=__doc__, |
456 |
version=full_version, |
457 |
config_file=pylintrc or config.PYLINTRC)
|
458 |
checkers.BaseTokenChecker.__init__(self)
|
459 |
# provided reports
|
460 |
self.reports = (('RP0001', 'Messages by category', |
461 |
report_total_messages_stats), |
462 |
('RP0002', '% errors / warnings by module', |
463 |
report_messages_by_module_stats), |
464 |
('RP0003', 'Messages', |
465 |
report_messages_stats), |
466 |
('RP0004', 'Global evaluation', |
467 |
self.report_evaluation),
|
468 |
) |
469 |
self.register_checker(self) |
470 |
self._dynamic_plugins = set() |
471 |
self._python3_porting_mode = False |
472 |
self._error_mode = False |
473 |
self.load_provider_defaults()
|
474 |
if reporter:
|
475 |
self.set_reporter(reporter)
|
476 |
|
477 |
def load_default_plugins(self): |
478 |
checkers.initialize(self)
|
479 |
reporters.initialize(self)
|
480 |
# Make sure to load the default reporter, because
|
481 |
# the option has been set before the plugins had been loaded.
|
482 |
if not self.reporter: |
483 |
self._load_reporter()
|
484 |
|
485 |
def load_plugin_modules(self, modnames): |
486 |
"""take a list of module names which are pylint plugins and load
|
487 |
and register them
|
488 |
"""
|
489 |
for modname in modnames: |
490 |
if modname in self._dynamic_plugins: |
491 |
continue
|
492 |
self._dynamic_plugins.add(modname)
|
493 |
module = modutils.load_module_from_name(modname) |
494 |
module.register(self)
|
495 |
|
496 |
def _load_reporter(self): |
497 |
name = self._reporter_name.lower()
|
498 |
if name in self._reporters: |
499 |
self.set_reporter(self._reporters[name]()) |
500 |
else:
|
501 |
qname = self._reporter_name
|
502 |
module = modutils.load_module_from_name( |
503 |
modutils.get_module_part(qname)) |
504 |
class_name = qname.split('.')[-1] |
505 |
reporter_class = getattr(module, class_name)
|
506 |
self.set_reporter(reporter_class())
|
507 |
|
508 |
def set_reporter(self, reporter): |
509 |
"""set the reporter used to display messages and reports"""
|
510 |
self.reporter = reporter
|
511 |
reporter.linter = self
|
512 |
|
513 |
def set_option(self, optname, value, action=None, optdict=None): |
514 |
"""overridden from config.OptionsProviderMixin to handle some
|
515 |
special options
|
516 |
"""
|
517 |
if optname in self._options_methods or \ |
518 |
optname in self._bw_options_methods: |
519 |
if value:
|
520 |
try:
|
521 |
meth = self._options_methods[optname]
|
522 |
except KeyError: |
523 |
meth = self._bw_options_methods[optname]
|
524 |
warnings.warn('%s is deprecated, replace it by %s' % (optname,
|
525 |
optname.split('-')[0]), |
526 |
DeprecationWarning)
|
527 |
value = utils._check_csv(value) |
528 |
if isinstance(value, (list, tuple)): |
529 |
for _id in value: |
530 |
meth(_id, ignore_unknown=True)
|
531 |
else:
|
532 |
meth(value) |
533 |
return # no need to call set_option, disable/enable methods do it |
534 |
elif optname == 'output-format': |
535 |
self._reporter_name = value
|
536 |
# If the reporters are already available, load
|
537 |
# the reporter class.
|
538 |
if self._reporters: |
539 |
self._load_reporter()
|
540 |
|
541 |
try:
|
542 |
checkers.BaseTokenChecker.set_option(self, optname,
|
543 |
value, action, optdict) |
544 |
except config.UnsupportedAction:
|
545 |
print('option %s can\'t be read from config file' % \
|
546 |
optname, file=sys.stderr) |
547 |
|
548 |
def register_reporter(self, reporter_class): |
549 |
self._reporters[reporter_class.name] = reporter_class
|
550 |
|
551 |
def report_order(self): |
552 |
reports = sorted(self._reports, key=lambda x: getattr(x, 'name', '')) |
553 |
try:
|
554 |
# Remove the current reporter and add it
|
555 |
# at the end of the list.
|
556 |
reports.pop(reports.index(self))
|
557 |
except ValueError: |
558 |
pass
|
559 |
else:
|
560 |
reports.append(self)
|
561 |
return reports
|
562 |
|
563 |
# checkers manipulation methods ############################################
|
564 |
|
565 |
def register_checker(self, checker): |
566 |
"""register a new checker
|
567 |
|
568 |
checker is an object implementing IRawChecker or / and IAstroidChecker
|
569 |
"""
|
570 |
assert checker.priority <= 0, 'checker priority can\'t be >= 0' |
571 |
self._checkers[checker.name].append(checker)
|
572 |
for r_id, r_title, r_cb in checker.reports: |
573 |
self.register_report(r_id, r_title, r_cb, checker)
|
574 |
self.register_options_provider(checker)
|
575 |
if hasattr(checker, 'msgs'): |
576 |
self.msgs_store.register_messages(checker)
|
577 |
checker.load_defaults() |
578 |
|
579 |
# Register the checker, but disable all of its messages.
|
580 |
# TODO(cpopa): we should have a better API for this.
|
581 |
if not getattr(checker, 'enabled', True): |
582 |
self.disable(checker.name)
|
583 |
|
584 |
def disable_noerror_messages(self): |
585 |
for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category): |
586 |
if msgcat == 'E': |
587 |
for msgid in msgids: |
588 |
self.enable(msgid)
|
589 |
else:
|
590 |
for msgid in msgids: |
591 |
self.disable(msgid)
|
592 |
|
593 |
def disable_reporters(self): |
594 |
"""disable all reporters"""
|
595 |
for _reporters in six.itervalues(self._reports): |
596 |
for report_id, _, _ in _reporters: |
597 |
self.disable_report(report_id)
|
598 |
|
599 |
def error_mode(self): |
600 |
"""error mode: enable only errors; no reports, no persistent"""
|
601 |
self._error_mode = True |
602 |
self.disable_noerror_messages()
|
603 |
self.disable('miscellaneous') |
604 |
if self._python3_porting_mode: |
605 |
self.disable('all') |
606 |
for msg_id in self._checker_messages('python3'): |
607 |
if msg_id.startswith('E'): |
608 |
self.enable(msg_id)
|
609 |
else:
|
610 |
self.disable('python3') |
611 |
self.set_option('reports', False) |
612 |
self.set_option('persistent', False) |
613 |
|
614 |
def python3_porting_mode(self): |
615 |
"""Disable all other checkers and enable Python 3 warnings."""
|
616 |
self.disable('all') |
617 |
self.enable('python3') |
618 |
if self._error_mode: |
619 |
# The error mode was activated, using the -E flag.
|
620 |
# So we'll need to enable only the errors from the
|
621 |
# Python 3 porting checker.
|
622 |
for msg_id in self._checker_messages('python3'): |
623 |
if msg_id.startswith('E'): |
624 |
self.enable(msg_id)
|
625 |
else:
|
626 |
self.disable(msg_id)
|
627 |
self._python3_porting_mode = True |
628 |
|
629 |
# block level option handling #############################################
|
630 |
#
|
631 |
# see func_block_disable_msg.py test case for expected behaviour
|
632 |
|
633 |
def process_tokens(self, tokens): |
634 |
"""process tokens from the current module to search for module/block
|
635 |
level options
|
636 |
"""
|
637 |
control_pragmas = {'disable', 'enable'} |
638 |
for (tok_type, content, start, _, _) in tokens: |
639 |
if tok_type != tokenize.COMMENT:
|
640 |
continue
|
641 |
match = utils.OPTION_RGX.search(content) |
642 |
if match is None: |
643 |
continue
|
644 |
if match.group(1).strip() == "disable-all" or \ |
645 |
match.group(1).strip() == 'skip-file': |
646 |
if match.group(1).strip() == "disable-all": |
647 |
self.add_message('deprecated-pragma', line=start[0], |
648 |
args=('disable-all', 'skip-file')) |
649 |
self.add_message('file-ignored', line=start[0]) |
650 |
self._ignore_file = True |
651 |
return
|
652 |
try:
|
653 |
opt, value = match.group(1).split('=', 1) |
654 |
except ValueError: |
655 |
self.add_message('bad-inline-option', args=match.group(1).strip(), |
656 |
line=start[0])
|
657 |
continue
|
658 |
opt = opt.strip() |
659 |
if opt in self._options_methods or opt in self._bw_options_methods: |
660 |
try:
|
661 |
meth = self._options_methods[opt]
|
662 |
except KeyError: |
663 |
meth = self._bw_options_methods[opt]
|
664 |
# found a "(dis|en)able-msg" pragma deprecated suppresssion
|
665 |
self.add_message('deprecated-pragma', line=start[0], |
666 |
args=(opt, opt.replace('-msg', ''))) |
667 |
for msgid in utils._splitstrip(value): |
668 |
# Add the line where a control pragma was encountered.
|
669 |
if opt in control_pragmas: |
670 |
self._pragma_lineno[msgid] = start[0] |
671 |
|
672 |
try:
|
673 |
if (opt, msgid) == ('disable', 'all'): |
674 |
self.add_message('deprecated-pragma', line=start[0], |
675 |
args=('disable=all', 'skip-file')) |
676 |
self.add_message('file-ignored', line=start[0]) |
677 |
self._ignore_file = True |
678 |
return
|
679 |
meth(msgid, 'module', start[0]) |
680 |
except utils.UnknownMessage:
|
681 |
self.add_message('bad-option-value', args=msgid, line=start[0]) |
682 |
else:
|
683 |
self.add_message('unrecognized-inline-option', args=opt, line=start[0]) |
684 |
|
685 |
|
686 |
# code checking methods ###################################################
|
687 |
|
688 |
def get_checkers(self): |
689 |
"""return all available checkers as a list"""
|
690 |
return [self] + [c for _checkers in six.itervalues(self._checkers) |
691 |
for c in _checkers if c is not self] |
692 |
|
693 |
def prepare_checkers(self): |
694 |
"""return checkers needed for activated messages and reports"""
|
695 |
if not self.config.reports: |
696 |
self.disable_reporters()
|
697 |
# get needed checkers
|
698 |
neededcheckers = [self]
|
699 |
for checker in self.get_checkers()[1:]: |
700 |
# fatal errors should not trigger enable / disabling a checker
|
701 |
messages = set(msg for msg in checker.msgs |
702 |
if msg[0] != 'F' and self.is_message_enabled(msg)) |
703 |
if (messages or |
704 |
any(self.report_is_enabled(r[0]) for r in checker.reports)): |
705 |
neededcheckers.append(checker) |
706 |
# Sort checkers by priority
|
707 |
neededcheckers = sorted(neededcheckers,
|
708 |
key=operator.attrgetter('priority'),
|
709 |
reverse=True)
|
710 |
return neededcheckers
|
711 |
|
712 |
def should_analyze_file(self, modname, path): # pylint: disable=unused-argument, no-self-use |
713 |
"""Returns whether or not a module should be checked.
|
714 |
|
715 |
This implementation returns True for all python source file, indicating
|
716 |
that all files should be linted.
|
717 |
|
718 |
Subclasses may override this method to indicate that modules satisfying
|
719 |
certain conditions should not be linted.
|
720 |
|
721 |
:param str modname: The name of the module to be checked.
|
722 |
:param str path: The full path to the source code of the module.
|
723 |
:returns: True if the module should be checked.
|
724 |
:rtype: bool
|
725 |
"""
|
726 |
return path.endswith('.py') |
727 |
|
728 |
def check(self, files_or_modules): |
729 |
"""main checking entry: check a list of files or modules from their
|
730 |
name.
|
731 |
"""
|
732 |
# initialize msgs_state now that all messages have been registered into
|
733 |
# the store
|
734 |
for msg in self.msgs_store.messages: |
735 |
if not msg.may_be_emitted(): |
736 |
self._msgs_state[msg.msgid] = False |
737 |
|
738 |
if not isinstance(files_or_modules, (list, tuple)): |
739 |
files_or_modules = (files_or_modules,) |
740 |
|
741 |
if self.config.jobs == 1: |
742 |
self._do_check(files_or_modules)
|
743 |
else:
|
744 |
with _patch_sysmodules():
|
745 |
self._parallel_check(files_or_modules)
|
746 |
|
747 |
def _get_jobs_config(self): |
748 |
child_config = {} |
749 |
filter_options = {'symbols', 'include-ids', 'long-help'} |
750 |
filter_options.update((opt_name for opt_name, _ in self._external_opts)) |
751 |
for opt_providers in six.itervalues(self._all_options): |
752 |
for optname, optdict, val in opt_providers.options_and_values(): |
753 |
if optdict.get('deprecated'): |
754 |
continue
|
755 |
|
756 |
if optname not in filter_options: |
757 |
child_config[optname] = utils._format_option_value( |
758 |
optdict, val) |
759 |
child_config['python3_porting_mode'] = self._python3_porting_mode |
760 |
child_config['plugins'] = self._dynamic_plugins |
761 |
return child_config
|
762 |
|
763 |
def _parallel_task(self, files_or_modules): |
764 |
# Prepare configuration for child linters.
|
765 |
child_config = self._get_jobs_config()
|
766 |
|
767 |
children = [] |
768 |
manager = multiprocessing.Manager() |
769 |
tasks_queue = manager.Queue() |
770 |
results_queue = manager.Queue() |
771 |
|
772 |
for _ in range(self.config.jobs): |
773 |
child_linter = ChildLinter(args=(tasks_queue, results_queue, |
774 |
child_config)) |
775 |
child_linter.start() |
776 |
children.append(child_linter) |
777 |
|
778 |
# Send files to child linters.
|
779 |
expanded_files = self.expand_files(files_or_modules)
|
780 |
for files_or_module in expanded_files: |
781 |
path = files_or_module['path']
|
782 |
tasks_queue.put([path]) |
783 |
|
784 |
# collect results from child linters
|
785 |
failed = False
|
786 |
for _ in expanded_files: |
787 |
try:
|
788 |
result = results_queue.get() |
789 |
except Exception as ex: |
790 |
print("internal error while receiving results from child linter",
|
791 |
file=sys.stderr) |
792 |
print(ex, file=sys.stderr) |
793 |
failed = True
|
794 |
break
|
795 |
yield result
|
796 |
|
797 |
# Stop child linters and wait for their completion.
|
798 |
for _ in range(self.config.jobs): |
799 |
tasks_queue.put('STOP')
|
800 |
for child in children: |
801 |
child.join() |
802 |
|
803 |
if failed:
|
804 |
print("Error occured, stopping the linter.", file=sys.stderr)
|
805 |
sys.exit(32)
|
806 |
|
807 |
def _parallel_check(self, files_or_modules): |
808 |
# Reset stats.
|
809 |
self.open()
|
810 |
|
811 |
all_stats = [] |
812 |
module = None
|
813 |
for result in self._parallel_task(files_or_modules): |
814 |
( |
815 |
_, |
816 |
self.file_state.base_name,
|
817 |
module, |
818 |
messages, |
819 |
stats, |
820 |
msg_status |
821 |
) = result |
822 |
|
823 |
for msg in messages: |
824 |
msg = utils.Message(*msg) |
825 |
self.set_current_module(module)
|
826 |
self.reporter.handle_message(msg)
|
827 |
|
828 |
all_stats.append(stats) |
829 |
self.msg_status |= msg_status
|
830 |
|
831 |
self.stats = _merge_stats(all_stats)
|
832 |
self.current_name = module
|
833 |
|
834 |
# Insert stats data to local checkers.
|
835 |
for checker in self.get_checkers(): |
836 |
if checker is not self: |
837 |
checker.stats = self.stats
|
838 |
|
839 |
def _do_check(self, files_or_modules): |
840 |
walker = utils.PyLintASTWalker(self)
|
841 |
_checkers = self.prepare_checkers()
|
842 |
tokencheckers = [c for c in _checkers |
843 |
if interfaces.implements(c, interfaces.ITokenChecker)
|
844 |
and c is not self] |
845 |
rawcheckers = [c for c in _checkers |
846 |
if interfaces.implements(c, interfaces.IRawChecker)]
|
847 |
# notify global begin
|
848 |
for checker in _checkers: |
849 |
checker.open() |
850 |
if interfaces.implements(checker, interfaces.IAstroidChecker):
|
851 |
walker.add_checker(checker) |
852 |
# build ast and check modules or packages
|
853 |
for descr in self.expand_files(files_or_modules): |
854 |
modname, filepath = descr['name'], descr['path'] |
855 |
if not descr['isarg'] and not self.should_analyze_file(modname, filepath): |
856 |
continue
|
857 |
if self.config.files_output: |
858 |
reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) |
859 |
self.reporter.set_output(open(reportfile, 'w')) |
860 |
self.set_current_module(modname, filepath)
|
861 |
# get the module representation
|
862 |
ast_node = self.get_ast(filepath, modname)
|
863 |
if ast_node is None: |
864 |
continue
|
865 |
# XXX to be correct we need to keep module_msgs_state for every
|
866 |
# analyzed module (the problem stands with localized messages which
|
867 |
# are only detected in the .close step)
|
868 |
self.file_state = utils.FileState(descr['basename']) |
869 |
self._ignore_file = False |
870 |
# fix the current file (if the source file was not available or
|
871 |
# if it's actually a c extension)
|
872 |
self.current_file = ast_node.file # pylint: disable=maybe-no-member |
873 |
self.check_astroid_module(ast_node, walker, rawcheckers, tokencheckers)
|
874 |
# warn about spurious inline messages handling
|
875 |
spurious_messages = self.file_state.iter_spurious_suppression_messages(self.msgs_store) |
876 |
for msgid, line, args in spurious_messages: |
877 |
self.add_message(msgid, line, None, args) |
878 |
# notify global end
|
879 |
self.stats['statement'] = walker.nbstatements |
880 |
for checker in reversed(_checkers): |
881 |
checker.close() |
882 |
|
883 |
def expand_files(self, modules): |
884 |
"""get modules and errors from a list of modules and handle errors
|
885 |
"""
|
886 |
result, errors = utils.expand_modules(modules, self.config.black_list)
|
887 |
for error in errors: |
888 |
message = modname = error["mod"]
|
889 |
key = error["key"]
|
890 |
self.set_current_module(modname)
|
891 |
if key == "fatal": |
892 |
message = str(error["ex"]).replace(os.getcwd() + os.sep, '') |
893 |
self.add_message(key, args=message)
|
894 |
return result
|
895 |
|
896 |
def set_current_module(self, modname, filepath=None): |
897 |
"""set the name of the currently analyzed module and
|
898 |
init statistics for it
|
899 |
"""
|
900 |
if not modname and filepath is None: |
901 |
return
|
902 |
self.reporter.on_set_current_module(modname, filepath)
|
903 |
self.current_name = modname
|
904 |
self.current_file = filepath or modname |
905 |
self.stats['by_module'][modname] = {} |
906 |
self.stats['by_module'][modname]['statement'] = 0 |
907 |
for msg_cat in six.itervalues(utils.MSG_TYPES): |
908 |
self.stats['by_module'][modname][msg_cat] = 0 |
909 |
|
910 |
def get_ast(self, filepath, modname): |
911 |
"""return a ast(roid) representation for a module"""
|
912 |
try:
|
913 |
return MANAGER.ast_from_file(filepath, modname, source=True) |
914 |
except astroid.AstroidBuildingException as ex: |
915 |
if isinstance(ex.args[0], SyntaxError): |
916 |
ex = ex.args[0]
|
917 |
self.add_message('syntax-error', |
918 |
line=ex.lineno or 0, |
919 |
args=ex.msg) |
920 |
else:
|
921 |
self.add_message('parse-error', args=ex) |
922 |
except Exception as ex: # pylint: disable=broad-except |
923 |
import traceback |
924 |
traceback.print_exc() |
925 |
self.add_message('astroid-error', args=(ex.__class__, ex)) |
926 |
|
927 |
def check_astroid_module(self, ast_node, walker, |
928 |
rawcheckers, tokencheckers): |
929 |
"""Check a module from its astroid representation."""
|
930 |
try:
|
931 |
tokens = utils.tokenize_module(ast_node) |
932 |
except tokenize.TokenError as ex: |
933 |
self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0]) |
934 |
return
|
935 |
|
936 |
if not ast_node.pure_python: |
937 |
self.add_message('raw-checker-failed', args=ast_node.name) |
938 |
else:
|
939 |
#assert astroid.file.endswith('.py')
|
940 |
# invoke ITokenChecker interface on self to fetch module/block
|
941 |
# level options
|
942 |
self.process_tokens(tokens)
|
943 |
if self._ignore_file: |
944 |
return False |
945 |
# walk ast to collect line numbers
|
946 |
self.file_state.collect_block_lines(self.msgs_store, ast_node) |
947 |
# run raw and tokens checkers
|
948 |
for checker in rawcheckers: |
949 |
checker.process_module(ast_node) |
950 |
for checker in tokencheckers: |
951 |
checker.process_tokens(tokens) |
952 |
# generate events to astroid checkers
|
953 |
walker.walk(ast_node) |
954 |
return True |
955 |
|
956 |
# IAstroidChecker interface #################################################
|
957 |
|
958 |
def open(self): |
959 |
"""initialize counters"""
|
960 |
self.stats = {'by_module' : {}, |
961 |
'by_msg' : {},
|
962 |
} |
963 |
MANAGER.optimize_ast = self.config.optimize_ast
|
964 |
MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
|
965 |
MANAGER.extension_package_whitelist.update( |
966 |
self.config.extension_pkg_whitelist)
|
967 |
for msg_cat in six.itervalues(utils.MSG_TYPES): |
968 |
self.stats[msg_cat] = 0 |
969 |
|
970 |
def generate_reports(self): |
971 |
"""close the whole package /module, it's time to make reports !
|
972 |
|
973 |
if persistent run, pickle results for later comparison
|
974 |
"""
|
975 |
# Display whatever messages are left on the reporter.
|
976 |
self.reporter.display_messages(report_nodes.Section())
|
977 |
|
978 |
if self.file_state.base_name is not None: |
979 |
# load previous results if any
|
980 |
previous_stats = config.load_results(self.file_state.base_name)
|
981 |
# XXX code below needs refactoring to be more reporter agnostic
|
982 |
self.reporter.on_close(self.stats, previous_stats) |
983 |
if self.config.reports: |
984 |
sect = self.make_reports(self.stats, previous_stats) |
985 |
if self.config.files_output: |
986 |
filename = 'pylint_global.' + self.reporter.extension |
987 |
self.reporter.set_output(open(filename, 'w')) |
988 |
else:
|
989 |
sect = report_nodes.Section() |
990 |
if self.config.reports: |
991 |
self.reporter.display_reports(sect)
|
992 |
# save results if persistent run
|
993 |
if self.config.persistent: |
994 |
config.save_results(self.stats, self.file_state.base_name) |
995 |
else:
|
996 |
self.reporter.on_close(self.stats, {}) |
997 |
|
998 |
# specific reports ########################################################
|
999 |
|
1000 |
def report_evaluation(self, sect, stats, previous_stats): |
1001 |
"""make the global evaluation report"""
|
1002 |
# check with at least check 1 statements (usually 0 when there is a
|
1003 |
# syntax error preventing pylint from further processing)
|
1004 |
if stats['statement'] == 0: |
1005 |
raise utils.EmptyReport()
|
1006 |
# get a global note for the code
|
1007 |
evaluation = self.config.evaluation
|
1008 |
try:
|
1009 |
note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used |
1010 |
except Exception as ex: # pylint: disable=broad-except |
1011 |
msg = 'An exception occurred while rating: %s' % ex
|
1012 |
else:
|
1013 |
stats['global_note'] = note
|
1014 |
msg = 'Your code has been rated at %.2f/10' % note
|
1015 |
pnote = previous_stats.get('global_note')
|
1016 |
if pnote is not None: |
1017 |
msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote)
|
1018 |
sect.append(report_nodes.Text(msg)) |
1019 |
|
1020 |
# some reporting functions ####################################################
|
1021 |
|
1022 |
def report_total_messages_stats(sect, stats, previous_stats): |
1023 |
"""make total errors / warnings report"""
|
1024 |
lines = ['type', 'number', 'previous', 'difference'] |
1025 |
lines += checkers.table_lines_from_stats(stats, previous_stats, |
1026 |
('convention', 'refactor', |
1027 |
'warning', 'error')) |
1028 |
sect.append(report_nodes.Table(children=lines, cols=4, rheaders=1)) |
1029 |
|
1030 |
def report_messages_stats(sect, stats, _): |
1031 |
"""make messages type report"""
|
1032 |
if not stats['by_msg']: |
1033 |
# don't print this report when we didn't detected any errors
|
1034 |
raise utils.EmptyReport()
|
1035 |
in_order = sorted([(value, msg_id)
|
1036 |
for msg_id, value in six.iteritems(stats['by_msg']) |
1037 |
if not msg_id.startswith('I')]) |
1038 |
in_order.reverse() |
1039 |
lines = ('message id', 'occurrences') |
1040 |
for value, msg_id in in_order: |
1041 |
lines += (msg_id, str(value))
|
1042 |
sect.append(report_nodes.Table(children=lines, cols=2, rheaders=1)) |
1043 |
|
1044 |
def report_messages_by_module_stats(sect, stats, _): |
1045 |
"""make errors / warnings by modules report"""
|
1046 |
if len(stats['by_module']) == 1: |
1047 |
# don't print this report when we are analysing a single module
|
1048 |
raise utils.EmptyReport()
|
1049 |
by_mod = collections.defaultdict(dict)
|
1050 |
for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): |
1051 |
total = stats[m_type] |
1052 |
for module in six.iterkeys(stats['by_module']): |
1053 |
mod_total = stats['by_module'][module][m_type]
|
1054 |
if total == 0: |
1055 |
percent = 0
|
1056 |
else:
|
1057 |
percent = float((mod_total)*100) / total |
1058 |
by_mod[module][m_type] = percent |
1059 |
sorted_result = [] |
1060 |
for module, mod_info in six.iteritems(by_mod): |
1061 |
sorted_result.append((mod_info['error'],
|
1062 |
mod_info['warning'],
|
1063 |
mod_info['refactor'],
|
1064 |
mod_info['convention'],
|
1065 |
module)) |
1066 |
sorted_result.sort() |
1067 |
sorted_result.reverse() |
1068 |
lines = ['module', 'error', 'warning', 'refactor', 'convention'] |
1069 |
for line in sorted_result: |
1070 |
# Don't report clean modules.
|
1071 |
if all(entry == 0 for entry in line[:-1]): |
1072 |
continue
|
1073 |
lines.append(line[-1])
|
1074 |
for val in line[:-1]: |
1075 |
lines.append('%.2f' % val)
|
1076 |
if len(lines) == 5: |
1077 |
raise utils.EmptyReport()
|
1078 |
sect.append(report_nodes.Table(children=lines, cols=5, rheaders=1)) |
1079 |
|
1080 |
|
1081 |
# utilities ###################################################################
|
1082 |
|
1083 |
|
1084 |
class ArgumentPreprocessingError(Exception): |
1085 |
"""Raised if an error occurs during argument preprocessing."""
|
1086 |
|
1087 |
|
1088 |
def preprocess_options(args, search_for): |
1089 |
"""look for some options (keys of <search_for>) which have to be processed
|
1090 |
before others
|
1091 |
|
1092 |
values of <search_for> are callback functions to call when the option is
|
1093 |
found
|
1094 |
"""
|
1095 |
i = 0
|
1096 |
while i < len(args): |
1097 |
arg = args[i] |
1098 |
if arg.startswith('--'): |
1099 |
try:
|
1100 |
option, val = arg[2:].split('=', 1) |
1101 |
except ValueError: |
1102 |
option, val = arg[2:], None |
1103 |
try:
|
1104 |
cb, takearg = search_for[option] |
1105 |
except KeyError: |
1106 |
i += 1
|
1107 |
else:
|
1108 |
del args[i]
|
1109 |
if takearg and val is None: |
1110 |
if i >= len(args) or args[i].startswith('-'): |
1111 |
msg = 'Option %s expects a value' % option
|
1112 |
raise ArgumentPreprocessingError(msg)
|
1113 |
val = args[i] |
1114 |
del args[i]
|
1115 |
elif not takearg and val is not None: |
1116 |
msg = "Option %s doesn't expects a value" % option
|
1117 |
raise ArgumentPreprocessingError(msg)
|
1118 |
cb(option, val) |
1119 |
else:
|
1120 |
i += 1
|
1121 |
|
1122 |
|
1123 |
@contextlib.contextmanager
|
1124 |
def fix_import_path(args): |
1125 |
"""Prepare sys.path for running the linter checks.
|
1126 |
|
1127 |
Within this context, each of the given arguments is importable.
|
1128 |
Paths are added to sys.path in corresponding order to the arguments.
|
1129 |
We avoid adding duplicate directories to sys.path.
|
1130 |
`sys.path` is reset to its original value upon exitign this context.
|
1131 |
"""
|
1132 |
orig = list(sys_path())
|
1133 |
changes = [] |
1134 |
for arg in args: |
1135 |
path = _get_python_path(arg) |
1136 |
if path in changes: |
1137 |
continue
|
1138 |
else:
|
1139 |
changes.append(path) |
1140 |
sys.path[:] = changes + sys_path() |
1141 |
try:
|
1142 |
yield
|
1143 |
finally:
|
1144 |
sys.path[:] = orig |
1145 |
|
1146 |
|
1147 |
class Run(object): |
1148 |
"""helper class to use as main for pylint :
|
1149 |
|
1150 |
run(*sys.argv[1:])
|
1151 |
"""
|
1152 |
LinterClass = PyLinter |
1153 |
option_groups = ( |
1154 |
('Commands', 'Options which are actually commands. Options in this \ |
1155 |
group are mutually exclusive.'),
|
1156 |
) |
1157 |
|
1158 |
def __init__(self, args, reporter=None, exit=True): |
1159 |
self._rcfile = None |
1160 |
self._plugins = []
|
1161 |
try:
|
1162 |
preprocess_options(args, { |
1163 |
# option: (callback, takearg)
|
1164 |
'init-hook': (cb_init_hook, True), |
1165 |
'rcfile': (self.cb_set_rcfile, True), |
1166 |
'load-plugins': (self.cb_add_plugins, True), |
1167 |
}) |
1168 |
except ArgumentPreprocessingError as ex: |
1169 |
print(ex, file=sys.stderr) |
1170 |
sys.exit(32)
|
1171 |
|
1172 |
self.linter = linter = self.LinterClass(( |
1173 |
('rcfile',
|
1174 |
{'action' : 'callback', 'callback' : lambda *args: 1, |
1175 |
'type': 'string', 'metavar': '<file>', |
1176 |
'help' : 'Specify a configuration file.'}), |
1177 |
|
1178 |
('init-hook',
|
1179 |
{'action' : 'callback', 'callback' : lambda *args: 1, |
1180 |
'type' : 'string', 'metavar': '<code>', |
1181 |
'level': 1, |
1182 |
'help' : 'Python code to execute, usually for sys.path ' |
1183 |
'manipulation such as pygtk.require().'}),
|
1184 |
|
1185 |
('help-msg',
|
1186 |
{'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>', |
1187 |
'callback' : self.cb_help_message, |
1188 |
'group': 'Commands', |
1189 |
'help' : 'Display a help message for the given message id and ' |
1190 |
'exit. The value may be a comma separated list of message ids.'}),
|
1191 |
|
1192 |
('list-msgs',
|
1193 |
{'action' : 'callback', 'metavar': '<msg-id>', |
1194 |
'callback' : self.cb_list_messages, |
1195 |
'group': 'Commands', 'level': 1, |
1196 |
'help' : "Generate pylint's messages."}), |
1197 |
|
1198 |
('list-conf-levels',
|
1199 |
{'action' : 'callback', |
1200 |
'callback' : cb_list_confidence_levels,
|
1201 |
'group': 'Commands', 'level': 1, |
1202 |
'help' : "Generate pylint's messages."}), |
1203 |
|
1204 |
('full-documentation',
|
1205 |
{'action' : 'callback', 'metavar': '<msg-id>', |
1206 |
'callback' : self.cb_full_documentation, |
1207 |
'group': 'Commands', 'level': 1, |
1208 |
'help' : "Generate pylint's full documentation."}), |
1209 |
|
1210 |
('generate-rcfile',
|
1211 |
{'action' : 'callback', 'callback' : self.cb_generate_config, |
1212 |
'group': 'Commands', |
1213 |
'help' : 'Generate a sample configuration file according to ' |
1214 |
'the current configuration. You can put other options '
|
1215 |
'before this one to get them in the generated '
|
1216 |
'configuration.'}),
|
1217 |
|
1218 |
('generate-man',
|
1219 |
{'action' : 'callback', 'callback' : self.cb_generate_manpage, |
1220 |
'group': 'Commands', |
1221 |
'help' : "Generate pylint's man page.", 'hide': True}), |
1222 |
|
1223 |
('errors-only',
|
1224 |
{'action' : 'callback', 'callback' : self.cb_error_mode, |
1225 |
'short': 'E', |
1226 |
'help' : 'In error mode, checkers without error messages are ' |
1227 |
'disabled and for others, only the ERROR messages are '
|
1228 |
'displayed, and no reports are done by default'''}), |
1229 |
|
1230 |
('py3k',
|
1231 |
{'action' : 'callback', 'callback' : self.cb_python3_porting_mode, |
1232 |
'help' : 'In Python 3 porting mode, all checkers will be ' |
1233 |
'disabled and only messages emitted by the porting '
|
1234 |
'checker will be displayed'}),
|
1235 |
|
1236 |
('profile', utils.deprecated_option(opt_type='yn')), |
1237 |
|
1238 |
), option_groups=self.option_groups, pylintrc=self._rcfile) |
1239 |
# register standard checkers
|
1240 |
linter.load_default_plugins() |
1241 |
# load command line plugins
|
1242 |
linter.load_plugin_modules(self._plugins)
|
1243 |
# add some help section
|
1244 |
linter.add_help_section('Environment variables', config.ENV_HELP, level=1) |
1245 |
# pylint: disable=bad-continuation
|
1246 |
linter.add_help_section('Output',
|
1247 |
'Using the default text output, the message format is : \n'
|
1248 |
' \n'
|
1249 |
' MESSAGE_TYPE: LINE_NUM:[OBJECT:] MESSAGE \n'
|
1250 |
' \n'
|
1251 |
'There are 5 kind of message types : \n'
|
1252 |
' * (C) convention, for programming standard violation \n'
|
1253 |
' * (R) refactor, for bad code smell \n'
|
1254 |
' * (W) warning, for python specific problems \n'
|
1255 |
' * (E) error, for probable bugs in the code \n'
|
1256 |
' * (F) fatal, if an error occurred which prevented pylint from doing further\n'
|
1257 |
'processing.\n'
|
1258 |
, level=1)
|
1259 |
linter.add_help_section('Output status code',
|
1260 |
'Pylint should leave with following status code: \n'
|
1261 |
' * 0 if everything went fine \n'
|
1262 |
' * 1 if a fatal message was issued \n'
|
1263 |
' * 2 if an error message was issued \n'
|
1264 |
' * 4 if a warning message was issued \n'
|
1265 |
' * 8 if a refactor message was issued \n'
|
1266 |
' * 16 if a convention message was issued \n'
|
1267 |
' * 32 on usage error \n'
|
1268 |
' \n'
|
1269 |
'status 1 to 16 will be bit-ORed so you can know which different categories has\n'
|
1270 |
'been issued by analysing pylint output status code\n',
|
1271 |
level=1)
|
1272 |
# read configuration
|
1273 |
linter.disable('suppressed-message')
|
1274 |
linter.disable('useless-suppression')
|
1275 |
linter.read_config_file() |
1276 |
config_parser = linter.cfgfile_parser |
1277 |
# run init hook, if present, before loading plugins
|
1278 |
if config_parser.has_option('MASTER', 'init-hook'): |
1279 |
cb_init_hook('init-hook',
|
1280 |
utils._unquote(config_parser.get('MASTER',
|
1281 |
'init-hook')))
|
1282 |
# is there some additional plugins in the file configuration, in
|
1283 |
if config_parser.has_option('MASTER', 'load-plugins'): |
1284 |
plugins = utils._splitstrip( |
1285 |
config_parser.get('MASTER', 'load-plugins')) |
1286 |
linter.load_plugin_modules(plugins) |
1287 |
# now we can load file config and command line, plugins (which can
|
1288 |
# provide options) have been registered
|
1289 |
linter.load_config_file() |
1290 |
if reporter:
|
1291 |
# if a custom reporter is provided as argument, it may be overridden
|
1292 |
# by file parameters, so re-set it here, but before command line
|
1293 |
# parsing so it's still overrideable by command line option
|
1294 |
linter.set_reporter(reporter) |
1295 |
try:
|
1296 |
args = linter.load_command_line_configuration(args) |
1297 |
except SystemExit as exc: |
1298 |
if exc.code == 2: # bad options |
1299 |
exc.code = 32
|
1300 |
raise
|
1301 |
if not args: |
1302 |
print(linter.help()) |
1303 |
sys.exit(32)
|
1304 |
|
1305 |
if linter.config.jobs < 0: |
1306 |
print("Jobs number (%d) should be greater than 0"
|
1307 |
% linter.config.jobs, file=sys.stderr) |
1308 |
sys.exit(32)
|
1309 |
if linter.config.jobs > 1 or linter.config.jobs == 0: |
1310 |
if multiprocessing is None: |
1311 |
print("Multiprocessing library is missing, "
|
1312 |
"fallback to single process", file=sys.stderr)
|
1313 |
linter.set_option("jobs", 1) |
1314 |
else:
|
1315 |
if linter.config.jobs == 0: |
1316 |
linter.config.jobs = multiprocessing.cpu_count() |
1317 |
|
1318 |
# insert current working directory to the python path to have a correct
|
1319 |
# behaviour
|
1320 |
with fix_import_path(args):
|
1321 |
linter.check(args) |
1322 |
linter.generate_reports() |
1323 |
if exit: |
1324 |
sys.exit(self.linter.msg_status)
|
1325 |
|
1326 |
def cb_set_rcfile(self, name, value): |
1327 |
"""callback for option preprocessing (i.e. before option parsing)"""
|
1328 |
self._rcfile = value
|
1329 |
|
1330 |
def cb_add_plugins(self, name, value): |
1331 |
"""callback for option preprocessing (i.e. before option parsing)"""
|
1332 |
self._plugins.extend(utils._splitstrip(value))
|
1333 |
|
1334 |
def cb_error_mode(self, *args, **kwargs): |
1335 |
"""error mode:
|
1336 |
* disable all but error messages
|
1337 |
* disable the 'miscellaneous' checker which can be safely deactivated in
|
1338 |
debug
|
1339 |
* disable reports
|
1340 |
* do not save execution information
|
1341 |
"""
|
1342 |
self.linter.error_mode()
|
1343 |
|
1344 |
def cb_generate_config(self, *args, **kwargs): |
1345 |
"""optik callback for sample config file generation"""
|
1346 |
self.linter.generate_config(skipsections=('COMMANDS',)) |
1347 |
sys.exit(0)
|
1348 |
|
1349 |
def cb_generate_manpage(self, *args, **kwargs): |
1350 |
"""optik callback for sample config file generation"""
|
1351 |
from pylint import __pkginfo__ |
1352 |
self.linter.generate_manpage(__pkginfo__)
|
1353 |
sys.exit(0)
|
1354 |
|
1355 |
def cb_help_message(self, option, optname, value, parser): |
1356 |
"""optik callback for printing some help about a particular message"""
|
1357 |
self.linter.msgs_store.help_message(utils._splitstrip(value))
|
1358 |
sys.exit(0)
|
1359 |
|
1360 |
def cb_full_documentation(self, option, optname, value, parser): |
1361 |
"""optik callback for printing full documentation"""
|
1362 |
self.linter.print_full_documentation()
|
1363 |
sys.exit(0)
|
1364 |
|
1365 |
def cb_list_messages(self, option, optname, value, parser): # FIXME |
1366 |
"""optik callback for printing available messages"""
|
1367 |
self.linter.msgs_store.list_messages()
|
1368 |
sys.exit(0)
|
1369 |
|
1370 |
def cb_python3_porting_mode(self, *args, **kwargs): |
1371 |
"""Activate only the python3 porting checker."""
|
1372 |
self.linter.python3_porting_mode()
|
1373 |
|
1374 |
|
1375 |
def cb_list_confidence_levels(option, optname, value, parser): |
1376 |
for level in interfaces.CONFIDENCE_LEVELS: |
1377 |
print('%-18s: %s' % level)
|
1378 |
sys.exit(0)
|
1379 |
|
1380 |
def cb_init_hook(optname, value): |
1381 |
"""exec arbitrary code to set sys.path for instance"""
|
1382 |
exec(value) # pylint: disable=exec-used
|
1383 |
|
1384 |
|
1385 |
if __name__ == '__main__': |
1386 |
Run(sys.argv[1:])
|