diff options
author | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2010-04-19 11:22:40 +0200 |
---|---|---|
committer | Sylvain Th?nault <sylvain.thenault@logilab.fr> | 2010-04-19 11:22:40 +0200 |
commit | 62adf7f849a701687d0e3fdddebf596b5994a801 (patch) | |
tree | 167b93ca6e99f793eb14d3a73dadaf7c33f8e130 | |
parent | cefa5131bc24376108431ff0db404b30f94637bf (diff) | |
download | pylint-62adf7f849a701687d0e3fdddebf596b5994a801.tar.gz |
cleanup, d-t-w, fix remaining [en|dis] method call
-rw-r--r-- | checkers/__init__.py | 13 | ||||
-rw-r--r-- | checkers/raw_metrics.py | 25 | ||||
-rw-r--r-- | checkers/similar.py | 46 | ||||
-rw-r--r-- | lint.py | 157 | ||||
-rw-r--r-- | setup.py | 14 | ||||
-rw-r--r-- | test/regrtest_data/package/__init__.py | 2 | ||||
-rw-r--r-- | test/smoketest.py | 28 | ||||
-rw-r--r-- | test/test_func.py | 26 | ||||
-rw-r--r-- | test/test_import_graph.py | 6 | ||||
-rw-r--r-- | test/test_regr.py | 8 | ||||
-rw-r--r-- | test/unittest_lint.py | 70 | ||||
-rw-r--r-- | utils.py | 116 |
12 files changed, 257 insertions, 254 deletions
diff --git a/checkers/__init__.py b/checkers/__init__.py index 144b680..55efce3 100644 --- a/checkers/__init__.py +++ b/checkers/__init__.py @@ -64,11 +64,18 @@ def table_lines_from_stats(stats, old_stats, columns): class BaseChecker(OptionsProviderMixIn, ASTWalker): """base class for checkers""" - - options = () - needs_checkers = () + # checker name (you may reuse an existing one) name = None + # options level (0 will be displaying in --help, 1 in --long-help) level = 1 + # ordered list of options to control the ckecker behaviour + options = () + # checker that should be run before this one + needs_checkers = () + # messages issued by this checker + msgs = {} + # reports issued by this checker + reports = () def __init__(self, linter=None): """checker instances should have the linter as argument diff --git a/checkers/raw_metrics.py b/checkers/raw_metrics.py index 4771207..872ca7b 100644 --- a/checkers/raw_metrics.py +++ b/checkers/raw_metrics.py @@ -10,7 +10,7 @@ # You should have received a copy of the GNU General Public License along with # this program; if not, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -""" Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE). +""" Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). http://www.logilab.fr/ -- mailto:contact@logilab.fr Raw metrics checker @@ -28,7 +28,6 @@ from pylint.interfaces import IRawChecker from pylint.checkers import BaseRawChecker, EmptyReport from pylint.reporters import diff_string - def report_raw_stats(sect, stats, old_stats): """calculate percentage of code / doc / comment / empty """ @@ -50,7 +49,7 @@ def report_raw_stats(sect, stats, old_stats): str(old), diff_str) sect.append(Table(children=lines, cols=5, rheaders=1)) - + class RawMetricsChecker(BaseRawChecker): """does not check anything but gives some raw metrics : * total number of lines @@ -59,7 +58,7 @@ class RawMetricsChecker(BaseRawChecker): * total number of comments lines * total number of empty lines """ - + __implements__ = (IRawChecker,) # configuration section name @@ -69,18 +68,18 @@ class RawMetricsChecker(BaseRawChecker): # messages msgs = {} # reports - reports = ( ('R0701', 'Raw metrics', report_raw_stats), ) - + reports = ( ('RP0701', 'Raw metrics', report_raw_stats), ) + def __init__(self, linter): BaseRawChecker.__init__(self, linter) self.stats = None - + def open(self): """init statistics""" self.stats = self.linter.add_stats(total_lines=0, code_lines=0, empty_lines=0, docstring_lines=0, comment_lines=0) - + def process_tokens(self, tokens): """update stats""" i = 0 @@ -90,12 +89,11 @@ class RawMetricsChecker(BaseRawChecker): self.stats['total_lines'] += lines_number self.stats[line_type] += lines_number - + JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER) def get_type(tokens, start_index): - """return the line type : docstring, comment, code, empty - """ + """return the line type : docstring, comment, code, empty""" i = start_index tok_type = tokens[i][0] start = tokens[i][2] @@ -114,15 +112,14 @@ def get_type(tokens, start_index): else: line_type = 'code_lines' i += 1 - if line_type is None: line_type = 'empty_lines' elif i < len(tokens) and tok_type == tokenize.NEWLINE: i += 1 return i, pos[0] - start[0] + 1, line_type - + def register(linter): """ required method to auto register this checker """ linter.register_checker(RawMetricsChecker(linter)) - + diff --git a/checkers/similar.py b/checkers/similar.py index 2b342a8..4660323 100644 --- a/checkers/similar.py +++ b/checkers/similar.py @@ -18,8 +18,6 @@ """ from __future__ import generators -__revision__ = '$Id: similar.py,v 1.14 2006-03-29 08:24:32 syt Exp $' - import sys from logilab.common.compat import set, izip, sum, enumerate @@ -31,7 +29,7 @@ from pylint.checkers import BaseChecker, table_lines_from_stats class Similar: """finds copy-pasted lines of code in a project""" - + def __init__(self, min_lines=4, ignore_comments=False, ignore_docstrings=False): self.min_lines = min_lines @@ -45,11 +43,11 @@ class Similar: stream.readlines(), self.ignore_comments, self.ignore_docstrings)) - + def run(self): """start looking for similarities and display results on stdout""" self._display_sims(self._compute_sims()) - + def _compute_sims(self): """compute similarities in appended files""" no_duplicates = {} @@ -69,12 +67,12 @@ class Similar: sims.sort() sims.reverse() return sims - + def _display_sims(self, sims): """display computed similarities on stdout""" nb_lignes_dupliquees = 0 for num, couples in sims: - print + print print num, "similar lines in", len(couples), "files" couples = list(couples) couples.sort() @@ -117,7 +115,7 @@ class Similar: yield num, lineset1, index1, lineset2, index2 skip = max(skip, num) index1 += skip - + def _iter_sims(self): """iterate on similarities among all files, by making a cartesian product @@ -156,7 +154,7 @@ class LineSet: self._stripped_lines = stripped_lines(lines, ignore_comments, ignore_docstrings) self._index = self._mk_index() - + def __str__(self): return '<Lineset for %s>' % self.name @@ -168,10 +166,10 @@ class LineSet: def __cmp__(self, other): return cmp(self.name, other.name) - + def __hash__(self): return id(self) - + def enumerate_stripped(self, start_at=0): """return an iterator on stripped lines, starting from a given index if specified, else 0 @@ -189,7 +187,7 @@ class LineSet: def find(self, stripped_line): """return positions of the given stripped line in this set""" return self._index.get(stripped_line, ()) - + def _mk_index(self): """create the index for this set""" index = {} @@ -219,7 +217,7 @@ class SimilarChecker(BaseChecker, Similar): memory / CPU intensive, so you should disable it if you experiments some problems. """ - + __implements__ = (IRawChecker,) # configuration section name name = 'similarities' @@ -241,37 +239,37 @@ class SimilarChecker(BaseChecker, Similar): ) # reports reports = ( ('R0801', 'Duplication', report_similarities), ) - + def __init__(self, linter=None): BaseChecker.__init__(self, linter) Similar.__init__(self, min_lines=4, ignore_comments=True, ignore_docstrings=True) self.stats = None - def set_option(self, opt_name, value, action=None, opt_dict=None): + def set_option(self, optname, value, action=None, optdict=None): """method called to set an option (registered in the options list) overridden to report options setting to Similar """ - BaseChecker.set_option(self, opt_name, value, action, opt_dict) - if opt_name == 'min-similarity-lines': + BaseChecker.set_option(self, optname, value, action, optdict) + if optname == 'min-similarity-lines': self.min_lines = self.config.min_similarity_lines - elif opt_name == 'ignore-comments': + elif optname == 'ignore-comments': self.ignore_comments = self.config.ignore_comments - elif opt_name == 'ignore-docstrings': + elif optname == 'ignore-docstrings': self.ignore_docstrings = self.config.ignore_docstrings - + def open(self): """init the checkers: reset linesets and statistics information""" self.linesets = [] self.stats = self.linter.add_stats(nb_duplicated_lines=0, percent_duplicated_lines=0) - + def process_module(self, stream): """process a module - + the module's content is accessible via the stream object - + stream must implements the readlines method """ self.append_stream(self.linter.current_name, stream) @@ -306,7 +304,7 @@ def usage(status=0): print 'Usage: similar [-d|--duplicates min_duplicated_lines] \ [--ignore-comments] file1...' sys.exit(status) - + def run(argv=None): """standalone command line access point""" if argv is None: @@ -34,6 +34,7 @@ import sys import os import re import tokenize +from warnings import warn from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn from logilab.common.optik_ext import check_csv @@ -42,13 +43,14 @@ from logilab.common.interface import implements from logilab.common.textutils import splitstrip from logilab.common.fileutils import norm_open from logilab.common.ureports import Table, Text, Section +from logilab.common.graph import ordered_nodes from logilab.common.__pkginfo__ import version as common_version from logilab.astng import MANAGER, nodes from logilab.astng.__pkginfo__ import version as astng_version -from pylint.utils import UnknownMessage, MessagesHandlerMixIn, \ - ReportsHandlerMixIn, MSG_TYPES, sort_checkers, expand_modules +from pylint.utils import PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn,\ + ReportsHandlerMixIn, MSG_TYPES, expand_modules from pylint.interfaces import ILinter, IRawChecker, IASTNGChecker from pylint.checkers import BaseRawChecker, EmptyReport, \ table_lines_from_stats @@ -67,6 +69,7 @@ REPORTER_OPT_MAP = {'text': TextReporter, 'colorized': ColorizedTextReporter, 'html': HTMLReporter,} + # Python Linter class ######################################################### MSGS = { @@ -112,61 +115,6 @@ MSGS = { } -class PyLintASTWalker(object): - def __init__(self): - # callbacks per node types - self.visit_events = {} - self.leave_events = {} - - def add_checker(self, checker): - hasvdefault = False - vcids = set() - hasldefault = False - lcids = set() - for member in dir(checker): - if member.startswith('visit_'): - cid = member[6:] - if cid != 'default': - cbs = self.visit_events.setdefault(cid, []) - cbs.append(getattr(checker, cid)) - vcids.add(cid) - else: - hasvdefault = getattr(checker, cid) - if member.startswith('leave_'): - if cid != 'default': - cbs = self.leave_events.setdefault(cid, []) - cbs.append(getattr(checker, cid)) - lcids.add(cid) - else: - hasldefault = getattr(checker, cid) - if hasvdefault: - for cls in nodes.ALL_NODE_CLASSES: - cid = cls.__name__.lower() - if cid not in vcids: - cbs = self.visit_events.setdefault(cid, []) - cbs.append(getattr(checker, hasvdefault)) - if hasldefault: - for cls in nodes.ALL_NODE_CLASSES: - cid = cls.__name__.lower() - if cid not in lcids: - cbs = self.leave_events.setdefault(cid, []) - cbs.append(getattr(checker, hasldefault)) - - def walk(self, astng): - """call visit events of astng checkers for the given node, recurse on - its children, then leave events. - """ - cid = node.__class__.__name__.lower() - # generate events for this node on each checker - for cb in self.visit_events.get(cid, ()): - cb(astng) - # recurse on children - for child in astng.get_children(): - self.walk(child) - for cb in self.leave_events.get(cid, ()): - cb(astng) - - class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, BaseRawChecker): """lint Python modules using external checkers. @@ -178,7 +126,7 @@ class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, * handle some basic but necessary stats'data (number of classes, methods...) """ - __implements__ = (ILinter, IRawChecker, IASTNGChecker) + __implements__ = (ILinter, IRawChecker) name = 'master' priority = 0 @@ -199,6 +147,7 @@ should be a base name, not a path. You may set this option multiple times.'}), ('load-plugins', {'type' : 'csv', 'metavar' : '<modules>', 'default' : (), + 'level': 1, 'help' : 'List of plugins (as comma separated values of \ python modules names) to load, usually to register additional checkers.'}), @@ -243,7 +192,7 @@ warning, statement which respectively contain the number of errors / warnings\ ('comment', {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', - 'group': 'Reports', + 'group': 'Reports', 'level': 1, 'help' : 'Add a comment according to your evaluation note. \ This is used by the global evaluation report (R0004).'}), @@ -338,26 +287,31 @@ This is used by the global evaluation report (R0004).'}), self.reporter = reporter reporter.linter = self - def set_option(self, opt_name, value, action=None, opt_dict=None): + def set_option(self, optname, value, action=None, optdict=None): """overridden from configuration.OptionsProviderMixin to handle some special options """ - if opt_name in self._options_methods: + if optname in self._options_methods or optname in self._bw_options_methods: if value: - meth = self._options_methods[opt_name] - value = check_csv(None, opt_name, value) + try: + meth = self._options_methods[optname] + except KeyError: + meth = self._bw_options_methods[optname] + warn('%s is deprecated, replace it by %s' % ( + optname, optname.split('-')[0]), DeprecationWarning) + value = check_csv(None, optname, value) if isinstance(value, (list, tuple)): for _id in value : meth(_id) else : meth(value) - elif opt_name == 'output-format': + elif optname == 'output-format': self.set_reporter(REPORTER_OPT_MAP[value.lower()]()) try: - BaseRawChecker.set_option(self, opt_name, value, action, opt_dict) + BaseRawChecker.set_option(self, optname, value, action, optdict) except UnsupportedAction: print >> sys.stderr, 'option %s can\'t be read from config file' % \ - opt_name + optname # checkers manipulation methods ############################################ @@ -380,10 +334,10 @@ This is used by the global evaluation report (R0004).'}), for msgcat, msgids in self._msgs_by_category.iteritems(): if msgcat == 'E': for msgid in msgids: - self.enable_message(msgid) + self.enable(msgid) else: for msgid in msgids: - self.disable_message(msgid) + self.disable(msgid) # block level option handling ############################################# # @@ -395,12 +349,9 @@ This is used by the global evaluation report (R0004).'}), """ comment = tokenize.COMMENT newline = tokenize.NEWLINE - #line_num = 0 for (tok_type, _, start, _, line) in tokens: if tok_type not in (comment, newline): continue - #if start[0] == line_num: - # continue match = OPTION_RGX.search(line) if match is None: continue @@ -415,9 +366,14 @@ This is used by the global evaluation report (R0004).'}), line=start[0]) continue opt = opt.strip() - #line_num = start[0] - if opt in self._options_methods and not opt.endswith('-report'): - meth = self._options_methods[opt] + if opt in self._options_methods or opt in self._bw_options_methods: + try: + meth = self._options_methods[opt] + except KeyError: + meth = self._bw_options_methods[opt] + warn('%s is deprecated, replace it by %s (%s)' % ( + opt, opt.split('-')[0], self.current_file), + DeprecationWarning) for msgid in splitstrip(value): try: meth(msgid, 'module', start[0]) @@ -433,7 +389,7 @@ This is used by the global evaluation report (R0004).'}), self.collect_block_lines(child, msg_state) first = node.fromlineno last = node.tolineno - # first child line number used to distinguish between disable-msg + # first child line number used to distinguish between disable # which are the first child of scoped node with those defined later. # For instance in the code below: # @@ -473,6 +429,36 @@ This is used by the global evaluation report (R0004).'}), # code checking methods ################################################### + def sort_checkers(self, checkers=None): + if checkers is None: + checkers = [checker for checkers in self._checkers.values() + for checker in checkers] + graph = {} + cls_instance = {} + for checker in checkers: + graph[checker.__class__] = set(checker.needs_checkers) + cls_instance[checker.__class__] = checker + checkers = [cls_instance.get(cls) for cls in ordered_nodes(graph)] + checkers.remove(self) + checkers.insert(0, self) + return checkers + + def _get_checkers(self): + # compute checkers needed according to activated messages and reports + neededcheckers = set() + for checkers in self._checkers.values(): + for checker in checkers: + for msgid in checker.msgs: + if self._msgs_state.get(msgid, True): + neededcheckers.add(checker) + break + else: + for reportid, _, _ in checker.reports: + if self.is_report_enabled(reportid): + neededcheckers.add(checker) + break + return self.sort_checkers(neededcheckers) + def check(self, files_or_modules): """main checking entry: check a list of files or modules from their name. @@ -480,17 +466,11 @@ This is used by the global evaluation report (R0004).'}), self.reporter.include_ids = self.config.include_ids if not isinstance(files_or_modules, (list, tuple)): files_or_modules = (files_or_modules,) + checkers = self._get_checkers() + #print 'checkers', checkers rawcheckers = [] walker = PyLintASTWalker() - # - neededcheckers = set() - for checker in self._checkers.values(): - for msgid in checker.msgs: - if self._msgs_state.get(msgid, True): - neededcheckers.add(checker) - break # notify global begin - checkers = sort_checkers(neededcheckers) for checker in checkers: checker.open() if implements(checker, IASTNGChecker): @@ -702,7 +682,6 @@ def report_messages_by_module_stats(sect, stats, _): sect.append(Table(children=lines, cols=5, rheaders=1)) - # utilities ################################################################### # this may help to import modules using gettext @@ -919,22 +898,22 @@ been issued by analysing pylint output status code self.linter.generate_manpage(__pkginfo__) sys.exit(0) - def cb_help_message(self, option, opt_name, value, parser): + def cb_help_message(self, option, optname, value, parser): """optik callback for printing some help about a particular message""" self.linter.help_message(splitstrip(value)) sys.exit(0) - def cb_full_documentation(self, option, opt_name, value, parser): + def cb_full_documentation(self, option, optname, value, parser): """optik callback for printing full documentation""" self.linter.print_full_documentation() sys.exit(0) - def cb_list_messages(self, option, opt_name, value, parser): # FIXME + def cb_list_messages(self, option, optname, value, parser): # FIXME """optik callback for printing available messages""" - self.linter.list_sorted_messages() + self.linter.list_messages() sys.exit(0) -def cb_init_hook(option, opt_name, value, parser): +def cb_init_hook(option, optname, value, parser): """exec arbitrary code to set sys.path for instance""" exec value @@ -39,14 +39,6 @@ try: except ImportError: scripts = [] try: - from __pkginfo__ import data_files -except ImportError: - data_files = None -try: - from __pkginfo__ import ext_modules -except ImportError: - ext_modules = None -try: from __pkginfo__ import install_requires except ImportError: install_requires = None @@ -123,7 +115,7 @@ def export(from_dir, to_dir, raise walk(from_dir, make_mirror, None) - + def install(**kwargs): """setup entry point""" kwargs['package_dir'] = {modname : '.'} @@ -144,10 +136,8 @@ def install(**kwargs): url = web, classifiers = classifiers, scripts = ensure_scripts(scripts), - data_files = data_files, - ext_modules = ext_modules, **kwargs ) - + if __name__ == '__main__' : install() diff --git a/test/regrtest_data/package/__init__.py b/test/regrtest_data/package/__init__.py index 8d82b43..7041268 100644 --- a/test/regrtest_data/package/__init__.py +++ b/test/regrtest_data/package/__init__.py @@ -8,7 +8,7 @@ __path__ += "folder" class AudioTime(object): """test precedence over the AudioTime submodule""" - + DECIMAL = 3 import subpackage diff --git a/test/smoketest.py b/test/smoketest.py index 0278cf9..b1c2c3a 100644 --- a/test/smoketest.py +++ b/test/smoketest.py @@ -39,68 +39,68 @@ class RunTC(TestCase): finally: sys.stderr = sys.__stderr__ sys.stdout = sys.__stdout__ - + @tag('smoke') def test0(self): """make pylint checking itself""" self._runtest(['pylint.__pkginfo__'], reporter=TextReporter(StringIO()), code=0) - + @tag('smoke') def test1(self): """make pylint checking itself""" self._runtest(['--include-ids=y', 'pylint.lint'], reporter=TextReporter(StringIO())) - + @tag('smoke') def test2(self): """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=ParseableTextReporter(StringIO())) - + @tag('smoke') def test3(self): """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=HTMLReporter(StringIO())) - + @tag('smoke') def test4(self): """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=ColorizedTextReporter(StringIO())) - + @tag('smoke') def test5(self): """make pylint checking itself""" self._runtest(['pylint.lint'], reporter=VSTextReporter(StringIO())) - + @tag('smoke') def test_no_ext_file(self): self._runtest([join(HERE, 'input', 'noext')], code=0) - + @tag('smoke') def test_w0704_ignored(self): self._runtest([join(HERE, 'input', 'ignore_except_pass_by_default.py')], code=0) - + @tag('smoke', 'help', 'config') def test_generate_config_option(self): """make pylint checking itself""" self._runtest(['--generate-rcfile'], reporter=HTMLReporter(StringIO()), code=0) - + @tag('smoke', 'help') def test_help_message_option(self): """make pylint checking itself""" self._runtest(['--help-msg', 'W0101'], reporter=HTMLReporter(StringIO()), code=0) - + @tag('smoke', 'help') def test_error_help_message_option(self): self._runtest(['--help-msg', 'WX101'], reporter=HTMLReporter(StringIO()), code=0) - + @tag('smoke', 'usage') def test_error_missing_arguments(self): self._runtest([], reporter=HTMLReporter(StringIO()), code=32) - - + + if __name__ == '__main__': unittest_main() diff --git a/test/test_func.py b/test/test_func.py index 74cd3d4..2d67824 100644 --- a/test/test_func.py +++ b/test/test_func.py @@ -56,7 +56,7 @@ def exception_str(ex): """function used to replace default __str__ method of exception instances""" return 'in %s\n:: %s' % (ex.file, ', '.join(ex.args)) -class LintTestUsingModule(testlib.TestCase): +class LintTestUsingModule(testlib.TestCase): DEFAULT_PACKAGE = 'input' package = DEFAULT_PACKAGE linter = linter @@ -83,7 +83,7 @@ class LintTestUsingModule(testlib.TestCase): tocheck += [self.package+'.%s' % name.replace('.py', '') for name, file in self.depends] self._test(tocheck) - + def _test(self, tocheck): if INFO_TEST_RGX.match(self.module): self.linter.enable('I') @@ -112,8 +112,8 @@ class LintTestUsingModule(testlib.TestCase): #ex.__str__ = new.instancemethod(exception_str, ex, None) raise AssertionError('%s: %s' % (self.module, ex)), None, sys.exc_info()[-1] -class LintTestUsingFile(LintTestUsingModule): - +class LintTestUsingFile(LintTestUsingModule): + _TEST_TYPE = 'file' def test_functionality(self): @@ -161,7 +161,7 @@ class TestTests(testlib.TestCase): def make_tests(filter_rgx): """generate tests classes from test info - + return the list of generated test classes """ if filter_rgx: @@ -181,7 +181,7 @@ def make_tests(filter_rgx): continue base = module_file.replace('func_', '').replace('.py', '') dependencies = get_tests_info(base, '.py') - + class LintTestUsingModuleTC(LintTestUsingModule): module = module_file.replace('.py', '') output = messages_file @@ -191,14 +191,14 @@ def make_tests(filter_rgx): if MODULES_ONLY: continue - + class LintTestUsingFileTC(LintTestUsingFile): module = module_file.replace('.py', '') output = exists(messages_file + '2') and (messages_file + '2') or messages_file depends = dependencies or None tags = testlib.Tags(('generated', 'pylint_input_%s' % module)) tests.append(LintTestUsingFileTC) - + ## # special test for f0003 ## module_file, messages_file in get_tests_info('func_f0003', '.pyc') ## class LintTestSubclass(LintTest): @@ -206,16 +206,16 @@ def make_tests(filter_rgx): ## output = messages_file ## depends = dependencies or None ## tests.append(LintTestSubclass) - + class LintBuiltinModuleTest(LintTestUsingModule): output = 'messages/builtin_module.txt' module = 'sys' def test_functionality(self): self._test(['sys']) tests.append(LintBuiltinModuleTest) - + if not filter_rgx: - # test all features are tested :) + # test all features are tested :) tests.append(TestTests) return tests @@ -231,8 +231,8 @@ if __name__=='__main__': if '-m' in sys.argv: MODULES_ONLY = True sys.argv.remove('-m') - - if len(sys.argv) > 1: + + if len(sys.argv) > 1: FILTER_RGX = sys.argv[1] del sys.argv[1] testlib.unittest_main(defaultTest='suite') diff --git a/test/test_import_graph.py b/test/test_import_graph.py index 3347515..0c3dec6 100644 --- a/test/test_import_graph.py +++ b/test/test_import_graph.py @@ -15,7 +15,7 @@ class DependenciesGraphTC(unittest.TestCase): dest = 'dependencies_graph.dot' def tearDown(self): os.remove(self.dest) - + def test_dependencies_graph(self): imports.dependencies_graph(self.dest, {'labas': ['hoho', 'yep'], 'hoho': ['yep']}) @@ -33,12 +33,12 @@ URL="." node[shape="box"] "yep" -> "labas" []; } '''.strip()) - + class ImportCheckerTC(unittest.TestCase): def setUp(self): self.linter = l = PyLinter(reporter=TestReporter()) initialize(l) - + def test_checker_dep_graphs(self): l = self.linter l.global_set_option('persistent', False) diff --git a/test/test_regr.py b/test/test_regr.py index dec9908..de5ccb6 100644 --- a/test/test_regr.py +++ b/test/test_regr.py @@ -43,12 +43,12 @@ class NonRegrTC(TestCase): pending messages if a test finished badly """ linter.reporter.finalize() - + def test_package___path___manipulation(self): linter.check('package.__init__') got = linter.reporter.finalize().strip() self.failUnlessEqual(got, '') - + def test_package___init___precedence(self): linter.check('precedence_test') got = linter.reporter.finalize().strip() @@ -134,7 +134,7 @@ class NonRegrTC(TestCase): if fname.endswith('_crash.py'): linter.check(join('regrtest_data', fname)) linter.reporter.finalize().strip() - + def test_try_finally_disable_msg_crash(self): linter.check(join('regrtest_data', 'try_finally_disable_msg_crash')) @@ -144,6 +144,6 @@ class NonRegrTC(TestCase): messages = linter.reporter.finalize().strip() self.failIf('__path__' in messages, messages) - + if __name__ == '__main__': unittest_main() diff --git a/test/unittest_lint.py b/test/unittest_lint.py index 1820a7c..2acd8f4 100644 --- a/test/unittest_lint.py +++ b/test/unittest_lint.py @@ -22,8 +22,9 @@ from cStringIO import StringIO from logilab.common.testlib import TestCase, unittest_main, create_files from logilab.common.compat import sorted -from pylint.config import get_note_message -from pylint.lint import PyLinter, Run, sort_checkers, UnknownMessage + +from pylint import config +from pylint.lint import PyLinter, Run, UnknownMessage from pylint.utils import sort_msgs from pylint import checkers @@ -48,11 +49,11 @@ class GetNoteMessageTC(TestCase): def test(self): msg = None for note in range(-1, 11): - note_msg = get_note_message(note) + note_msg = config.get_note_message(note) self.assertNotEquals(msg, note_msg) msg = note_msg if optimized: - self.assertRaises(AssertionError, get_note_message, 11) + self.assertRaises(AssertionError, config.get_note_message, 11) HERE = abspath(dirname(__file__)) @@ -80,7 +81,7 @@ class PyLinterTC(TestCase): def setUp(self): self.linter = PyLinter() - self.linter.disable_message_category('I') + self.linter.disable('I') self.linter.config.persistent = 0 # register checkers checkers.initialize(self.linter) @@ -97,15 +98,15 @@ class PyLinterTC(TestCase): linter.set_current_module('toto') self.assert_(linter.is_message_enabled('W0101')) self.assert_(linter.is_message_enabled('W0102')) - linter.disable_message('W0101', scope='package') - linter.disable_message('W0102', scope='module', line=1) + linter.disable('W0101', scope='package') + linter.disable('W0102', scope='module', line=1) self.assert_(not linter.is_message_enabled('W0101')) self.assert_(not linter.is_message_enabled('W0102', 1)) linter.set_current_module('tutu') self.assert_(not linter.is_message_enabled('W0101')) self.assert_(linter.is_message_enabled('W0102')) - linter.enable_message('W0101', scope='package') - linter.enable_message('W0102', scope='module', line=1) + linter.enable('W0101', scope='package') + linter.enable('W0102', scope='module', line=1) self.assert_(linter.is_message_enabled('W0101')) self.assert_(linter.is_message_enabled('W0102', 1)) @@ -114,18 +115,20 @@ class PyLinterTC(TestCase): linter.open() linter.set_current_module('toto') self.assert_(linter.is_message_enabled('W0101')) - self.assert_(linter.is_message_enabled('R0102')) - linter.disable_message_category('W', scope='package') - linter.disable_message_category('R', scope='module') + self.assert_(linter.is_message_enabled('C0121')) + linter.disable('W', scope='package') + linter.disable('C', scope='module', line=1) self.assert_(not linter.is_message_enabled('W0101')) - self.assert_(not linter.is_message_enabled('R0102')) + self.assert_(linter.is_message_enabled('C0121')) + self.assert_(not linter.is_message_enabled('C0121', line=1)) linter.set_current_module('tutu') self.assert_(not linter.is_message_enabled('W0101')) - self.assert_(linter.is_message_enabled('R0102')) - linter.enable_message_category('W', scope='package') - linter.enable_message_category('R', scope='module') + self.assert_(linter.is_message_enabled('C0121')) + linter.enable('W', scope='package') + linter.enable('C', scope='module', line=1) self.assert_(linter.is_message_enabled('W0101')) - self.assert_(linter.is_message_enabled('R0102')) + self.assert_(linter.is_message_enabled('C0121')) + self.assert_(linter.is_message_enabled('C0121', line=1)) def test_enable_message_block(self): linter = self.linter @@ -194,41 +197,32 @@ class PyLinterTC(TestCase): pass def test_enable_report(self): - self.assertEquals(self.linter.is_report_enabled('R0001'), True) - self.linter.disable_report('R0001') - self.assertEquals(self.linter.is_report_enabled('R0001'), False) - self.linter.enable_report('R0001') - self.assertEquals(self.linter.is_report_enabled('R0001'), True) + self.assertEquals(self.linter.is_report_enabled('RP0001'), True) + self.linter.disable('RP0001') + self.assertEquals(self.linter.is_report_enabled('RP0001'), False) + self.linter.enable('RP0001') + self.assertEquals(self.linter.is_report_enabled('RP0001'), True) def test_set_option_1(self): linter = self.linter - linter.set_option('disable-msg', 'C0111,W0142') + linter.set_option('disable', 'C0111,W0142') self.assert_(not linter.is_message_enabled('C0111')) self.assert_(not linter.is_message_enabled('W0142')) self.assert_(linter.is_message_enabled('W0113')) def test_set_option_2(self): linter = self.linter - linter.set_option('disable-msg', ('C0111', 'W0142') ) + linter.set_option('disable', ('C0111', 'W0142') ) self.assert_(not linter.is_message_enabled('C0111')) self.assert_(not linter.is_message_enabled('W0142')) self.assert_(linter.is_message_enabled('W0113')) - def test_enable_checkers1(self): - self.linter.enable_checkers(['design'], False) - self.assertEquals(sorted([c.name for c in self.linter._checkers.values() - if c.is_enabled()]), - ['basic', 'classes', 'exceptions', 'format', 'imports', - 'logging', 'master', 'metrics', 'miscellaneous', 'newstyle', - 'similarities', 'string_format', 'typecheck', 'variables']) + def test_enable_checkers(self): + self.linter.disable('design') + self.failIf('design' in set([c.name for c in self.linter._get_checkers()])) + self.linter.enable('design') + self.failUnless('design' in set([c.name for c in self.linter._get_checkers()])) - def test_enable_checkers2(self): - self.linter.enable_checkers(['design'], True) - self.assertEquals(sorted([c.name for c in self.linter._checkers.values() - if c.is_enabled()]), - ['design', 'master']) - -from pylint import config class ConfigTC(TestCase): @@ -20,13 +20,15 @@ main pylint class import sys from os import linesep +from os.path import dirname, basename, splitext, exists, isdir, join, normpath +from logilab.common.modutils import modpath_from_file, get_module_files, \ + file_from_modpath from logilab.common.textutils import normalize_text from logilab.common.configuration import rest_format_section from logilab.common.ureports import Section -from logilab.common.graph import ordered_nodes -from logilab.astng import Module +from logilab.astng import nodes, Module from pylint.checkers import EmptyReport @@ -53,14 +55,6 @@ MSG_TYPES_STATUS = { 'F' : 1 } -def sort_checkers(checkers): - graph = {} - cls_instance = {} - for checker in checkers: - graph[cube.__class__] = set(checker.needs_checkers) - cls_instance[cube.__class__] = checker - return [cls_instance.get(cls) for cls in ordered_nodes(graph)] - def sort_msgs(msgids): """sort message identifiers according to their category first""" msg_order = 'EWRCIF' @@ -331,15 +325,8 @@ class MessagesHandlerMixIn: def list_messages(self): """output full messages list documentation in ReST format""" - for checker in sort_checkers(self._checkers.values()): - if checker.msgs: - self.list_checkers_messages( checker) - print - - def list_sorted_messages(self): - """output full sorted messages list in ReST format""" msgids = [] - for checker in self._checkers.values(): + for checker in self.sort_checkers(): for msgid in checker.msgs.keys(): msgids.append(msgid) msgids.sort() @@ -356,32 +343,32 @@ class ReportsHandlerMixIn: self._reports = {} self._reports_state = {} - def register_report(self, r_id, r_title, r_cb, checker): + def register_report(self, reportid, r_title, r_cb, checker): """register a report - r_id is the unique identifier for the report + reportid is the unique identifier for the report r_title the report's title r_cb the method to call to make the report checker is the checker defining the report """ - r_id = r_id.upper() - self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) ) + reportid = reportid.upper() + self._reports.setdefault(checker, []).append( (reportid, r_title, r_cb) ) - def enable_report(self, r_id): + def enable_report(self, reportid): """disable the report of the given id""" - r_id = r_id.upper() - self._reports_state[r_id] = True + reportid = reportid.upper() + self._reports_state[reportid] = True - def disable_report(self, r_id): + def disable_report(self, reportid): """disable the report of the given id""" - r_id = r_id.upper() - self._reports_state[r_id] = False + reportid = reportid.upper() + self._reports_state[reportid] = False - def is_report_enabled(self, r_id): + def is_report_enabled(self, reportid): """return true if the report associated to the given identifier is enabled """ - return self._reports_state.get(r_id, True) + return self._reports_state.get(reportid, True) def make_reports(self, stats, old_stats): """render registered reports""" @@ -390,18 +377,18 @@ class ReportsHandlerMixIn: self.reporter.set_output(open(filename, 'w')) sect = Section('Report', '%s statements analysed.'% (self.stats['statement'])) - checkers = sort_checkers(self._reports.keys()) + checkers = self.sort_checkers(self._reports) checkers.reverse() for checker in checkers: - for r_id, r_title, r_cb in self._reports[checker]: - if not self.is_report_enabled(r_id): + for reportid, r_title, r_cb in self._reports[checker]: + if not self.is_report_enabled(reportid): continue report_sect = Section(r_title) try: r_cb(report_sect, stats, old_stats) except EmptyReport: continue - report_sect.report_id = r_id + report_sect.report_id = reportid sect.append(report_sect) self.reporter.display_results(sect) @@ -416,11 +403,6 @@ class ReportsHandlerMixIn: self.stats[key] = value return self.stats -### ### - - - - import utils - - - - ### ### - -from os.path import dirname, basename, splitext, exists, isdir, join, normpath -from logilab.common.modutils import modpath_from_file, get_module_files, \ - file_from_modpath def expand_modules(files_or_modules, black_list): """take a list of files/modules/packages and return the list of tuple @@ -462,3 +444,59 @@ def expand_modules(files_or_modules, black_list): result.append( {'path': subfilepath, 'name': submodname, 'basepath': filepath, 'basename': modname} ) return result, errors + + +class PyLintASTWalker(object): + def __init__(self): + # callbacks per node types + self.visit_events = {} + self.leave_events = {} + + def add_checker(self, checker): + hasvdefault = False + vcids = set() + hasldefault = False + lcids = set() + for member in dir(checker): + if member.startswith('visit_'): + cid = member[6:] + if cid != 'default': + cbs = self.visit_events.setdefault(cid, []) + cbs.append(getattr(checker, member)) + vcids.add(cid) + else: + hasvdefault = getattr(checker, member) + elif member.startswith('leave_'): + cid = member[6:] + if cid != 'default': + cbs = self.leave_events.setdefault(cid, []) + cbs.append(getattr(checker, member)) + lcids.add(cid) + else: + hasldefault = getattr(checker, member) + if hasvdefault: + for cls in nodes.ALL_NODE_CLASSES: + cid = cls.__name__.lower() + if cid not in vcids: + cbs = self.visit_events.setdefault(cid, []) + cbs.append(hasvdefault) + if hasldefault: + for cls in nodes.ALL_NODE_CLASSES: + cid = cls.__name__.lower() + if cid not in lcids: + cbs = self.leave_events.setdefault(cid, []) + cbs.append(hasldefault) + + def walk(self, astng): + """call visit events of astng checkers for the given node, recurse on + its children, then leave events. + """ + cid = astng.__class__.__name__.lower() + # generate events for this node on each checker + for cb in self.visit_events.get(cid, ()): + cb(astng) + # recurse on children + for child in astng.get_children(): + self.walk(child) + for cb in self.leave_events.get(cid, ()): + cb(astng) |