diff options
44 files changed, 333 insertions, 537 deletions
@@ -7,6 +7,10 @@ ChangeLog for Pylint comprehensions, etc), and 'not-a-mapping', emitted when non-mapping value is used in a mapping context. Closes issue #563. + * Make 'no-self-use' checker not emit a warning if there is a 'super()' + call inside the method. + Closes issue #667. + * Add checker to identify multiple imports on one line. Closes issue #598. @@ -332,6 +336,14 @@ ChangeLog for Pylint * Don't emit 'assigning-non-slot' for descriptors. Closes issue #652. + * Add a new error, 'repeated-keyword', when a keyword argument is passed + multiple times into a function call. + + This is similar with redundant-keyword-arg, but it's mildly different + that it needs to be a separate error. + + * --enable=all can now be used. Closes issue #142. + 2015-03-14 -- 1.4.3 diff --git a/pylint/checkers/base.py b/pylint/checkers/base.py index 30e337b..927e77b 100644 --- a/pylint/checkers/base.py +++ b/pylint/checkers/base.py @@ -64,6 +64,7 @@ REVERSED_METHODS = (SEQUENCE_PROTOCOL_METHODS, PY33 = sys.version_info >= (3, 3) PY3K = sys.version_info >= (3, 0) +PY35 = sys.version_info >= (3, 5) BAD_FUNCTIONS = ['map', 'filter'] if sys.version_info < (3, 0): BAD_FUNCTIONS.append('input') @@ -357,6 +358,11 @@ class BasicErrorChecker(_BasicChecker): # f(*args) is converted to Call(args=[Starred]), so ignore # them for this check. return + if PY35 and isinstance(node.parent, + (astroid.List, astroid.Tuple, + astroid.Set, astroid.Dict)): + # PEP 448 unpacking. + return stmt = node.statement() if not isinstance(stmt, astroid.Assign): @@ -1194,7 +1200,7 @@ class NameChecker(_BasicChecker): def visit_classdef(self, node): self._check_name('class', node.name, node) for attr, anodes in six.iteritems(node.instance_attrs): - if not list(node.instance_attr_ancestors(attr)): + if not any(node.instance_attr_ancestors(attr)): self._check_name('attr', attr, anodes[0]) @check_messages('blacklisted-name', 'invalid-name') diff --git a/pylint/checkers/classes.py b/pylint/checkers/classes.py index 0fc0fae..c39b971 100644 --- a/pylint/checkers/classes.py +++ b/pylint/checkers/classes.py @@ -140,6 +140,14 @@ def _is_attribute_property(name, klass): return True return False +def _has_bare_super_call(fundef_node): + for call in fundef_node.nodes_of_class(astroid.Call): + func = call.func + if (isinstance(func, astroid.Name) and + func.name == 'super' and + not call.args): + return True + return False MSGS = { 'F0202': ('Unable to check methods signature (%s / %s)', @@ -545,7 +553,8 @@ a metaclass class method.'} and not node.name in PYMETHODS and not (node.is_abstract() or overrides_a_method(class_node, node.name) or - decorated_with_property(node))): + decorated_with_property(node) or + (six.PY3 and _has_bare_super_call(node)))): self.add_message('no-self-use', node=node) def visit_attribute(self, node): diff --git a/pylint/checkers/spelling.py b/pylint/checkers/spelling.py index a5b7857..725f25a 100644 --- a/pylint/checkers/spelling.py +++ b/pylint/checkers/spelling.py @@ -21,6 +21,8 @@ import tokenize import string import re +import six + if sys.version_info[0] >= 3: maketrans = str.maketrans else: @@ -244,6 +246,10 @@ class SpellingChecker(BaseTokenChecker): return start_line = node.lineno + 1 + if six.PY2: + encoding = node.root().file_encoding + docstring = docstring.decode(encoding or sys.getdefaultencoding(), + 'replace') # Go through lines of docstring for idx, line in enumerate(docstring.splitlines()): diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index 5095d72..80b51b5 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -24,10 +24,11 @@ import sys import astroid import astroid.context -from astroid import bases +import astroid.arguments from astroid import exceptions from astroid import objects from astroid import helpers +from astroid import node_classes import six from pylint.interfaces import IAstroidChecker, INFERENCE, INFERENCE_FAILURE @@ -55,7 +56,7 @@ def _unflatten(iterable): not isinstance(elem, six.string_types)): for subelem in _unflatten(elem): yield subelem - elif isinstance(elem, bases.NodeNG): + elif isinstance(elem, node_classes.NodeNG): yield elem @@ -185,6 +186,9 @@ MSGS = { 'unsupported-binary-operation', 'Emitted when a binary arithmetic operation between two ' 'operands is not supported.'), + 'E1132': ('Got multiple values for keyword argument %r in function call', + 'repeated-keyword', + 'Emitted when a function call got multiple values for a keyword.'), } # builtin sequence types in Python 2 and 3. @@ -500,9 +504,9 @@ accessed. Python regular expressions are accepted.'} """ # Build the set of keyword arguments, checking for duplicate keywords, # and count the positional arguments. - keyword_args = {keyword.arg for keyword in node.keywords or [] - if keyword.arg is not None} - num_positional_args = len(node.args) + call_site = astroid.arguments.CallSite.from_call(node) + num_positional_args = len(call_site.positional_arguments) + keyword_args = list(call_site.keyword_arguments.keys()) called = helpers.safe_infer(node.func) # only function, generator and object defining __call__ are allowed @@ -524,8 +528,17 @@ accessed. Python regular expressions are accepted.'} return if len(called.argnames()) != len(set(called.argnames())): - # Duplicate parameter name (see E9801). We can't really make sense - # of the function call in this case, so just return. + # Duplicate parameter name (see duplicate-argument). We can't really + # make sense of the function call in this case, so just return. + return + + # Warn about duplicated keyword arguments, such as `f=24, **{'f': 24}` + for keyword in call_site.duplicated_keywords: + self.add_message('repeated-keyword', + node=node, args=(keyword, )) + + if call_site.has_invalid_arguments() or call_site.has_invalid_keywords(): + # Can't make sense of this. return # Analyze the list of formal parameters. @@ -606,23 +619,7 @@ accessed. Python regular expressions are accepted.'} self.add_message('unexpected-keyword-arg', node=node, args=(keyword, callable_name)) - # 3. Match the *args, if any. Note that Python actually processes - # *args _before_ any keyword arguments, but we wait until after - # looking at the keyword arguments so as to make a more conservative - # guess at how many values are in the *args sequence. - if node.starargs: - for i in range(num_positional_args, len(parameters)): - [(name, defval), assigned] = parameters[i] - # Assume that *args provides just enough values for all - # non-default parameters after the last parameter assigned by - # the positional arguments but before the first parameter - # assigned by the keyword arguments. This is the best we can - # get without generating any false positives. - if (defval is not None) or assigned: - break - parameters[i][1] = True - - # 4. Match the **kwargs, if any. + # 3. Match the **kwargs, if any. if node.kwargs: for i, [(name, defval), assigned] in enumerate(parameters): # Assume that *kwargs provides values for all remaining diff --git a/pylint/reporters/__init__.py b/pylint/reporters/__init__.py index 5c4437a..664c62a 100644 --- a/pylint/reporters/__init__.py +++ b/pylint/reporters/__init__.py @@ -47,12 +47,9 @@ class BaseReporter(object): def __init__(self, output=None): self.linter = None - # self.include_ids = None # Deprecated - # self.symbols = None # Deprecated self.section = 0 self.out = None self.out_encoding = None - self.encode = None self.set_output(output) # Build the path prefix to strip to get relative paths self.path_strip_prefix = os.getcwd() + os.sep @@ -75,12 +72,11 @@ class BaseReporter(object): def set_output(self, output=None): """set output stream""" self.out = output or sys.stdout - # py3k streams handle their encoding : - if sys.version_info >= (3, 0): - self.encode = lambda x: x - return - def encode(string): + if six.PY3: + encode = lambda self, string: string + else: + def encode(self, string): if not isinstance(string, six.text_type): return string encoding = (getattr(self.out, 'encoding', None) or @@ -90,7 +86,6 @@ class BaseReporter(object): # source code line that can't be encoded with the current locale # settings return string.encode(encoding, 'replace') - self.encode = encode def writeln(self, string=''): """write a line in the output buffer""" @@ -110,12 +105,10 @@ class BaseReporter(object): # Event callbacks def on_set_current_module(self, module, filepath): - """starting analyzis of a module""" - pass + """Hook called when a module starts to be analysed.""" def on_close(self, stats, previous_stats): - """global end of analyzis""" - pass + """Hook called when a module finished analyzing.""" class CollectingReporter(BaseReporter): diff --git a/pylint/reporters/html.py b/pylint/reporters/html.py index b2214b1..b06ee16 100644 --- a/pylint/reporters/html.py +++ b/pylint/reporters/html.py @@ -17,6 +17,8 @@ import itertools import string import sys +import six + from pylint.interfaces import IReporter from pylint.reporters import BaseReporter from pylint.reporters.ureports.html_writer import HTMLWriter @@ -67,7 +69,9 @@ class HTMLReporter(BaseReporter): self._parse_template() # We want to add the lines given by the template - self.msgs += [str(getattr(msg, field)) for field in self.msgargs] + values = [getattr(msg, field) for field in self.msgargs] + self.msgs += [value if isinstance(value, six.text_type) else str(value) + for value in values] def set_output(self, output=None): """set output stream diff --git a/pylint/reporters/ureports/__init__.py b/pylint/reporters/ureports/__init__.py index 0da4051..02322db 100644 --- a/pylint/reporters/ureports/__init__.py +++ b/pylint/reporters/ureports/__init__.py @@ -26,8 +26,6 @@ import sys import six -# pylint: disable=method-hidden; Weird API in compute_content. - class BaseWriter(object): """base class for ureport writers""" @@ -43,7 +41,6 @@ class BaseWriter(object): if not encoding: encoding = getattr(stream, 'encoding', 'UTF-8') self.encoding = encoding or 'UTF-8' - self.__compute_funcs = [] self.out = stream self.begin_format() layout.accept(self) @@ -62,10 +59,7 @@ class BaseWriter(object): def write(self, string): """write a string in the output buffer""" - try: - self.out.write(string) - except UnicodeEncodeError: - self.out.write(string.encode(self.encoding)) + self.out.write(string) def begin_format(self): """begin to format a layout""" @@ -98,27 +92,15 @@ class BaseWriter(object): return an iterator on strings (one for each child element) """ - # use cells ! - def write(data): - try: - stream.write(data) - except UnicodeEncodeError: - stream.write(data.encode(self.encoding)) - def writeln(data=u''): - try: - stream.write(data + os.linesep) - except UnicodeEncodeError: - stream.write(data.encode(self.encoding) + os.linesep) - self.write = write - self.writeln = writeln - self.__compute_funcs.append((write, writeln)) - for child in layout.children: - stream = six.StringIO() - child.accept(self) - yield stream.getvalue() - self.__compute_funcs.pop() + # Patch the underlying output stream with a fresh-generated stream, + # which is used to store a temporary representation of a child + # node. + out = self.out try: - self.write, self.writeln = self.__compute_funcs[-1] - except IndexError: - del self.write - del self.writeln + for child in layout.children: + stream = six.StringIO() + self.out = stream + child.accept(self) + yield stream.getvalue() + finally: + self.out = out diff --git a/pylint/reporters/ureports/html_writer.py b/pylint/reporters/ureports/html_writer.py index 005ac62..c5f74d3 100644 --- a/pylint/reporters/ureports/html_writer.py +++ b/pylint/reporters/ureports/html_writer.py @@ -27,18 +27,6 @@ class HTMLWriter(BaseWriter): super(HTMLWriter, self).__init__() self.snippet = snippet - @staticmethod - def handle_attrs(layout): - """get an attribute string from layout member attributes""" - attrs = u'' - klass = getattr(layout, 'klass', None) - if klass: - attrs += u' class="%s"' % klass - nid = getattr(layout, 'id', None) - if nid: - attrs += u' id="%s"' % nid - return attrs - def begin_format(self): """begin to format a layout""" super(HTMLWriter, self).begin_format() @@ -55,20 +43,20 @@ class HTMLWriter(BaseWriter): def visit_section(self, layout): """display a section as html, using div + h[section level]""" self.section += 1 - self.writeln(u'<div%s>' % self.handle_attrs(layout)) + self.writeln(u'<div>') self.format_children(layout) self.writeln(u'</div>') self.section -= 1 def visit_title(self, layout): """display a title using <hX>""" - self.write(u'<h%s%s>' % (self.section, self.handle_attrs(layout))) + self.write(u'<h%s>' % self.section) self.format_children(layout) self.writeln(u'</h%s>' % self.section) def visit_table(self, layout): """display a table as html""" - self.writeln(u'<table%s>' % self.handle_attrs(layout)) + self.writeln(u'<table>') table_content = self.get_table_content(layout) for i, row in enumerate(table_content): if i == 0 and layout.rheaders: @@ -76,7 +64,7 @@ class HTMLWriter(BaseWriter): elif i+1 == len(table_content) and layout.rrheaders: self.writeln(u'<tr class="header">') else: - self.writeln(u'<tr class="%s">' % (i%2 and 'even' or 'odd')) + self.writeln(u'<tr class="%s">' % (u'even' if i % 2 else u'odd')) for j, cell in enumerate(row): cell = cell or u' ' if (layout.rheaders and i == 0) or \ @@ -89,30 +77,12 @@ class HTMLWriter(BaseWriter): self.writeln(u'</tr>') self.writeln(u'</table>') - def visit_list(self, layout): - """display a list as html""" - self.writeln(u'<ul%s>' % self.handle_attrs(layout)) - for row in list(self.compute_content(layout)): - self.writeln(u'<li>%s</li>' % row) - self.writeln(u'</ul>') - def visit_paragraph(self, layout): """display links (using <p>)""" self.write(u'<p>') self.format_children(layout) self.write(u'</p>') - def visit_span(self, layout): - """display links (using <p>)""" - self.write(u'<span%s>' % self.handle_attrs(layout)) - self.format_children(layout) - self.write(u'</span>') - - def visit_link(self, layout): - """display links (using <a>)""" - self.write(u' <a href="%s"%s>%s</a>' % (layout.url, - self.handle_attrs(layout), - layout.label)) def visit_verbatimtext(self, layout): """display verbatim text (using <pre>)""" self.write(u'<pre>') diff --git a/pylint/reporters/ureports/nodes.py b/pylint/reporters/ureports/nodes.py index 01cbcb7..104ba83 100644 --- a/pylint/reporters/ureports/nodes.py +++ b/pylint/reporters/ureports/nodes.py @@ -22,32 +22,58 @@ A micro report is a tree of layout and content objects. from six import string_types -from pylint.reporters.ureports.tree import VNode +class VNode(object): -class BaseComponent(VNode): - """base report component + def __init__(self, nid=None): + self.id = nid + # navigation + self.parent = None + self.children = [] - attributes - * id : the component's optional id - * klass : the component's optional klass - """ - def __init__(self, id=None, klass=None): - super(BaseComponent, self).__init__(id) - self.klass = klass + def __iter__(self): + return iter(self.children) - -class BaseLayout(BaseComponent): + def append(self, child): + """add a node to children""" + self.children.append(child) + child.parent = self + + def insert(self, index, child): + """insert a child node""" + self.children.insert(index, child) + child.parent = self + + def _get_visit_name(self): + """ + return the visit name for the mixed class. When calling 'accept', the + method <'visit_' + name returned by this method> will be called on the + visitor + """ + try: + return self.TYPE.replace('-', '_') + except Exception: + return self.__class__.__name__.lower() + + def accept(self, visitor, *args, **kwargs): + func = getattr(visitor, 'visit_%s' % self._get_visit_name()) + return func(self, *args, **kwargs) + + def leave(self, visitor, *args, **kwargs): + func = getattr(visitor, 'leave_%s' % self._get_visit_name()) + return func(self, *args, **kwargs) + + +class BaseLayout(VNode): """base container node attributes - * BaseComponent attributes * children : components in this table (i.e. the table's cells) """ def __init__(self, children=(), **kwargs): super(BaseLayout, self).__init__(**kwargs) for child in children: - if isinstance(child, BaseComponent): + if isinstance(child, VNode): self.append(child) else: self.add_text(child) @@ -71,11 +97,10 @@ class BaseLayout(BaseComponent): # non container nodes ######################################################### -class Text(BaseComponent): +class Text(VNode): """a text portion attributes : - * BaseComponent attributes * data : the text value as an encoded or unicode string """ def __init__(self, data, escaped=True, **kwargs): @@ -91,26 +116,9 @@ class VerbatimText(Text): """a verbatim text, display the raw data attributes : - * BaseComponent attributes * data : the text value as an encoded or unicode string """ - -class Link(BaseComponent): - """a labelled link - - attributes : - * BaseComponent attributes - * url : the link's target (REQUIRED) - * label : the link's label as a string (use the url by default) - """ - def __init__(self, url, label=None, **kwargs): - super(Link, self).__init__(**kwargs) - assert url - self.url = url - self.label = label or url - - # container nodes ############################################################# class Section(BaseLayout): @@ -173,11 +181,3 @@ class Table(BaseLayout): self.cheaders = cheaders self.rrheaders = rrheaders self.rcheaders = rcheaders - - -class List(BaseLayout): - """some list data - - attributes : - * BaseLayout attributes - """ diff --git a/pylint/reporters/ureports/text_writer.py b/pylint/reporters/ureports/text_writer.py index 545f999..6109b95 100644 --- a/pylint/reporters/ureports/text_writer.py +++ b/pylint/reporters/ureports/text_writer.py @@ -19,10 +19,6 @@ from __future__ import print_function -import os - -from six.moves import range - from pylint.reporters.ureports import BaseWriter @@ -36,7 +32,6 @@ class TextWriter(BaseWriter): def begin_format(self): super(TextWriter, self).begin_format() self.list_level = 0 - self.pending_urls = [] def visit_section(self, layout): """display a section as text @@ -44,11 +39,6 @@ class TextWriter(BaseWriter): self.section += 1 self.writeln() self.format_children(layout) - if self.pending_urls: - self.writeln() - for label, url in self.pending_urls: - self.writeln(u'.. _`%s`: %s' % (label, url)) - self.pending_urls = [] self.section -= 1 self.writeln() @@ -65,23 +55,15 @@ class TextWriter(BaseWriter): self.format_children(layout) self.writeln() - def visit_span(self, layout): - """enter a span""" - self.format_children(layout) - def visit_table(self, layout): """display a table as text""" table_content = self.get_table_content(layout) # get columns width cols_width = [0]*len(table_content[0]) for row in table_content: - for index in range(len(row)): - col = row[index] + for index, col in enumerate(row): cols_width[index] = max(cols_width[index], len(col)) - if layout.klass == 'field': - self.field_table(layout, table_content, cols_width) - else: - self.default_table(layout, table_content, cols_width) + self.default_table(layout, table_content, cols_width) self.writeln() def default_table(self, layout, table_content, cols_width): @@ -89,47 +71,21 @@ class TextWriter(BaseWriter): cols_width = [size+1 for size in cols_width] format_strings = u' '.join([u'%%-%ss'] * len(cols_width)) format_strings = format_strings % tuple(cols_width) - format_strings = format_strings.split(' ') + format_strings = format_strings.split(u' ') table_linesep = u'\n+' + u'+'.join([u'-'*w for w in cols_width]) + u'+\n' headsep = u'\n+' + u'+'.join([u'='*w for w in cols_width]) + u'+\n' # FIXME: layout.cheaders self.write(table_linesep) - for i in range(len(table_content)): + for index, line in enumerate(table_content): self.write(u'|') - line = table_content[i] - for j in range(len(line)): - self.write(format_strings[j] % line[j]) + for line_index, at_index in enumerate(line): + self.write(format_strings[line_index] % at_index) self.write(u'|') - if i == 0 and layout.rheaders: + if index == 0 and layout.rheaders: self.write(headsep) else: self.write(table_linesep) - def field_table(self, layout, table_content, cols_width): - """special case for field table""" - assert layout.cols == 2 - format_string = u'%s%%-%ss: %%s' % (os.linesep, cols_width[0]) - for field, value in table_content: - self.write(format_string % (field, value)) - - def visit_list(self, layout): - """display a list layout as text""" - bullet = BULLETS[self.list_level % len(BULLETS)] - indent = ' ' * self.list_level - self.list_level += 1 - for child in layout.children: - self.write(u'%s%s%s ' % (os.linesep, indent, bullet)) - child.accept(self) - self.list_level -= 1 - - def visit_link(self, layout): - """add a hyperlink""" - if layout.label != layout.url: - self.write(u'`%s`_' % layout.label) - self.pending_urls.append((layout.label, layout.url)) - else: - self.write(layout.url) - def visit_verbatimtext(self, layout): """display a verbatim layout as text (so difficult ;) """ diff --git a/pylint/reporters/ureports/tree.py b/pylint/reporters/ureports/tree.py deleted file mode 100644 index 99965f2..0000000 --- a/pylint/reporters/ureports/tree.py +++ /dev/null @@ -1,235 +0,0 @@ -# copyright 2003-2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of pylint. -# -# logilab-common is free software: you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) any -# later version. -# -# pylint is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with pylint. If not, see <http://www.gnu.org/licenses/>. - - -class NodeNotFound(Exception): - """raised when a node has not been found""" - -EX_SIBLING_NOT_FOUND = "No such sibling as '%s'" -EX_CHILD_NOT_FOUND = "No such child as '%s'" -EX_NODE_NOT_FOUND = "No such node as '%s'" - - -# Base node ################################################################### - -class Node(object): - """a basic tree node, characterized by an id""" - - def __init__(self, nid=None): - self.id = nid - # navigation - self.parent = None - self.children = [] - - def __iter__(self): - return iter(self.children) - - def __str__(self, indent=0): - s = ['%s%s %s' % (' '*indent, self.__class__.__name__, self.id)] - indent += 2 - for child in self.children: - try: - s.append(child.__str__(indent)) - except TypeError: - s.append(child.__str__()) - return '\n'.join(s) - - def is_leaf(self): - return not self.children - - def append(self, child): - """add a node to children""" - self.children.append(child) - child.parent = self - - def remove(self, child): - """remove a child node""" - self.children.remove(child) - child.parent = None - - def insert(self, index, child): - """insert a child node""" - self.children.insert(index, child) - child.parent = self - - def replace(self, old_child, new_child): - """replace a child node with another""" - i = self.children.index(old_child) - self.children.pop(i) - self.children.insert(i, new_child) - new_child.parent = self - - def get_sibling(self, nid): - """return the sibling node that has given id""" - try: - return self.parent.get_child_by_id(nid) - except NodeNotFound: - raise NodeNotFound(EX_SIBLING_NOT_FOUND % nid) - - def next_sibling(self): - """ - return the next sibling for this node if any - """ - parent = self.parent - if parent is None: - # root node has no sibling - return None - index = parent.children.index(self) - try: - return parent.children[index+1] - except IndexError: - return None - - def previous_sibling(self): - """ - return the previous sibling for this node if any - """ - parent = self.parent - if parent is None: - # root node has no sibling - return None - index = parent.children.index(self) - if index > 0: - return parent.children[index-1] - return None - - def get_node_by_id(self, nid): - """ - return node in whole hierarchy that has given id - """ - root = self.root() - try: - return root.get_child_by_id(nid, 1) - except NodeNotFound: - raise NodeNotFound(EX_NODE_NOT_FOUND % nid) - - def get_child_by_id(self, nid, recurse=None): - """ - return child of given id - """ - if self.id == nid: - return self - for c in self.children: - if recurse: - try: - return c.get_child_by_id(nid, 1) - except NodeNotFound: - continue - if c.id == nid: - return c - raise NodeNotFound(EX_CHILD_NOT_FOUND % nid) - - def get_child_by_path(self, path): - """ - return child of given path (path is a list of ids) - """ - if len(path) > 0 and path[0] == self.id: - if len(path) == 1: - return self - else: - for c in self.children: - try: - return c.get_child_by_path(path[1:]) - except NodeNotFound: - pass - raise NodeNotFound(EX_CHILD_NOT_FOUND % path) - - def depth(self): - """ - return depth of this node in the tree - """ - if self.parent is not None: - return 1 + self.parent.depth() - else: - return 0 - - def depth_down(self): - """ - return depth of the tree from this node - """ - if self.children: - return 1 + max([c.depth_down() for c in self.children]) - return 1 - - def width(self): - """ - return the width of the tree from this node - """ - return len(self.leaves()) - - def root(self): - """ - return the root node of the tree - """ - if self.parent is not None: - return self.parent.root() - return self - - def leaves(self): - """ - return a list with all the leaves nodes descendant from this node - """ - leaves = [] - if self.children: - for child in self.children: - leaves += child.leaves() - return leaves - else: - return [self] - - def flatten(self, _list=None): - """ - return a list with all the nodes descendant from this node - """ - if _list is None: - _list = [] - _list.append(self) - for c in self.children: - c.flatten(_list) - return _list - - def lineage(self): - """ - return list of parents up to root node - """ - lst = [self] - if self.parent is not None: - lst.extend(self.parent.lineage()) - return lst - - -class VNode(Node): - - def get_visit_name(self): - """ - return the visit name for the mixed class. When calling 'accept', the - method <'visit_' + name returned by this method> will be called on the - visitor - """ - try: - return self.TYPE.replace('-', '_') - except Exception: - return self.__class__.__name__.lower() - - def accept(self, visitor, *args, **kwargs): - func = getattr(visitor, 'visit_%s' % self.get_visit_name()) - return func(self, *args, **kwargs) - - def leave(self, visitor, *args, **kwargs): - func = getattr(visitor, 'leave_%s' % self.get_visit_name()) - return func(self, *args, **kwargs) diff --git a/pylint/test/functional/arguments.py b/pylint/test/functional/arguments.py index f8db22c..9f0dc63 100644 --- a/pylint/test/functional/arguments.py +++ b/pylint/test/functional/arguments.py @@ -1,4 +1,4 @@ -# pylint: disable=too-few-public-methods, no-absolute-import,missing-docstring +# pylint: disable=too-few-public-methods, no-absolute-import,missing-docstring,import-error """Test function argument checker""" def decorator(fun): @@ -142,3 +142,15 @@ class Issue642(object): attr = 0 def __str__(self): return "{self.attr}".format(self=self) + +# These should not emit anything regarding the number of arguments, +# since they have something invalid. +from ala_bala_portocola import unknown + +function_1_arg(*unknown) +function_1_arg(1, *2) +function_1_arg(1, 2, 3, **unknown) +function_1_arg(4, 5, **1) +function_1_arg(5, 6, **{unknown: 1}) +function_1_arg(**{object: 1}) +function_1_arg(**{1: 2}) diff --git a/pylint/test/input/func_w0312.py b/pylint/test/functional/mixed_indentation.py index 491f75b..724ecff 100644 --- a/pylint/test/input/func_w0312.py +++ b/pylint/test/functional/mixed_indentation.py @@ -1,11 +1,10 @@ """test mixed tabs and spaces""" from __future__ import print_function -__revision__ = 1 def spaces_func(): """yo""" print("yo") def tab_func(): - """yo""" - print("yo") + """yo""" # [mixed-indentation] + print("yo") # [mixed-indentation] diff --git a/pylint/test/functional/mixed_indentation.txt b/pylint/test/functional/mixed_indentation.txt new file mode 100644 index 0000000..d4857e6 --- /dev/null +++ b/pylint/test/functional/mixed_indentation.txt @@ -0,0 +1,2 @@ +mixed-indentation:9::Found indentation with tabs instead of spaces +mixed-indentation:10::Found indentation with tabs instead of spaces diff --git a/pylint/test/functional/no_self_use_py3.py b/pylint/test/functional/no_self_use_py3.py new file mode 100644 index 0000000..f401508 --- /dev/null +++ b/pylint/test/functional/no_self_use_py3.py @@ -0,0 +1,12 @@ +# pylint: disable=missing-docstring,no-init,unused-argument,invalid-name,too-few-public-methods + +class A: + def __init__(self): + self.store = {} + + def get(self, key, default=None): + return self.store.get(key, default) + +class B(A): + def get_memo(self, obj): + return super().get(obj) diff --git a/pylint/test/functional/no_self_use_py3.rc b/pylint/test/functional/no_self_use_py3.rc new file mode 100644 index 0000000..a2ab06c --- /dev/null +++ b/pylint/test/functional/no_self_use_py3.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.0
\ No newline at end of file diff --git a/pylint/test/functional/no_self_use_py3.txt b/pylint/test/functional/no_self_use_py3.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/pylint/test/functional/no_self_use_py3.txt @@ -0,0 +1 @@ + diff --git a/pylint/test/input/func_typecheck_non_callable_call.py b/pylint/test/functional/not_callable.py index 832657d..c2a3ab4 100644 --- a/pylint/test/input/func_typecheck_non_callable_call.py +++ b/pylint/test/functional/not_callable.py @@ -1,19 +1,13 @@ -# pylint: disable=R0903,missing-docstring,no-self-use -""" - 'E1102': ('%s is not callable', - 'Used when an object being called has been infered to a non \ - callable object'), -""" +# pylint: disable=missing-docstring,no-self-use,too-few-public-methods -__revision__ = None +REVISION = None -__revision__() +REVISION() # [not-callable] def correct(): - """callable object""" return 1 -__revision__ = correct() +REVISION = correct() class Correct(object): """callable object""" @@ -26,15 +20,15 @@ class MetaCorrect(object): INSTANCE = Correct() CALLABLE_INSTANCE = MetaCorrect() CORRECT = CALLABLE_INSTANCE() -INCORRECT = INSTANCE() +INCORRECT = INSTANCE() # [not-callable] LIST = [] -INCORRECT = LIST() +INCORRECT = LIST() # [not-callable] DICT = {} -INCORRECT = DICT() +INCORRECT = DICT() # [not-callable] TUPLE = () -INCORRECT = TUPLE() +INCORRECT = TUPLE() # [not-callable] INT = 1 -INCORRECT = INT() +INCORRECT = INT() # [not-callable] # Test calling properties. Pylint can detect when using only the # getter, but it doesn't infer properly when having a getter @@ -69,8 +63,8 @@ class PropertyTest(object): self.attr = value PROP = PropertyTest() -PROP.test(40) -PROP.custom() +PROP.test(40) # [not-callable] +PROP.custom() # [not-callable] # Safe from not-callable when using properties. diff --git a/pylint/test/functional/not_callable.txt b/pylint/test/functional/not_callable.txt new file mode 100644 index 0000000..4928c8c --- /dev/null +++ b/pylint/test/functional/not_callable.txt @@ -0,0 +1,8 @@ +not-callable:5::REVISION is not callable +not-callable:23::INSTANCE is not callable +not-callable:25::LIST is not callable +not-callable:27::DICT is not callable +not-callable:29::TUPLE is not callable +not-callable:31::INT is not callable +not-callable:66::PROP.test is not callable +not-callable:67::PROP.custom is not callable
\ No newline at end of file diff --git a/pylint/test/functional/repeated_keyword.py b/pylint/test/functional/repeated_keyword.py new file mode 100644 index 0000000..786c53b --- /dev/null +++ b/pylint/test/functional/repeated_keyword.py @@ -0,0 +1,13 @@ +"""Check that a keyword is not repeated in a function call
+
+This is somehow related to redundant-keyword, but it's not the same.
+"""
+
+# pylint: disable=missing-docstring, invalid-name
+
+def test(a, b):
+ return a, b
+
+test(1, 24)
+test(1, b=24, **{})
+test(1, b=24, **{'b': 24}) # [repeated-keyword]
diff --git a/pylint/test/functional/repeated_keyword.txt b/pylint/test/functional/repeated_keyword.txt new file mode 100644 index 0000000..1344b15 --- /dev/null +++ b/pylint/test/functional/repeated_keyword.txt @@ -0,0 +1 @@ +repeated-keyword:13::"Got multiple values for keyword argument 'b' in function call"
\ No newline at end of file diff --git a/pylint/test/input/func_return_outside_func.py b/pylint/test/functional/return_outside_function.py index 440798d..449dafd 100644 --- a/pylint/test/input/func_return_outside_func.py +++ b/pylint/test/functional/return_outside_function.py @@ -1,3 +1,2 @@ """This is gramatically correct, but it's still a SyntaxError""" -__revision__ = None -return +return # [return-outside-function] diff --git a/pylint/test/functional/return_outside_function.txt b/pylint/test/functional/return_outside_function.txt new file mode 100644 index 0000000..0c9aa56 --- /dev/null +++ b/pylint/test/functional/return_outside_function.txt @@ -0,0 +1 @@ +return-outside-function:2::Return outside function
\ No newline at end of file diff --git a/pylint/test/functional/star_needs_assignment_target_py35.py b/pylint/test/functional/star_needs_assignment_target_py35.py index d2801de..58e43db 100644 --- a/pylint/test/functional/star_needs_assignment_target_py35.py +++ b/pylint/test/functional/star_needs_assignment_target_py35.py @@ -2,9 +2,14 @@ Test PEP 0448 -- Additional Unpacking Generalizations https://www.python.org/dev/peps/pep-0448/ """ + +# pylint: disable=superfluous-parens + UNPACK_TUPLE = (*range(4), 4) UNPACK_LIST = [*range(4), 4] UNPACK_SET = {*range(4), 4} UNPACK_DICT = {'a': 1, **{'b': '2'}} UNPACK_DICT2 = {**UNPACK_DICT, "x": 1, "y": 2} UNPACK_DICT3 = {**{'a': 1}, 'a': 2, **{'a': 3}} + +UNPACK_IN_COMP = {elem for elem in (*range(10))} # [star-needs-assignment-target] diff --git a/pylint/test/functional/star_needs_assignment_target_py35.txt b/pylint/test/functional/star_needs_assignment_target_py35.txt index e69de29..0777052 100644 --- a/pylint/test/functional/star_needs_assignment_target_py35.txt +++ b/pylint/test/functional/star_needs_assignment_target_py35.txt @@ -0,0 +1 @@ +star-needs-assignment-target:15::Can use starred expression only in assignment target
\ No newline at end of file diff --git a/pylint/test/functional/too_many_locals.py b/pylint/test/functional/too_many_locals.py index 56ed514..ac38a9e 100644 --- a/pylint/test/functional/too_many_locals.py +++ b/pylint/test/functional/too_many_locals.py @@ -7,3 +7,54 @@ def function(arg1, arg2, arg3, arg4, arg5): # [too-many-locals] loc1, loc2, loc3, loc4, loc5, loc6, loc7 = arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 print(loc1, loc2, loc3, loc4, loc5, loc6, loc7) + + +def too_many_locals_function(): # [too-many-locals] + """pylint will complains about too many local variables""" + args0 = 0 + args1 = args0 * 1 + args2 = args1 * 2 + args3 = args2 * 3 + args4 = args3 * 4 + args5 = args4 * 5 + args6 = args5 * 6 + args7 = args6 * 7 + args8 = args7 * 8 + args9 = args8 * 9 + args10 = args9 * 10 + args11 = args10 * 11 + args12 = args11 * 12 + args13 = args12 * 13 + args14 = args13 * 14 + args15 = args14 * 15 + return args15 + +def too_many_arguments_function(arga, argu, argi, arge, argt, args): # [too-many-arguments] + """pylint will complains about too many arguments.""" + arga = argu + arga += argi + arga += arge + arga += argt + arga += args + return arga + +def ignored_arguments_function(arga, argu, argi, + _arge=0, _argt=1, _args=None): + """pylint will ignore _arge, _argt, _args. + + Consequently pylint will only coun 13 arguments. + """ + arg0 = 0 + arg1 = arg0 * 1 + arga + arg2 = arg1 * 2 + argu + arg3 = arg2 * 3 + argi + arg4 = arg3 * 4 + _arge + arg5 = arg4 * 5 + _argt + arg6 = arg5 * 6 + arg7 = arg6 * 7 + arg8 = arg7 * 8 + arg9 = arg8 * 9 + arg9 += arg0 + if _args: + arg9 += sum(_args) + return arg9 diff --git a/pylint/test/functional/too_many_locals.txt b/pylint/test/functional/too_many_locals.txt index b94c307..4a7d19d 100644 --- a/pylint/test/functional/too_many_locals.txt +++ b/pylint/test/functional/too_many_locals.txt @@ -1 +1,3 @@ -too-many-locals:4:function:Too many local variables (16/15)
\ No newline at end of file +too-many-locals:4:function:Too many local variables (16/15)
+too-many-locals:12:too_many_locals_function:Too many local variables (16/15)
+too-many-arguments:32:too_many_arguments_function:Too many arguments (6/5)
\ No newline at end of file diff --git a/pylint/test/functional/unpacking_generalizations.py b/pylint/test/functional/unpacking_generalizations.py new file mode 100644 index 0000000..1c5fb16 --- /dev/null +++ b/pylint/test/functional/unpacking_generalizations.py @@ -0,0 +1,29 @@ +"""Various tests for unpacking generalizations added in Python 3.5"""
+
+# pylint: disable=missing-docstring, invalid-name
+
+def func_variadic_args(*args):
+ return args
+
+
+def func_variadic_positional_args(a, b, *args):
+ return a, b, args
+
+def func_positional_args(a, b, c, d):
+ return a, b, c, d
+
+
+func_variadic_args(*(2, 3), *(3, 4), *(4, 5))
+func_variadic_args(1, 2, *(2, 3), 2, 3, *(4, 5))
+func_variadic_positional_args(1, 2, *(4, 5), *(5, 6))
+func_variadic_positional_args(*(2, 3), *(4, 5), *(5, 6))
+func_variadic_positional_args(*(2, 3))
+func_variadic_positional_args(*(2, 3, 4))
+func_variadic_positional_args(1, 2, 3, *(3, 4))
+
+func_positional_args(*(2, 3, 4), *(2, 3)) # [too-many-function-args]
+func_positional_args(*(1, 2), 3) # [no-value-for-parameter]
+func_positional_args(1, *(2, ), 3, *(4, 5)) # [too-many-function-args]
+func_positional_args(1, 2, c=24, d=32, **{'d': 32}) # [repeated-keyword]
+# +1: [repeated-keyword,repeated-keyword]
+func_positional_args(1, 2, c=24, **{'c': 34, 'd': 33}, **{'d': 24})
diff --git a/pylint/test/functional/unpacking_generalizations.rc b/pylint/test/functional/unpacking_generalizations.rc new file mode 100644 index 0000000..03004f2 --- /dev/null +++ b/pylint/test/functional/unpacking_generalizations.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.5
\ No newline at end of file diff --git a/pylint/test/functional/unpacking_generalizations.txt b/pylint/test/functional/unpacking_generalizations.txt new file mode 100644 index 0000000..d92fcc9 --- /dev/null +++ b/pylint/test/functional/unpacking_generalizations.txt @@ -0,0 +1,6 @@ +too-many-function-args:24::Too many positional arguments for function call +no-value-for-parameter:25::"No value for argument 'd' in function call" +too-many-function-args:26::Too many positional arguments for function call +repeated-keyword:27::Got multiple values for keyword argument 'd' in function call +repeated-keyword:29::Got multiple values for keyword argument 'c' in function call +repeated-keyword:29::Got multiple values for keyword argument 'd' in function call
\ No newline at end of file diff --git a/pylint/test/functional/unrecognized_inline_option.py b/pylint/test/functional/unrecognized_inline_option.py new file mode 100644 index 0000000..3163b1e --- /dev/null +++ b/pylint/test/functional/unrecognized_inline_option.py @@ -0,0 +1,3 @@ +# +1: [unrecognized-inline-option] +# pylint:bouboule=1 +"""Check unknown option"""
diff --git a/pylint/test/functional/unrecognized_inline_option.txt b/pylint/test/functional/unrecognized_inline_option.txt new file mode 100644 index 0000000..922cc92 --- /dev/null +++ b/pylint/test/functional/unrecognized_inline_option.txt @@ -0,0 +1 @@ +unrecognized-inline-option:2::"Unrecognized file option 'bouboule'" diff --git a/pylint/test/input/func_e0001_py30.py b/pylint/test/input/func_e0001_py30.py deleted file mode 100644 index 9c1b727..0000000 --- a/pylint/test/input/func_e0001_py30.py +++ /dev/null @@ -1,12 +0,0 @@ -"""test string exception -""" - -__revision__ = '' - -def function1(): - """hehehe""" - raise "String Exception" - -def function2(): - """hehehe""" - raise 'exception', 'message' diff --git a/pylint/test/input/func_e0011.py b/pylint/test/input/func_e0011.py deleted file mode 100644 index f2bb592..0000000 --- a/pylint/test/input/func_e0011.py +++ /dev/null @@ -1,5 +0,0 @@ -# pylint:bouboule=1 -"""check unknown option -""" -__revision__ = 1 - diff --git a/pylint/test/input/func_too_many_locals_arguments.py b/pylint/test/input/func_too_many_locals_arguments.py deleted file mode 100644 index f63a5ee..0000000 --- a/pylint/test/input/func_too_many_locals_arguments.py +++ /dev/null @@ -1,52 +0,0 @@ -"""tests number of arguments and local variables in functions -""" - -__revision__ = None - -def too_many_locals_function(): - '''pylint will complains about too many local variables''' - args0 = 0 - args1 = args0 * 1 - args2 = args1 * 2 - args3 = args2 * 3 - args4 = args3 * 4 - args5 = args4 * 5 - args6 = args5 * 6 - args7 = args6 * 7 - args8 = args7 * 8 - args9 = args8 * 9 - args10 = args9 * 10 - args11 = args10 * 11 - args12 = args11 * 12 - args13 = args12 * 13 - args14 = args13 * 14 - args15 = args14 * 15 - return args15 - -def too_many_arguments_function(arga, argu, argi, arge, argt, args): - '''pylint will complains about too many arguments.''' - arga = argu - arga += argi - arga += arge - arga += argt - arga += args - return arga - -def ignored_arguments_function(arga, argu, argi, - _arge=0, _argt=1, _args=None): - '''pylint will ignore _arge, _argt, _args. - Consequently pylint will only coun 13 arguments''' - arg0 = 0 - arg1 = arg0 * 1 + arga - arg2 = arg1 * 2 + argu - arg3 = arg2 * 3 + argi - arg4 = arg3 * 4 + _arge - arg5 = arg4 * 5 + _argt - arg6 = arg5 * 6 - arg7 = arg6 * 7 - arg8 = arg7 * 8 - arg9 = arg8 * 9 - arg9 += arg0 - if _args: - arg9 += sum(_args) - return arg9 diff --git a/pylint/test/messages/func_e0001_py30.txt b/pylint/test/messages/func_e0001_py30.txt deleted file mode 100644 index 1cf05ca..0000000 --- a/pylint/test/messages/func_e0001_py30.txt +++ /dev/null @@ -1,2 +0,0 @@ -E: 12: invalid syntax - diff --git a/pylint/test/messages/func_e0011.txt b/pylint/test/messages/func_e0011.txt deleted file mode 100644 index 55f07b1..0000000 --- a/pylint/test/messages/func_e0011.txt +++ /dev/null @@ -1 +0,0 @@ -E: 1: Unrecognized file option 'bouboule' diff --git a/pylint/test/messages/func_return_outside_func.txt b/pylint/test/messages/func_return_outside_func.txt deleted file mode 100644 index e61be76..0000000 --- a/pylint/test/messages/func_return_outside_func.txt +++ /dev/null @@ -1 +0,0 @@ -E: 3: Return outside function diff --git a/pylint/test/messages/func_too_many_locals_arguments.txt b/pylint/test/messages/func_too_many_locals_arguments.txt deleted file mode 100644 index 8f236c2..0000000 --- a/pylint/test/messages/func_too_many_locals_arguments.txt +++ /dev/null @@ -1,2 +0,0 @@ -R: 6:too_many_locals_function: Too many local variables (16/15) -R: 26:too_many_arguments_function: Too many arguments (6/5) diff --git a/pylint/test/regrtest_data/html_crash_420.py b/pylint/test/regrtest_data/html_crash_420.py new file mode 100644 index 0000000..a0edbb5 --- /dev/null +++ b/pylint/test/regrtest_data/html_crash_420.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*-
+tag2struct = {u"#": "R_HEADER"
+ ,u"£": "RDR_HEADER"
+ ,u"µ": "RDR_DRAFT"
+ }
\ No newline at end of file diff --git a/pylint/test/test_self.py b/pylint/test/test_self.py index 228c808..b430b12 100644 --- a/pylint/test/test_self.py +++ b/pylint/test/test_self.py @@ -247,6 +247,23 @@ class RunTC(unittest.TestCase): expected = "no such option: --load-plugin" self._test_output([".", "--load-plugin"], expected_output=expected) + def test_enable_all_works(self): + module = join(HERE, 'data', 'clientmodule_test.py') + expected = textwrap.dedent(""" + No config file found, using default configuration + ************* Module data.clientmodule_test + W: 10, 8: Unused variable 'local_variable' (unused-variable) + C: 18, 4: Missing method docstring (missing-docstring) + C: 22, 0: Missing class docstring (missing-docstring) + """) + self._test_output([module, "--disable=all", "--enable=all", "-rn"], + expected_output=expected) + + def test_html_crash_report(self): + out = six.StringIO() + module = join(HERE, 'regrtest_data', 'html_crash_420.py') + self._runtest([module], code=16, reporter=HTMLReporter(out)) + if __name__ == '__main__': unittest.main() diff --git a/pylint/utils.py b/pylint/utils.py index 47297b0..e34e8e8 100644 --- a/pylint/utils.py +++ b/pylint/utils.py @@ -142,9 +142,8 @@ def category_id(cid): return MSG_TYPES_LONG.get(cid) -def _decoding_readline(stream, module): - return lambda: stream.readline().decode(module.file_encoding, - 'replace') +def _decoding_readline(stream, encoding): + return lambda: stream.readline().decode(encoding, 'replace') def tokenize_module(module): @@ -152,7 +151,8 @@ def tokenize_module(module): readline = stream.readline if sys.version_info < (3, 0): if module.file_encoding is not None: - readline = _decoding_readline(stream, module) + readline = _decoding_readline(stream, module.file_encoding) + return list(tokenize.generate_tokens(readline)) return list(tokenize.tokenize(readline)) @@ -307,6 +307,14 @@ class MessagesHandlerMixIn(object): def enable(self, msgid, scope='package', line=None, ignore_unknown=False): """reenable message of the given id""" assert scope in ('package', 'module') + if msgid == 'all': + for msgid_ in MSG_TYPES: + self.enable(msgid_, scope=scope, line=line) + if not self._python3_porting_mode: + # Don't activate the python 3 porting checker if it + # wasn't activated explicitly. + self.disable('python3') + return catid = category_id(msgid) # msgid is a category? if catid is not None: @@ -7,9 +7,6 @@ # pygtk.require(). #init-hook= -# Profiled execution. -profile=no - # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS |